diff --git a/masque/test/test_utils.py b/masque/test/test_utils.py index d142896..941e54c 100644 --- a/masque/test/test_utils.py +++ b/masque/test/test_utils.py @@ -5,7 +5,15 @@ 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, normalize_mirror, DeferredDict +from ..utils import ( + DeferredDict, + apply_transforms, + normalize_mirror, + poly_contains_points, + remove_colinear_vertices, + remove_duplicate_vertices, + rotation_matrix_2d, +) from ..file.utils import tmpfile from ..utils.curves import bezier from ..error import PatternError @@ -23,6 +31,23 @@ def test_remove_duplicate_vertices() -> None: assert_equal(v_clean_open, [[0, 0], [1, 1], [2, 2], [0, 0]]) +def test_remove_duplicate_vertices_tolerance_defaults_to_exact_match() -> None: + v = [[0, 0], [1, 1], [1 + 1e-13, 1], [2, 2], [1e-13, 0]] + + assert_allclose(remove_duplicate_vertices(v, closed_path=True), v, atol=0, rtol=0) + assert_allclose( + remove_duplicate_vertices(v, closed_path=True, tolerance=1e-12), + [[0, 0], [1 + 1e-13, 1], [2, 2]], + atol=0, + rtol=0, + ) + + +def test_remove_duplicate_vertices_rejects_negative_tolerance() -> None: + with pytest.raises(ValueError, match='non-negative'): + remove_duplicate_vertices([[0, 0]], tolerance=-1) + + def test_remove_colinear_vertices() -> None: v = [[0, 0], [1, 0], [2, 0], [2, 1], [2, 2], [1, 1], [0, 0]] v_clean = remove_colinear_vertices(v, closed_path=True) diff --git a/masque/utils/vertices.py b/masque/utils/vertices.py index 5a5df9f..0eaf13e 100644 --- a/masque/utils/vertices.py +++ b/masque/utils/vertices.py @@ -5,7 +5,11 @@ import numpy from numpy.typing import NDArray, ArrayLike -def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) -> NDArray[numpy.float64]: +def remove_duplicate_vertices( + vertices: ArrayLike, + closed_path: bool = True, + tolerance: float = 0.0, + ) -> NDArray[numpy.float64]: """ Given a list of vertices, remove any consecutive duplicates. @@ -13,14 +17,22 @@ def remove_duplicate_vertices(vertices: ArrayLike, closed_path: bool = True) -> vertices: `[[x0, y0], [x1, y1], ...]` closed_path: If True, `vertices` is interpreted as an implicity-closed path (i.e. the last vertex will be removed if it is the same as the first) + tolerance: Maximum coordinate-wise absolute difference for two vertices to + be considered duplicates. Default `0` requires exact equality. Returns: `vertices` with no consecutive duplicates. This may be a view into the original array. """ + if tolerance < 0: + raise ValueError(f'tolerance must be non-negative, got {tolerance}') + vertices = numpy.asarray(vertices) if vertices.shape[0] <= 1: return vertices - duplicates = (vertices == numpy.roll(vertices, -1, axis=0)).all(axis=1) + if tolerance == 0: + duplicates = (vertices == numpy.roll(vertices, -1, axis=0)).all(axis=1) + else: + duplicates = (numpy.abs(vertices - numpy.roll(vertices, -1, axis=0)) <= tolerance).all(axis=1) if not closed_path: duplicates[-1] = False