diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index 96225d10..cc8a7f22 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -210,20 +210,20 @@ def as_artist(self, origin=(0, 0), **kwargs): **mpl_kwargs) def _update_from_mpl_selector(self, *args, **kwargs): - # _rect_properties replace _rect_bbox in matplotlib#19864 - # "Note that if rotation != 0, ``xmin, ymin`` are interpreted as the + # _rect_properties replace _rect_bbox in matplotlib#19864, unchanged in #20839. + # "Note that if rotation != 0, ``xmin, ymin`` are always interpreted as the # lower corner, and ``xmax, ymax`` are calculated using only width and - # height assuming no rotation." + # height assuming no rotation (as specified for ``selector.extents``)." self.center = PixCoord(*self._mpl_selector.center) - if hasattr(self._mpl_selector, '_rotation'): - x0, y0, self.width, self.height, rotation = self._mpl_selector._rect_properties + xmin, xmax, ymin, ymax = self._mpl_selector.extents + self.width = 2 * (self.center.x - xmin) + self.height = 2 * (self.center.y - ymin) + if hasattr(self._mpl_selector, 'rotation'): + rotation = self._mpl_selector.rotation else: - xmin, xmax, ymin, ymax = self._mpl_selector.extents - self.width = 2 * (self.center.x - xmin) - self.height = 2 * (self.center.y - ymin) rotation = 0 - self.angle = rotation * u.radian + self.angle = rotation * u.deg if getattr(self, '_mpl_selector_callback', None) is not None: self._mpl_selector_callback(self) @@ -267,13 +267,14 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, ``selector.set_active(True)`` or ``selector.set_active(False)``. """ from matplotlib.widgets import EllipseSelector + from matplotlib._version import version as _mpl_version if hasattr(self, '_mpl_selector'): - raise Exception('Cannot attach more than one selector to a ' - 'region.') + raise Exception('Cannot attach more than one selector to a region.') - if self.angle.value != 0 and not hasattr(EllipseSelector, '_rotation'): - raise NotImplementedError('Cannot create matplotlib selector for rotated ellipse.') + if self.angle.value != 0 and not hasattr(EllipseSelector, 'rotation'): + raise NotImplementedError('Creating selectors for rotated shapes is not ' + f'yet supported with matplotlib {_mpl_version}.') if sync: sync_callback = self._update_from_mpl_selector @@ -293,8 +294,7 @@ def sync_callback(*args, **kwargs): xy0[1], self.center.y + self.height / 2) if self.angle.value != 0: - self._mpl_selector._set_corner_width_rotation(xy0, self.width, self.height, - self.angle.to_value('radian')) + self._mpl_selector.rotation = self.angle.to_value('deg') self._mpl_selector.set_active(active) self._mpl_selector_callback = callback diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index dfef2920..c62dbc40 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -203,20 +203,20 @@ def as_artist(self, origin=(0, 0), **kwargs): angle=angle, **mpl_kwargs) def _update_from_mpl_selector(self, *args, **kwargs): - # _rect_properties replace _rect_bbox in matplotlib#19864 - # "Note that if rotation != 0, ``xmin, ymin`` are interpreted as the + # _rect_properties replace _rect_bbox in matplotlib#19864, unchanged in #20839. + # "Note that if rotation != 0, ``xmin, ymin`` are always interpreted as the # lower corner, and ``xmax, ymax`` are calculated using only width and - # height assuming no rotation." + # height assuming no rotation (as specified for ``selector.extents``)." self.center = PixCoord(*self._mpl_selector.center) - if hasattr(self._mpl_selector, '_rotation'): - x0, y0, self.width, self.height, rotation = self._mpl_selector._rect_properties + xmin, xmax, ymin, ymax = self._mpl_selector.extents + self.width = 2 * (self.center.x - xmin) + self.height = 2 * (self.center.y - ymin) + if hasattr(self._mpl_selector, 'rotation'): + rotation = self._mpl_selector.rotation else: - xmin, xmax, ymin, ymax = self._mpl_selector.extents - self.width = 2 * (self.center.x - xmin) - self.height = 2 * (self.center.y - ymin) rotation = 0 - self.angle = rotation * u.radian + self.angle = rotation * u.deg if getattr(self, '_mpl_selector_callback', None) is not None: self._mpl_selector_callback(self) @@ -260,13 +260,14 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, ``selector.set_active(True)`` or ``selector.set_active(False)``. """ from matplotlib.widgets import RectangleSelector + from matplotlib._version import version as _mpl_version if hasattr(self, '_mpl_selector'): - raise Exception('Cannot attach more than one selector to a ' - 'region.') + raise Exception('Cannot attach more than one selector to a region.') - if self.angle.value != 0 and not hasattr(RectangleSelector, '_rotation'): - raise NotImplementedError('Cannot create matplotlib selector for rotated rectangle.') + if self.angle.value != 0 and not hasattr(RectangleSelector, 'rotation'): + raise NotImplementedError('Creating selectors for rotated shapes is not ' + f'yet supported with matplotlib {_mpl_version}.') if sync: sync_callback = self._update_from_mpl_selector @@ -281,13 +282,12 @@ def sync_callback(*args, **kwargs): 'linewidth': self.visual.get('linewidth', 1), 'linestyle': self.visual.get('linestyle', 'solid')}) - xy0 = [self.center.x - self.width / 2, self.center.y - self.height / 2] - self._mpl_selector.extents = (xy0[0], self.center.x + self.width / 2, - xy0[1], self.center.y + self.height / 2) + dxy = [self.width / 2, self.height / 2] + self._mpl_selector.extents = (self.center.x - dxy[0], self.center.x + dxy[0], + self.center.y - dxy[1], self.center.y + dxy[1]) if self.angle.value != 0: - self._mpl_selector._set_corner_width_rotation(xy0, self.width, self.height, - self.angle.to_value('radian')) + self._mpl_selector.rotation = self.angle.to_value('deg') self._mpl_selector.set_active(active) self._mpl_selector_callback = callback diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index c927d1e7..ebdcf594 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -117,12 +117,17 @@ def update_mask(reg): # copy() below. if not MATPLOTLIB_HAS_ROTATING_SELECTORS: with pytest.raises(NotImplementedError, - match=('Cannot create matplotlib selector for rotated ellipse.')): + match='Creating selectors for rotated shapes is not yet supported'): self.reg.as_mpl_selector(ax) angle = 0 * u.deg + expected = [8.3, 4.9, 2.0, 1.0] else: angle = self.reg.angle + expected = [8.339773, 4.810942, 2.079545, 0.8218832] + + if not sync: + expected = [3, 4, 4, 3] region = self.reg.copy(angle=angle) @@ -148,30 +153,19 @@ def update_mask(reg): ax.figure.canvas.draw() - if sync: + assert_allclose(region.center.x, expected[0]) + assert_allclose(region.center.y, expected[1]) + assert_allclose(region.width, expected[2]) + assert_allclose(region.height, expected[3]) - assert_allclose(region.center.x, 8.3) - assert_allclose(region.center.y, 4.9) - assert_allclose(region.width, 2) - assert_allclose(region.height, 1) + if sync: assert_quantity_allclose(region.angle, 0 * u.deg) - - assert_equal(mask, - region.to_mask(mode='subpixels', - subpixels=10).to_image(data.shape)) - + assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape)) else: - - assert_allclose(region.center.x, 3) - assert_allclose(region.center.y, 4) - assert_allclose(region.width, 4) - assert_allclose(region.height, 3) assert_quantity_allclose(region.angle, angle) - assert_equal(mask, 0) - with pytest.raises(Exception, match=('Cannot attach more than one ' - 'selector to a region.')): + with pytest.raises(Exception, match='Cannot attach more than one selector to a region.'): region.as_mpl_selector(ax) diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index 4fb8c63d..f737a902 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -120,12 +120,17 @@ def update_mask(reg): # call to copy() below. if not MATPLOTLIB_HAS_ROTATING_SELECTORS: with pytest.raises(NotImplementedError, - match=('Cannot create matplotlib selector for rotated rectangle.')): + match='Creating selectors for rotated shapes is not yet supported'): self.reg.as_mpl_selector(ax) angle = 0 * u.deg + expected = [8.3, 4.9, 2.0, 1.0] else: angle = self.reg.angle + expected = [8.339773, 4.810942, 2.079545, 0.8218832] + + if not sync: + expected = [3, 4, 4, 3] region = self.reg.copy(angle=angle) @@ -151,27 +156,19 @@ def update_mask(reg): ax.figure.canvas.draw() + assert_allclose(region.center.x, expected[0]) + assert_allclose(region.center.y, expected[1]) + assert_allclose(region.width, expected[2]) + assert_allclose(region.height, expected[3]) + if sync: - assert_allclose(region.center.x, 8.3) - assert_allclose(region.center.y, 4.9) - assert_allclose(region.width, 2) - assert_allclose(region.height, 1) assert_quantity_allclose(region.angle, 0 * u.deg) - - assert_equal(mask, region.to_mask( - mode='subpixels', subpixels=10).to_image(data.shape)) - + assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape)) else: - assert_allclose(region.center.x, 3) - assert_allclose(region.center.y, 4) - assert_allclose(region.width, 4) - assert_allclose(region.height, 3) assert_quantity_allclose(region.angle, angle) - assert_equal(mask, 0) - with pytest.raises(Exception, match=('Cannot attach more than one ' - 'selector to a region.')): + with pytest.raises(Exception, match='Cannot attach more than one selector to a region.'): region.as_mpl_selector(ax) diff --git a/regions/shapes/tests/utils.py b/regions/shapes/tests/utils.py index 646f3715..d3f5934b 100644 --- a/regions/shapes/tests/utils.py +++ b/regions/shapes/tests/utils.py @@ -5,7 +5,7 @@ import matplotlib # noqa HAS_MATPLOTLIB = True import matplotlib.widgets - if hasattr(matplotlib.widgets.EllipseSelector, '_rotation'): + if hasattr(matplotlib.widgets.EllipseSelector, 'rotation'): MATPLOTLIB_HAS_ROTATING_SELECTORS = True except ImportError: HAS_MATPLOTLIB = False