From 0cc6e71d2cdf76b702be851afafa97a2b8719e13 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Thu, 29 Jul 2021 15:28:26 +0200 Subject: [PATCH] Support rotation of Ellipse,RectanglePixelRegion MPL selectors --- regions/shapes/ellipse.py | 40 ++++++++++++++++++++++++------------- regions/shapes/rectangle.py | 40 ++++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index b3414671..3efc5744 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -209,13 +209,22 @@ def as_artist(self, origin=(0, 0), **kwargs): **mpl_kwargs) def _update_from_mpl_selector(self, *args, **kwargs): - xmin, xmax, ymin, ymax = self._mpl_selector.extents - self.center = PixCoord(x=0.5 * (xmin + xmax), - y=0.5 * (ymin + ymax)) - self.width = (xmax - xmin) - self.height = (ymax - ymin) - self.angle = 0. * u.deg - if self._mpl_selector_callback is not None: + # _rect_properties replace _rect_bbox in matplotlib#19864 + # "Note that if rotation != 0, ``xmin, ymin`` are interpreted as the + # lower corner, and ``xmax, ymax`` are calculated using only width and + # height assuming no rotation." + + 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 + 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 + + if getattr(self, '_mpl_selector_callback', None) is not None: self._mpl_selector_callback(self) def as_mpl_selector(self, ax, active=True, sync=True, callback=None, @@ -262,9 +271,8 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, raise Exception('Cannot attach more than one selector to a ' 'region.') - if self.angle.value != 0: - raise NotImplementedError('Cannot create matplotlib selector for ' - 'rotated ellipse.') + if self.angle.value != 0 and not hasattr(EllipseSelector, '_rotation'): + raise NotImplementedError('Cannot create matplotlib selector for rotated ellipse.') if sync: sync_callback = self._update_from_mpl_selector @@ -279,10 +287,14 @@ def sync_callback(*args, **kwargs): 'linewidth': self.visual.get('linewidth', 1), 'linestyle': self.visual.get('linestyle', 'solid')}) - self._mpl_selector.extents = (self.center.x - self.width / 2, - self.center.x + self.width / 2, - self.center.y - self.height / 2, - self.center.y + self.height / 2) + 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) + + 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.set_active(active) self._mpl_selector_callback = callback diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 57947772..49127f63 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -202,13 +202,22 @@ def as_artist(self, origin=(0, 0), **kwargs): angle=angle, **mpl_kwargs) def _update_from_mpl_selector(self, *args, **kwargs): - xmin, xmax, ymin, ymax = self._mpl_selector.extents - self.center = PixCoord(x=0.5 * (xmin + xmax), - y=0.5 * (ymin + ymax)) - self.width = (xmax - xmin) - self.height = (ymax - ymin) - self.angle = 0. * u.deg - if self._mpl_selector_callback is not None: + # _rect_properties replace _rect_bbox in matplotlib#19864 + # "Note that if rotation != 0, ``xmin, ymin`` are interpreted as the + # lower corner, and ``xmax, ymax`` are calculated using only width and + # height assuming no rotation." + + 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 + 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 + + if getattr(self, '_mpl_selector_callback', None) is not None: self._mpl_selector_callback(self) def as_mpl_selector(self, ax, active=True, sync=True, callback=None, @@ -255,9 +264,8 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, raise Exception('Cannot attach more than one selector to a ' 'region.') - if self.angle.value != 0: - raise NotImplementedError('Cannot create matplotlib selector for ' - 'rotated rectangle.') + if self.angle.value != 0 and not hasattr(RectangleSelector, '_rotation'): + raise NotImplementedError('Cannot create matplotlib selector for rotated rectangle.') if sync: sync_callback = self._update_from_mpl_selector @@ -272,10 +280,14 @@ def sync_callback(*args, **kwargs): 'linewidth': self.visual.get('linewidth', 1), 'linestyle': self.visual.get('linestyle', 'solid')}) - self._mpl_selector.extents = (self.center.x - self.width / 2, - self.center.x + self.width / 2, - self.center.y - self.height / 2, - self.center.y + self.height / 2) + 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) + + 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.set_active(active) self._mpl_selector_callback = callback