From 8dfb6d44407194da7f0c96f24bab5a67bc477ee1 Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 20 Apr 2019 00:35:53 -0700 Subject: [PATCH] Move vertex-cleanup functions to utils and generalize for non-closed paths --- masque/shapes/polygon.py | 20 +++----------------- masque/utils.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/masque/shapes/polygon.py b/masque/shapes/polygon.py index 9f52af8..a0b214b 100644 --- a/masque/shapes/polygon.py +++ b/masque/shapes/polygon.py @@ -6,6 +6,7 @@ from numpy import pi from . import Shape, normalized_shape_tuple from .. import PatternError from ..utils import is_scalar, rotation_matrix_2d, vector2 +from ..utils import remove_colinear_vertices, remove_duplicate_vertices __author__ = 'Jan Petykiewicz' @@ -267,8 +268,7 @@ class Polygon(Shape): :returns: self ''' - duplicates = (self.vertices == numpy.roll(self.vertices, 1, axis=0)).all(axis=1) - self.vertices = self.vertices[~duplicates] + self.vertices = remove_duplicate_vertices(self.vertices, closed_path=True) return self def remove_colinear_vertices(self) -> 'Polygon': @@ -277,19 +277,5 @@ class Polygon(Shape): :returns: self ''' - dv0 = numpy.roll(self.vertices, 1, axis=0) - self.vertices - dv1 = numpy.roll(dv0, -1, axis=0) - - # find cases where at least one coordinate is 0 in successive dv's - eq = dv1 == dv0 - aa_colinear = numpy.logical_and(eq, dv0 == 0).any(axis=1) - - # find cases where slope is equal - with numpy.errstate(divide='ignore', invalid='ignore'): # don't care about zeroes - slope_quotient = (dv0[:, 0] * dv1[:, 1]) / (dv1[:, 0] * dv0[:, 1]) - slopes_equal = numpy.abs(slope_quotient - 1) < 1e-14 - - colinear = numpy.logical_or(aa_colinear, slopes_equal) - self.vertices = self.vertices[~colinear] + self.vertices = remove_colinear_vertices(self.vertices, closed_path=True) return self - diff --git a/masque/utils.py b/masque/utils.py index 5d99835..b1faa5f 100644 --- a/masque/utils.py +++ b/masque/utils.py @@ -55,3 +55,35 @@ def rotation_matrix_2d(theta: float) -> numpy.ndarray: """ return numpy.array([[numpy.cos(theta), -numpy.sin(theta)], [numpy.sin(theta), +numpy.cos(theta)]]) + + +def remove_duplicate_vertices(vertices: numpy.ndarray, closed_path: bool = True) -> numpy.ndarray: + duplicates = (vertices == numpy.roll(vertices, 1, axis=0)).all(axis=1) + if not closed_path: + duplicates[0] = False + return vertices[~duplicates] + + +def remove_colinear_vertices(vertices: numpy.ndarray, closed_path: bool = True) -> numpy.ndarray: + ''' + Given a list of vertices, remove any superflous vertices (i.e. + those which lie along the line formed by their neighbors) + + :param vertices: Nx2 ndarray of vertices + :param closed_path: If True, the vertices are assumed to represent an implicitly + closed path. If False, the path is assumed to be open. Default True. + :return: + ''' + # Check for dx0/dy0 == dx1/dy1 + + dv = numpy.roll(vertices, 1, axis=0) - vertices #[y0 - yn1, y1-y0, ...] + dxdy = dv * numpy.roll(dv, 1, axis=0)[:, ::-1] # [[dx1*dy0, dx1*dy0], ...] + + dxdy_diff = numpy.abs(numpy.diff(dxdy, axis=1))[:, 0] + err_mult = 2 * numpy.abs(dxdy).sum(axis=1) + 1e-40 + + slopes_equal = (dxdy_diff / err_mult) < 1e-15 + if not closed_path: + slopes_equal[[0, -1]] = False + + return vertices[~slopes_equal]