diff --git a/glue/core/roi.py b/glue/core/roi.py index 27ece95a7..f5f0114bc 100644 --- a/glue/core/roi.py +++ b/glue/core/roi.py @@ -136,11 +136,25 @@ def to_polygon(self): raise NotImplementedError() def rotate_to(self, theta): - """Rotate anticlockwise around center to position angle theta (radian)""" + """ + Rotate anticlockwise around center to position angle theta. + + Parameters + ---------- + theta : float + Angle of anticlockwise rotation around center in radian. + """ raise NotImplementedError() def rotate_by(self, dtheta, **kwargs): - """Rotate the Roi around center by angle dtheta (radian)""" + """ + Rotate the Roi around center by angle dtheta. + + Parameters + ---------- + dtheta : float + Change in anticlockwise rotation angle around center in radian. + """ self.rotate_to(getattr(self, 'theta', 0.0) + dtheta, **kwargs) def copy(self): @@ -697,9 +711,10 @@ def add_point(self, x, y): self.vy.append(y) def reset(self): - """Reset the vertex lists""" + """Reset the vertex lists and position angle""" self.vx = [] self.vy = [] + self.theta = 0 def replace_last_point(self, x, y): if len(self.vx) > 0: @@ -856,12 +871,27 @@ def move_to(self, xdelta, ydelta): def rotate_to(self, theta, center=None): """ - Rotate polygon by angle `theta` [radian] around `center` (default centroid). + Rotate polygon to position angle `theta` around `center`. + + Parameters + ---------- + theta : float + Angle of anticlockwise rotation around center in radian. + center : pair of float, optional + Coordinates of center of rotation. Defaults to + :meth:`~glue.core.roi.PolygonalROI.centroid`, for linear + "polygons" to :meth:`~glue.core.roi.PolygonalROI.mean`. """ theta = 0 if theta is None else theta - center = self.centroid() if center is None else center + # For linear (1D) "polygons" centroid is not defined. + if center is None: + if self.area() == 0: + center = self.mean() + else: + center = self.centroid() dtheta = theta - self.theta + if self.defined() and not np.isclose(dtheta % np.pi, 0.0, atol=1e-9): dx, dy = np.array([self.vx, self.vy]) - np.array(center).reshape(2, 1) self.vx, self.vy = (rotation_matrix_2d(dtheta) @ (dx, dy) + diff --git a/glue/core/tests/test_roi.py b/glue/core/tests/test_roi.py index 789f15e0a..e5aeac2f7 100644 --- a/glue/core/tests/test_roi.py +++ b/glue/core/tests/test_roi.py @@ -559,6 +559,20 @@ def test_rotate_triangle(self): assert_almost_equal(self.roi.vx, (2/3, -1/3, 2/3), decimal=12) assert_almost_equal(self.roi.vy, (0, 0, 1), decimal=12) + def test_rotate_polyline(self): + """ Test rotation of degenerate (linear) ROI around mean """ + self.roi.reset() + self.roi.add_point(-2, 0) + self.roi.add_point(4, 0) + assert_almost_equal(self.roi.mean(), (1.0, 0.0), decimal=12) + self.roi.add_point(-0.5, 0) + self.roi.add_point(-1.5, 0) + assert_almost_equal(self.roi.mean(), (0.0, 0.0), decimal=12) + assert all(np.isnan(self.roi.centroid())) + self.roi.rotate_to(np.pi/2) + assert_almost_equal(self.roi.vx, (0, 0, 0, 0), decimal=12) + assert_almost_equal(self.roi.vy, (-2, 4, -0.5, -1.5), decimal=12) + def test_append_mock_points(self): """ Test that adding points on the side of square ROI conserves area and centroid.