Skip to content

Commit 3bff348

Browse files
committed
Switch to mpl #20839 rotation implementation
1 parent 18dcec9 commit 3bff348

File tree

6 files changed

+55
-47
lines changed

6 files changed

+55
-47
lines changed

regions/_utils/optional_deps.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
try:
44
import matplotlib # noqa
55
HAS_MATPLOTLIB = True
6-
MPL_VERSION = getattr(matplotlib, '__version__', None)
7-
if MPL_VERSION is None:
8-
MPL_VERSION = matplotlib._version.version
9-
MPL_VERSION = MPL_VERSION.split('.')
6+
MPL_VER_STR = getattr(matplotlib, '__version__', None)
7+
if MPL_VER_STR is None:
8+
MPL_VER_STR = matplotlib._version.version
9+
MPL_VERSION = MPL_VER_STR.split('.')
1010
MPL_VERSION = 10 * int(MPL_VERSION[0]) + int(MPL_VERSION[1])
1111
except ImportError:
1212
HAS_MATPLOTLIB = False
13+
MPL_VER_STR = 'None'
1314
MPL_VERSION = 0

regions/shapes/ellipse.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,10 @@ def as_artist(self, origin=(0, 0), **kwargs):
212212
**mpl_kwargs)
213213

214214
def _update_from_mpl_selector(self, *args, **kwargs):
215-
# _rect_properties replace _rect_bbox in matplotlib#19864
216-
# "Note that if rotation != 0, ``xmin, ymin`` are interpreted as the
215+
# _rect_properties replace _rect_bbox in matplotlib#19864, unchanged in #20839.
216+
# "Note that if rotation != 0, ``xmin, ymin`` are always interpreted as the
217217
# lower corner, and ``xmax, ymax`` are calculated using only width and
218-
# height assuming no rotation."
218+
# height assuming no rotation (as specified for ``selector.extents``)."
219219

220220
xmin, xmax, ymin, ymax = self._mpl_selector.extents
221221
self.width = xmax - xmin
@@ -226,7 +226,7 @@ def _update_from_mpl_selector(self, *args, **kwargs):
226226
else:
227227
self.center = PixCoord(x=0.5 * (xmin + xmax), y=0.5 * (ymin + ymax))
228228
rotation = 0
229-
self.angle = rotation * u.radian
229+
self.angle = rotation * u.deg
230230

231231
if getattr(self, '_mpl_selector_callback', None) is not None:
232232
self._mpl_selector_callback(self)
@@ -274,13 +274,14 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None,
274274
``selector.set_active(True)`` or ``selector.set_active(False)``.
275275
"""
276276
from matplotlib.widgets import EllipseSelector
277-
from .._utils.optional_deps import MPL_VERSION
277+
from .._utils.optional_deps import MPL_VERSION, MPL_VER_STR
278278

279279
if hasattr(self, '_mpl_selector'):
280280
raise AttributeError('Cannot attach more than one selector to a region.')
281281

282282
if self.angle.value != 0 and not hasattr(EllipseSelector, 'rotation'):
283-
raise NotImplementedError('Cannot create matplotlib selector for rotated ellipse.')
283+
raise NotImplementedError('Creating selectors for rotated shapes is not '
284+
f'yet supported with matplotlib {MPL_VER_STR}.')
284285

285286
if sync:
286287
sync_callback = self._update_from_mpl_selector
@@ -306,7 +307,7 @@ def sync_callback(*args, **kwargs):
306307
xy0[1], self.center.y + self.height / 2)
307308

308309
if self.angle.value != 0:
309-
self._mpl_selector.rotation = self.angle.to_value('radian')
310+
self._mpl_selector.rotation = self.angle.to_value('deg')
310311

311312
self._mpl_selector.set_active(active)
312313
self._mpl_selector_callback = callback

regions/shapes/rectangle.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,10 @@ def as_artist(self, origin=(0, 0), **kwargs):
205205
angle=angle, **mpl_kwargs)
206206

207207
def _update_from_mpl_selector(self, *args, **kwargs):
208-
# _rect_properties replace _rect_bbox in matplotlib#19864
209-
# "Note that if rotation != 0, ``xmin, ymin`` are interpreted as the
208+
# _rect_properties replace _rect_bbox in matplotlib#19864, unchanged in #20839.
209+
# "Note that if rotation != 0, ``xmin, ymin`` are always interpreted as the
210210
# lower corner, and ``xmax, ymax`` are calculated using only width and
211-
# height assuming no rotation."
211+
# height assuming no rotation (as specified for ``selector.extents``)."
212212

213213
xmin, xmax, ymin, ymax = self._mpl_selector.extents
214214
self.width = xmax - xmin
@@ -219,7 +219,7 @@ def _update_from_mpl_selector(self, *args, **kwargs):
219219
else:
220220
self.center = PixCoord(x=0.5 * (xmin + xmax), y=0.5 * (ymin + ymax))
221221
rotation = 0
222-
self.angle = rotation * u.radian
222+
self.angle = rotation * u.deg
223223

224224
if getattr(self, '_mpl_selector_callback', None) is not None:
225225
self._mpl_selector_callback(self)
@@ -267,13 +267,14 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None,
267267
``selector.set_active(True)`` or ``selector.set_active(False)``.
268268
"""
269269
from matplotlib.widgets import RectangleSelector
270-
from .._utils.optional_deps import MPL_VERSION
270+
from .._utils.optional_deps import MPL_VERSION, MPL_VER_STR
271271

272272
if hasattr(self, '_mpl_selector'):
273273
raise AttributeError('Cannot attach more than one selector to a region.')
274274

275275
if self.angle.value != 0 and not hasattr(RectangleSelector, 'rotation'):
276-
raise NotImplementedError('Cannot create matplotlib selector for rotated rectangle.')
276+
raise NotImplementedError('Creating selectors for rotated shapes is not '
277+
f'yet supported with matplotlib {MPL_VER_STR}.')
277278

278279
if sync:
279280
sync_callback = self._update_from_mpl_selector
@@ -294,12 +295,12 @@ def sync_callback(*args, **kwargs):
294295

295296
self._mpl_selector = RectangleSelector(ax, sync_callback, interactive=True, **kwargs)
296297

297-
xy0 = [self.center.x - self.width / 2, self.center.y - self.height / 2]
298-
self._mpl_selector.extents = (xy0[0], self.center.x + self.width / 2,
299-
xy0[1], self.center.y + self.height / 2)
298+
dxy = [self.width / 2, self.height / 2]
299+
self._mpl_selector.extents = (self.center.x - dxy[0], self.center.x + dxy[0],
300+
self.center.y - dxy[1], self.center.y + dxy[1])
300301

301302
if self.angle.value != 0:
302-
self._mpl_selector.rotation = self.angle.to_value('radian')
303+
self._mpl_selector.rotation = self.angle.to_value('deg')
303304

304305
self._mpl_selector.set_active(active)
305306
self._mpl_selector_callback = callback

regions/shapes/tests/test_ellipse.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -128,14 +128,18 @@ def update_mask(reg):
128128
# works with rotated ellipses, the following exception check can
129129
# be removed as well as the ``angle=0 * u.deg`` in the call to
130130
# copy() below - should (hopefully) be implemented with mpl 3.6.
131+
expected = [8.3, 4.9, 2.0, 1.0]
131132
if MPL_VERSION < 36:
132133
with pytest.raises(NotImplementedError,
133-
match=('Cannot create matplotlib selector for rotated ellipse.')):
134+
match='Creating selectors for rotated shapes is not yet supported'):
134135
self.reg.as_mpl_selector(ax)
135136
angle = 0 * u.deg
136137
else:
137138
angle = self.reg.angle
138139

140+
if not sync:
141+
expected = [3, 4, 4, 3]
142+
139143
region = self.reg.copy(angle=angle)
140144

141145
selector = region.as_mpl_selector(ax, callback=update_mask, sync=sync) # noqa
@@ -146,24 +150,16 @@ def update_mask(reg):
146150

147151
ax.figure.canvas.draw()
148152

149-
if sync:
153+
assert_allclose(region.center.x, expected[0])
154+
assert_allclose(region.center.y, expected[1])
155+
assert_allclose(region.width, expected[2])
156+
assert_allclose(region.height, expected[3])
150157

151-
assert_allclose(region.center.x, 8.3)
152-
assert_allclose(region.center.y, 4.9)
153-
assert_allclose(region.width, 2)
154-
assert_allclose(region.height, 1)
158+
if sync:
155159
assert_quantity_allclose(region.angle, 0 * u.deg)
156-
157160
assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape))
158-
159161
else:
160-
161-
assert_allclose(region.center.x, 3)
162-
assert_allclose(region.center.y, 4)
163-
assert_allclose(region.width, 4)
164-
assert_allclose(region.height, 3)
165162
assert_quantity_allclose(region.angle, angle)
166-
167163
assert_equal(mask, 0)
168164

169165
with pytest.raises(AttributeError, match=('Cannot attach more than one selector to a reg')):

regions/shapes/tests/test_rectangle.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,19 @@ def update_mask(reg):
134134
# this works with rotated rectangles, the following exception
135135
# check can be removed as well as the ``angle=0 * u.deg`` in the
136136
# copy() below - should (hopefully) be implemented with mpl 3.6.
137+
expected = [8.3, 4.9, 2.0, 1.0]
137138
if MPL_VERSION < 36:
138139
with pytest.raises(NotImplementedError,
139-
match=('Cannot create matplotlib selector for rotated rectangle.')):
140+
match='Creating selectors for rotated shapes is not yet supported'):
140141
self.reg.as_mpl_selector(ax)
141142

142143
angle = 0 * u.deg
143144
else:
144145
angle = self.reg.angle
145146

147+
if not sync:
148+
expected = [3, 4, 4, 3]
149+
146150
region = self.reg.copy(angle=angle)
147151

148152
selector = region.as_mpl_selector(ax, callback=update_mask, sync=sync) # noqa
@@ -153,22 +157,16 @@ def update_mask(reg):
153157

154158
ax.figure.canvas.draw()
155159

160+
assert_allclose(region.center.x, expected[0])
161+
assert_allclose(region.center.y, expected[1])
162+
assert_allclose(region.width, expected[2])
163+
assert_allclose(region.height, expected[3])
164+
156165
if sync:
157-
assert_allclose(region.center.x, 8.3)
158-
assert_allclose(region.center.y, 4.9)
159-
assert_allclose(region.width, 2)
160-
assert_allclose(region.height, 1)
161166
assert_quantity_allclose(region.angle, 0 * u.deg)
162-
163167
assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape))
164-
165168
else:
166-
assert_allclose(region.center.x, 3)
167-
assert_allclose(region.center.y, 4)
168-
assert_allclose(region.width, 4)
169-
assert_allclose(region.height, 3)
170169
assert_quantity_allclose(region.angle, angle)
171-
172170
assert_equal(mask, 0)
173171

174172
with pytest.raises(AttributeError, match=('Cannot attach more than one selector to a reg')):

regions/shapes/tests/utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Licensed under a 3-clause BSD style license - see LICENSE.rst
2+
3+
MATPLOTLIB_HAS_ROTATING_SELECTORS = False
4+
try:
5+
import matplotlib # noqa
6+
HAS_MATPLOTLIB = True
7+
import matplotlib.widgets
8+
if hasattr(matplotlib.widgets.EllipseSelector, 'rotation'):
9+
MATPLOTLIB_HAS_ROTATING_SELECTORS = True
10+
except ImportError:
11+
HAS_MATPLOTLIB = False

0 commit comments

Comments
 (0)