From d366db5a62f59c1c126bfc317f3acdbc5780757b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Wed, 1 Apr 2026 21:59:27 -0700 Subject: [PATCH] [utils.transform] better input validation in normalize_mirror and apply_transform --- masque/test/test_utils.py | 24 +++++++++++++++++++++++- masque/utils/transform.py | 10 +++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/masque/test/test_utils.py b/masque/test/test_utils.py index 0511a24..ddab9cd 100644 --- a/masque/test/test_utils.py +++ b/masque/test/test_utils.py @@ -5,7 +5,7 @@ from numpy.testing import assert_equal, assert_allclose from numpy import pi import pytest -from ..utils import remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points, rotation_matrix_2d, apply_transforms, DeferredDict +from ..utils import remove_duplicate_vertices, remove_colinear_vertices, poly_contains_points, rotation_matrix_2d, apply_transforms, normalize_mirror, DeferredDict from ..file.utils import tmpfile from ..utils.curves import bezier from ..error import PatternError @@ -94,6 +94,28 @@ def test_apply_transforms_advanced() -> None: assert_allclose(combined[0], [0, 10, pi / 2, 1, 1], atol=1e-10) +def test_apply_transforms_empty_inputs() -> None: + empty_outer = apply_transforms(numpy.empty((0, 5)), [[1, 2, 0, 0, 1]]) + assert empty_outer.shape == (0, 5) + + empty_inner = apply_transforms([[1, 2, 0, 0, 1]], numpy.empty((0, 5))) + assert empty_inner.shape == (0, 5) + + both_empty_tensor = apply_transforms(numpy.empty((0, 5)), numpy.empty((0, 5)), tensor=True) + assert both_empty_tensor.shape == (0, 0, 5) + + +def test_normalize_mirror_validates_length() -> None: + with pytest.raises(ValueError, match='2-item sequence'): + normalize_mirror(()) + + with pytest.raises(ValueError, match='2-item sequence'): + normalize_mirror((True,)) + + with pytest.raises(ValueError, match='2-item sequence'): + normalize_mirror((True, False, True)) + + def test_bezier_validates_weight_length() -> None: with pytest.raises(PatternError, match='one entry per control point'): bezier([[0, 0], [1, 1]], [0, 0.5, 1], weights=[1]) diff --git a/masque/utils/transform.py b/masque/utils/transform.py index ed0453b..7b39122 100644 --- a/masque/utils/transform.py +++ b/masque/utils/transform.py @@ -50,7 +50,10 @@ def normalize_mirror(mirrored: Sequence[bool]) -> tuple[bool, float]: `angle_to_rotate` in radians """ - mirrored_x, mirrored_y = mirrored + if len(mirrored) != 2: + raise ValueError(f'mirrored must be a 2-item sequence, got length {len(mirrored)}') + + mirrored_x, mirrored_y = (bool(value) for value in mirrored) mirror_x = (mirrored_x != mirrored_y) # XOR angle = numpy.pi if mirrored_y else 0 return mirror_x, angle @@ -111,6 +114,11 @@ def apply_transforms( if inner.shape[1] == 4: inner = numpy.pad(inner, ((0, 0), (0, 1)), constant_values=1.0) + if outer.shape[0] == 0 or inner.shape[0] == 0: + if tensor: + return numpy.empty((outer.shape[0], inner.shape[0], 5)) + return numpy.empty((0, 5)) + # If mirrored, flip y's xy_mir = numpy.tile(inner[:, :2], (outer.shape[0], 1, 1)) # dims are outer, inner, xyrm xy_mir[outer[:, 3].astype(bool), :, 1] *= -1