From cea2b1800f483dac985d04b0a0e8e9761568a5d6 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Fri, 12 Nov 2021 01:20:01 +0100 Subject: [PATCH 01/10] Enable passing through of **kwargs as_mpl_selector --- regions/shapes/ellipse.py | 13 +++--- regions/shapes/polygon.py | 84 +++++++++++++++++++++++++++++++++++++ regions/shapes/rectangle.py | 13 +++--- 3 files changed, 98 insertions(+), 12 deletions(-) diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index c2ddbbf50..9d029cfe0 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -273,12 +273,13 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, def sync_callback(*args, **kwargs): pass - self._mpl_selector = EllipseSelector( - ax, sync_callback, interactive=True, - rectprops={'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid')}) + rectprops = kwargs.pop('rectprops', {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid')}) + + self._mpl_selector = EllipseSelector(ax, sync_callback, interactive=True, + rectprops=rectprops, **kwargs) self._mpl_selector.extents = (self.center.x - self.width / 2, self.center.x + self.width / 2, diff --git a/regions/shapes/polygon.py b/regions/shapes/polygon.py index d4d81f583..bc3a3cc4a 100644 --- a/regions/shapes/polygon.py +++ b/regions/shapes/polygon.py @@ -176,6 +176,90 @@ def as_artist(self, origin=(0, 0), **kwargs): return Polygon(xy=xy, **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: + self._mpl_selector_callback(self) + + def as_mpl_selector(self, ax, active=True, sync=True, callback=None, **kwargs): + """ + A matplotlib editable widget for this region + (`matplotlib.widgets.PolygonSelector`). + + Parameters + ---------- + ax : `~matplotlib.axes.Axes` + The matplotlib axes to add the selector to. + active : bool, optional + Whether the selector should be active by default. + sync : bool, optional + If `True` (the default), the region will be kept in + sync with the selector. Otherwise, the selector will be + initialized with the values from the region but the two will + then be disconnected. + callback : callable, optional + If specified, this function will be called every time the + region is updated. This only has an effect if ``sync`` is + `True`. If a callback is set, it is called for the first + time once the selector has been created. + **kwargs : dict + Additional keyword arguments that are passed to + `matplotlib.widgets.PolygonSelector`. + + Returns + ------- + selector : `matplotlib.widgets.PolygonSelector` + The matplotlib selector. + + Notes + ----- + Once a selector has been created, you will need to keep a + reference to it until you no longer need it. In addition, + you can enable/disable the selector at any point by calling + ``selector.set_active(True)`` or ``selector.set_active(False)``. + """ + from matplotlib.widgets import PolygonSelector + + if hasattr(self, '_mpl_selector'): + 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 sync: + sync_callback = self._update_from_mpl_selector + else: + def sync_callback(*args, **kwargs): + pass + + props = kwargs.pop('props', {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid'), + 'alpha': self.visual.get('alpha', 0.3)}) + + self._mpl_selector = PolygonSelector(ax, sync_callback, interactive=True, + props=props, **kwargs) + + 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) + self._mpl_selector.set_active(active) + self._mpl_selector_callback = callback + + if sync and self._mpl_selector_callback is not None: + self._mpl_selector_callback(self) + + return self._mpl_selector + def rotate(self, center, angle): """ Rotate the region. diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 697cdb9b2..42aff54f2 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -266,12 +266,13 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, def sync_callback(*args, **kwargs): pass - self._mpl_selector = RectangleSelector( - ax, sync_callback, interactive=True, - rectprops={'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid')}) + rectprops = kwargs.pop('rectprops', {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid')}) + + self._mpl_selector = RectangleSelector(ax, sync_callback, interactive=True, + rectprops=rectprops, **kwargs) self._mpl_selector.extents = (self.center.x - self.width / 2, self.center.x + self.width / 2, From 54e39d5af0ef1fd19587542a70978baa989fe873 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Fri, 7 Jan 2022 21:06:06 +0100 Subject: [PATCH 02/10] Use correct rectangle `props` kwarg for mpl 3.5+ --- regions/shapes/ellipse.py | 22 ++++++++++++++++------ regions/shapes/rectangle.py | 22 ++++++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index 9d029cfe0..b0dd601ea 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -258,6 +258,12 @@ 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 + import matplotlib._version + from packaging.version import Version + _mpl_version = getattr(matplotlib._version, 'version', None) + if _mpl_version is None: + _mpl_version = matplotlib._version.get_versions()['version'] + _mpl_version = Version(_mpl_version) if hasattr(self, '_mpl_selector'): raise Exception('Cannot attach more than one selector to a ' @@ -273,13 +279,17 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, def sync_callback(*args, **kwargs): pass - rectprops = kwargs.pop('rectprops', {'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid')}) + # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. + rectprops = kwargs.pop('props', {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid')}) + if _mpl_version < Version('3.5'): + kwargs.update({'rectprops': rectprops}) + else: + kwargs.update({'props': rectprops}) - self._mpl_selector = EllipseSelector(ax, sync_callback, interactive=True, - rectprops=rectprops, **kwargs) + self._mpl_selector = EllipseSelector(ax, sync_callback, interactive=True, **kwargs) self._mpl_selector.extents = (self.center.x - self.width / 2, self.center.x + self.width / 2, diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 42aff54f2..d958ff417 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -251,6 +251,12 @@ 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 + import matplotlib._version + from packaging.version import Version + _mpl_version = getattr(matplotlib._version, 'version', None) + if _mpl_version is None: + _mpl_version = matplotlib._version.get_versions()['version'] + _mpl_version = Version(_mpl_version) if hasattr(self, '_mpl_selector'): raise Exception('Cannot attach more than one selector to a ' @@ -266,13 +272,17 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, def sync_callback(*args, **kwargs): pass - rectprops = kwargs.pop('rectprops', {'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid')}) + # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. + rectprops = kwargs.pop('props', {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid')}) + if _mpl_version < Version('3.5'): + kwargs.update({'rectprops': rectprops}) + else: + kwargs.update({'props': rectprops}) - self._mpl_selector = RectangleSelector(ax, sync_callback, interactive=True, - rectprops=rectprops, **kwargs) + self._mpl_selector = RectangleSelector(ax, sync_callback, interactive=True, **kwargs) self._mpl_selector.extents = (self.center.x - self.width / 2, self.center.x + self.width / 2, From f5d73ec597eff98a4a7d4b0302d9b284709d23eb Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Fri, 7 Jan 2022 21:07:14 +0100 Subject: [PATCH 03/10] TST: add drag test; use mpl `on_select` --- regions/shapes/tests/test_ellipse.py | 31 +++------ regions/shapes/tests/test_rectangle.py | 87 ++++++++++++++++++-------- 2 files changed, 68 insertions(+), 50 deletions(-) diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index 64f173619..da85eb22a 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -107,6 +107,7 @@ def test_rotate(self): def test_as_mpl_selector(self, sync): plt = pytest.importorskip('matplotlib.pyplot') + from matplotlib.testing.widgets import do_event data = np.random.random((16, 16)) mask = np.zeros_like(data) @@ -115,39 +116,23 @@ def test_as_mpl_selector(self, sync): ax.imshow(data) def update_mask(reg): - mask[:] = reg.to_mask(mode='subpixels', - subpixels=10).to_image(data.shape) + mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) # For now this will only work with unrotated ellipses. Once this # works with rotated ellipses, the following exception check can # be removed as well as the ``angle=0 * u.deg`` in the call to # copy() below. with pytest.raises(NotImplementedError, - match=('Cannot create matplotlib selector for ' - 'rotated ellipse.')): + match=('Cannot create matplotlib selector for rotated ellipse.')): self.reg.as_mpl_selector(ax) region = self.reg.copy(angle=0 * u.deg) selector = region.as_mpl_selector(ax, callback=update_mask, sync=sync) # noqa - from matplotlib.backend_bases import MouseEvent, MouseButton - - x, y = ax.transData.transform([[7.3, 4.4]])[0] - ax.figure.canvas.callbacks.process('button_press_event', - MouseEvent('button_press_event', - ax.figure.canvas, x, y, - button=MouseButton.LEFT)) - x, y = ax.transData.transform([[9.3, 5.4]])[0] - ax.figure.canvas.callbacks.process('motion_notify_event', - MouseEvent('button_press_event', - ax.figure.canvas, x, y, - button=MouseButton.LEFT)) - x, y = ax.transData.transform([[9.3, 5.4]])[0] - ax.figure.canvas.callbacks.process('button_release_event', - MouseEvent('button_press_event', - ax.figure.canvas, x, y, - button=MouseButton.LEFT)) + do_event(selector, 'press', xdata=7.3, ydata=4.4, button=1) + do_event(selector, 'onmove', xdata=9.3, ydata=5.4, button=1) + do_event(selector, 'release', xdata=9.3, ydata=5.4, button=1) ax.figure.canvas.draw() @@ -159,9 +144,7 @@ def update_mask(reg): 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: diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index 46d5cb5f8..4f6f74fe8 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -103,12 +103,10 @@ def test_rotate(self): assert_allclose(reg.center.xy, (1, 4)) assert_allclose(reg.angle.to_value('deg'), 95) - # TODO: Is this MatplotlibDeprecationWarning something to worry about? - @pytest.mark.filterwarnings(r"ignore:The 'rectprops' parameter of " - r"__init__\(\) has been renamed 'props'") @pytest.mark.parametrize('sync', (False, True)) def test_as_mpl_selector(self, sync): plt = pytest.importorskip('matplotlib.pyplot') + from matplotlib.testing.widgets import do_event data = np.random.random((16, 16)) mask = np.zeros_like(data) @@ -117,39 +115,23 @@ def test_as_mpl_selector(self, sync): ax.imshow(data) def update_mask(reg): - mask[:] = reg.to_mask( - mode='subpixels', subpixels=10).to_image(data.shape) + mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) # For now this will only work with unrotated rectangles. Once # this works with rotated rectangles, the following exception # check can be removed as well as the ``angle=0 * u.deg`` in the # call to copy() below. with pytest.raises(NotImplementedError, - match=('Cannot create matplotlib selector for ' - 'rotated rectangle.')): + match=('Cannot create matplotlib selector for rotated rectangle.')): self.reg.as_mpl_selector(ax) region = self.reg.copy(angle=0 * u.deg) selector = region.as_mpl_selector(ax, callback=update_mask, sync=sync) # noqa - from matplotlib.backend_bases import MouseEvent, MouseButton - - x, y = ax.transData.transform([[7.3, 4.4]])[0] - ax.figure.canvas.callbacks.process('button_press_event', - MouseEvent('button_press_event', - ax.figure.canvas, x, y, - button=MouseButton.LEFT)) - x, y = ax.transData.transform([[9.3, 5.4]])[0] - ax.figure.canvas.callbacks.process('motion_notify_event', - MouseEvent('button_press_event', - ax.figure.canvas, x, y, - button=MouseButton.LEFT)) - x, y = ax.transData.transform([[9.3, 5.4]])[0] - ax.figure.canvas.callbacks.process('button_release_event', - MouseEvent('button_press_event', - ax.figure.canvas, x, y, - button=MouseButton.LEFT)) + do_event(selector, 'press', xdata=7.3, ydata=4.4, button=1) + do_event(selector, 'onmove', xdata=9.3, ydata=5.4, button=1) + do_event(selector, 'release', xdata=9.3, ydata=5.4, button=1) ax.figure.canvas.draw() @@ -160,8 +142,7 @@ def update_mask(reg): 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) @@ -176,6 +157,60 @@ def update_mask(reg): 'selector to a region.')): region.as_mpl_selector(ax) + @pytest.mark.parametrize('anywhere', (False, True)) + def test_mpl_selector_drag(self, anywhere): + """Test dragging of entire region from central handle and anywhere.""" + + plt = pytest.importorskip('matplotlib.pyplot') + from matplotlib.testing.widgets import do_event # click_and_drag + + data = np.random.random((16, 16)) + mask = np.zeros_like(data) + + ax = plt.subplot(1, 1, 1) + ax.imshow(data) + + def update_mask(reg): + mask[:] = reg.to_mask( + mode='subpixels', subpixels=10).to_image(data.shape) + + region = self.reg.copy(angle=0 * u.deg) + + selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) + + # click_and_drag(selector, start=(3, 4), end=(3.5, 4.5)) + do_event(selector, 'press', xdata=3, ydata=4, button=1) + do_event(selector, 'onmove', xdata=3.5, ydata=4.5, button=1) + do_event(selector, 'release', xdata=3.5, ydata=4.5, button=1) + + ax.figure.canvas.draw() + + assert_allclose(region.center.x, 3.5) + assert_allclose(region.center.y, 4.5) + assert_allclose(region.width, 4) + assert_allclose(region.height, 3) + + do_event(selector, 'press', xdata=3.25, ydata=4.25, button=1) + do_event(selector, 'onmove', xdata=4.25, ydata=5.25, button=1) + do_event(selector, 'release', xdata=4.25, ydata=5.25, button=1) + + ax.figure.canvas.draw() + + # For drag_from_anywhere=False this will have created a new 1x1 rectangle. + if anywhere: + assert_allclose(region.center.x, 4.5) + assert_allclose(region.center.y, 5.5) + assert_allclose(region.width, 4) + assert_allclose(region.height, 3) + else: + assert_allclose(region.center.x, 4.5) + assert_allclose(region.center.y, 5.5) + + assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape)) + + assert selector.drag_from_anywhere is anywhere + assert region._mpl_selector.drag_from_anywhere is anywhere + def test_rectangular_pixel_region_bbox(): # odd sizes From 909f82fa26404fb479ca42d545f7d21dcd0f739a Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Sat, 8 Jan 2022 00:24:19 +0100 Subject: [PATCH 04/10] Correct default `props`, add kwargs tests --- regions/shapes/ellipse.py | 13 ++-- regions/shapes/rectangle.py | 13 ++-- regions/shapes/tests/test_ellipse.py | 95 +++++++++++++++++++++++++- regions/shapes/tests/test_rectangle.py | 45 ++++++++++-- 4 files changed, 152 insertions(+), 14 deletions(-) diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index b0dd601ea..3736a83ae 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -241,6 +241,10 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, region is updated. This only has an effect if ``sync`` is `True`. If a callback is set, it is called for the first time once the selector has been created. + drag_from_anywhere : bool, optional + If `True`, the selector can be moved by clicking anywhere within + its bounds, else only at the central anchor + (only available with matplotlib 3.5 upwards; default: `False`). **kwargs : dict Additional keyword arguments that are passed to `matplotlib.widgets.EllipseSelector`. @@ -279,11 +283,12 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, def sync_callback(*args, **kwargs): pass + rectprops = {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid')} + rectprops.update(kwargs.pop('props', dict())) # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. - rectprops = kwargs.pop('props', {'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid')}) if _mpl_version < Version('3.5'): kwargs.update({'rectprops': rectprops}) else: diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index d958ff417..8f85c4cd5 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -234,6 +234,10 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, region is updated. This only has an effect if ``sync`` is `True`. If a callback is set, it is called for the first time once the selector has been created. + drag_from_anywhere : bool, optional + If `True`, the selector can be moved by clicking anywhere within + its bounds, else only at the central anchor + (only available with matplotlib 3.5 upwards; default: `False`). **kwargs : dict Additional keyword arguments are passed to `matplotlib.widgets.RectangleSelector`. @@ -272,11 +276,12 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, def sync_callback(*args, **kwargs): pass + rectprops = {'edgecolor': self.visual.get('color', 'black'), + 'facecolor': 'none', + 'linewidth': self.visual.get('linewidth', 1), + 'linestyle': self.visual.get('linestyle', 'solid')} + rectprops.update(kwargs.pop('props', dict())) # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. - rectprops = kwargs.pop('props', {'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid')}) if _mpl_version < Version('3.5'): kwargs.update({'rectprops': rectprops}) else: diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index da85eb22a..cf0089bc8 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -156,10 +156,101 @@ def update_mask(reg): 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) + @pytest.mark.parametrize('anywhere', (False, True)) + def test_mpl_selector_drag(self, anywhere): + """Test dragging of entire region from central handle and anywhere.""" + + plt = pytest.importorskip('matplotlib.pyplot') + from matplotlib.testing.widgets import do_event # click_and_drag + + data = np.random.random((16, 16)) + mask = np.zeros_like(data) + + ax = plt.subplot(1, 1, 1) + ax.imshow(data) + + def update_mask(reg): + mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) + + region = self.reg.copy(angle=0 * u.deg) + + selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) + + # click_and_drag(selector, start=(3, 4), end=(3.5, 4.5)) + do_event(selector, 'press', xdata=3, ydata=4, button=1) + do_event(selector, 'onmove', xdata=3.5, ydata=4.5, button=1) + do_event(selector, 'release', xdata=3.5, ydata=4.5, button=1) + + ax.figure.canvas.draw() + + assert_allclose(region.center.x, 3.5) + assert_allclose(region.center.y, 4.5) + assert_allclose(region.width, 4) + assert_allclose(region.height, 3) + + do_event(selector, 'press', xdata=3.25, ydata=4.25, button=1) + do_event(selector, 'onmove', xdata=4.25, ydata=5.25, button=1) + do_event(selector, 'release', xdata=4.25, ydata=5.25, button=1) + + ax.figure.canvas.draw() + + # For drag_from_anywhere=False this will have created a new 1x1 rectangle. + if anywhere: + assert_allclose(region.center.x, 4.5) + assert_allclose(region.center.y, 5.5) + assert_allclose(region.width, 4) + assert_allclose(region.height, 3) + else: + assert_allclose(region.center.x, 4.5) + assert_allclose(region.center.y, 5.5) + + assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape)) + + assert selector.drag_from_anywhere is anywhere + assert region._mpl_selector.drag_from_anywhere is anywhere + + @pytest.mark.parametrize('userargs', + ({'useblit': True}, + {'grab_range': 20, 'minspanx': 5, 'minspany': 4}, + {'props': {'facecolor': 'blue', 'linewidth': 2}}, + {'twit': 'gumby'})) + def test_mpl_selector_kwargs(self, userargs): + """Test that additional kwargs are passed to selector.""" + + plt = pytest.importorskip('matplotlib.pyplot') + + data = np.random.random((16, 16)) + mask = np.zeros_like(data) + + ax = plt.subplot(1, 1, 1) + ax.imshow(data) + + def update_mask(reg): + mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) + + region = self.reg.copy(angle=0 * u.deg) + + if 'twit' in userargs: + with pytest.raises(TypeError, match=(r'__init__.. got an unexpected keyword argument')): + selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) + else: + selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) + assert region._mpl_selector.artists[0].get_edgecolor() == (0, 0, 0, 1) + + if 'props' in userargs: + assert region._mpl_selector.artists[0].get_facecolor() == (0, 0, 1, 1) + assert region._mpl_selector.artists[0].get_linewidth() == 2 + else: + assert region._mpl_selector.artists[0].get_facecolor() == (0, 0, 0, 0) + assert region._mpl_selector.artists[0].get_linewidth() == 1 + + for key, val in userargs.items(): + assert getattr(region._mpl_selector, key) == val + assert getattr(selector, key) == val + class TestEllipseSkyRegion(BaseTestSkyRegion): meta = RegionMeta({'text': 'test'}) diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index 4f6f74fe8..106645350 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -153,8 +153,7 @@ def update_mask(reg): 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) @pytest.mark.parametrize('anywhere', (False, True)) @@ -171,8 +170,7 @@ def test_mpl_selector_drag(self, anywhere): ax.imshow(data) def update_mask(reg): - mask[:] = reg.to_mask( - mode='subpixels', subpixels=10).to_image(data.shape) + mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) region = self.reg.copy(angle=0 * u.deg) @@ -211,6 +209,45 @@ def update_mask(reg): assert selector.drag_from_anywhere is anywhere assert region._mpl_selector.drag_from_anywhere is anywhere + @pytest.mark.parametrize('userargs', + ({'useblit': True}, + {'grab_range': 20, 'minspanx': 5, 'minspany': 4}, + {'props': {'facecolor': 'blue', 'linewidth': 2}}, + {'twit': 'gumby'})) + def test_mpl_selector_kwargs(self, userargs): + """Test that additional kwargs are passed to selector.""" + + plt = pytest.importorskip('matplotlib.pyplot') + + data = np.random.random((16, 16)) + mask = np.zeros_like(data) + + ax = plt.subplot(1, 1, 1) + ax.imshow(data) + + def update_mask(reg): + mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) + + region = self.reg.copy(angle=0 * u.deg) + + if 'twit' in userargs: + with pytest.raises(TypeError, match=(r'__init__.. got an unexpected keyword argument')): + selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) + else: + selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) + assert region._mpl_selector.artists[0].get_edgecolor() == (0, 0, 0, 1) + + if 'props' in userargs: + assert region._mpl_selector.artists[0].get_facecolor() == (0, 0, 1, 1) + assert region._mpl_selector.artists[0].get_linewidth() == 2 + else: + assert region._mpl_selector.artists[0].get_facecolor() == (0, 0, 0, 0) + assert region._mpl_selector.artists[0].get_linewidth() == 1 + + for key, val in userargs.items(): + assert getattr(region._mpl_selector, key) == val + assert getattr(selector, key) == val + def test_rectangular_pixel_region_bbox(): # odd sizes From 8acaa58804274a03726e815822d1e17da63c6995 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Sat, 8 Jan 2022 19:03:27 +0100 Subject: [PATCH 05/10] MNT: Mpl version management; intersphinx fix --- docs/conf.py | 1 + regions/shapes/ellipse.py | 15 ++++----------- regions/shapes/rectangle.py | 12 +++--------- regions/shapes/tests/test_circle.py | 2 +- regions/shapes/tests/test_ellipse.py | 24 +++++++++++++++--------- regions/shapes/tests/test_line.py | 2 +- regions/shapes/tests/test_point.py | 2 +- regions/shapes/tests/test_polygon.py | 2 +- regions/shapes/tests/test_rectangle.py | 21 +++++++++++++++------ regions/shapes/tests/utils.py | 7 ------- regions/shapes/utils.py | 13 +++++++++++++ 11 files changed, 55 insertions(+), 46 deletions(-) delete mode 100644 regions/shapes/tests/utils.py create mode 100644 regions/shapes/utils.py diff --git a/docs/conf.py b/docs/conf.py index 45669fd70..6cc639e56 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,6 +41,7 @@ # Extend astropy intersphinx_mapping with packages we use here intersphinx_mapping['photutils'] = ('https://photutils.readthedocs.io/en/stable/', None) # noqa #intersphinx_mapping['shapely'] = ('https://shapely.readthedocs.io/en/stable/', None) # noqa +intersphinx_mapping['matplotlib'] = ('https://matplotlib.org/stable/', None) # noqa # Exclude astropy intersphinx_mapping for unused packages del intersphinx_mapping['scipy'] # noqa diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index 3736a83ae..a2c1080c3 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -262,20 +262,13 @@ 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 - import matplotlib._version - from packaging.version import Version - _mpl_version = getattr(matplotlib._version, 'version', None) - if _mpl_version is None: - _mpl_version = matplotlib._version.get_versions()['version'] - _mpl_version = Version(_mpl_version) + from .utils import 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: - raise NotImplementedError('Cannot create matplotlib selector for ' - 'rotated ellipse.') + raise NotImplementedError('Cannot create matplotlib selector for rotated ellipse.') if sync: sync_callback = self._update_from_mpl_selector @@ -289,7 +282,7 @@ def sync_callback(*args, **kwargs): 'linestyle': self.visual.get('linestyle', 'solid')} rectprops.update(kwargs.pop('props', dict())) # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. - if _mpl_version < Version('3.5'): + if MPL_VERSION < 35: kwargs.update({'rectprops': rectprops}) else: kwargs.update({'props': rectprops}) diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 8f85c4cd5..936363e3c 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -255,16 +255,10 @@ 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 - import matplotlib._version - from packaging.version import Version - _mpl_version = getattr(matplotlib._version, 'version', None) - if _mpl_version is None: - _mpl_version = matplotlib._version.get_versions()['version'] - _mpl_version = Version(_mpl_version) + from .utils import 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: raise NotImplementedError('Cannot create matplotlib selector for ' @@ -282,7 +276,7 @@ def sync_callback(*args, **kwargs): 'linestyle': self.visual.get('linestyle', 'solid')} rectprops.update(kwargs.pop('props', dict())) # `rectprops` renamed `props` in mpl 3.5 and deprecated for 3.7. - if _mpl_version < Version('3.5'): + if MPL_VERSION < 35: kwargs.update({'rectprops': rectprops}) else: kwargs.update({'props': rectprops}) diff --git a/regions/shapes/tests/test_circle.py b/regions/shapes/tests/test_circle.py index 14a634bca..b159c2dcb 100644 --- a/regions/shapes/tests/test_circle.py +++ b/regions/shapes/tests/test_circle.py @@ -14,7 +14,7 @@ from ...tests.helpers import make_simple_wcs from ..circle import CirclePixelRegion, CircleSkyRegion from .test_common import BaseTestPixelRegion, BaseTestSkyRegion -from .utils import HAS_MATPLOTLIB # noqa +from ..utils import HAS_MATPLOTLIB # noqa @pytest.fixture(scope='session', name='wcs') diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index cf0089bc8..469aa282e 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -15,7 +15,7 @@ from ...tests.helpers import make_simple_wcs from ..ellipse import EllipsePixelRegion, EllipseSkyRegion from .test_common import BaseTestPixelRegion, BaseTestSkyRegion -from .utils import HAS_MATPLOTLIB # noqa +from ..utils import HAS_MATPLOTLIB, MPL_VERSION # noqa @pytest.fixture(scope='session', name='wcs') @@ -100,9 +100,7 @@ def test_rotate(self): assert_allclose(reg.center.xy, (1, 4)) assert_allclose(reg.angle.to_value("deg"), 95) - # TODO: Is this MatplotlibDeprecationWarning something to worry about? - @pytest.mark.filterwarnings(r"ignore:The 'rectprops' parameter of " - r"__init__\(\) has been renamed 'props'") + @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') @pytest.mark.parametrize('sync', (False, True)) def test_as_mpl_selector(self, sync): @@ -159,12 +157,13 @@ def update_mask(reg): with pytest.raises(Exception, match=('Cannot attach more than one selector to a region.')): region.as_mpl_selector(ax) + @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') @pytest.mark.parametrize('anywhere', (False, True)) def test_mpl_selector_drag(self, anywhere): """Test dragging of entire region from central handle and anywhere.""" plt = pytest.importorskip('matplotlib.pyplot') - from matplotlib.testing.widgets import do_event # click_and_drag + from matplotlib.testing.widgets import do_event data = np.random.random((16, 16)) mask = np.zeros_like(data) @@ -177,7 +176,14 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) - selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) + if anywhere and MPL_VERSION < 35: + pytest.skip('Requires `drag_from_anywhere` kwarg') + elif MPL_VERSION < 35: + selector = region.as_mpl_selector(ax, callback=update_mask) + else: + selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) + assert selector.drag_from_anywhere is anywhere + assert region._mpl_selector.drag_from_anywhere is anywhere # click_and_drag(selector, start=(3, 4), end=(3.5, 4.5)) do_event(selector, 'press', xdata=3, ydata=4, button=1) @@ -209,9 +215,6 @@ def update_mask(reg): assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape)) - assert selector.drag_from_anywhere is anywhere - assert region._mpl_selector.drag_from_anywhere is anywhere - @pytest.mark.parametrize('userargs', ({'useblit': True}, {'grab_range': 20, 'minspanx': 5, 'minspany': 4}, @@ -233,6 +236,9 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) + if MPL_VERSION < 35 and 'grab_range' in userargs: + userargs['maxdist'] = userargs.pop('grab_range') + if 'twit' in userargs: with pytest.raises(TypeError, match=(r'__init__.. got an unexpected keyword argument')): selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) diff --git a/regions/shapes/tests/test_line.py b/regions/shapes/tests/test_line.py index 6db4221ce..ac7eba4c7 100644 --- a/regions/shapes/tests/test_line.py +++ b/regions/shapes/tests/test_line.py @@ -15,7 +15,7 @@ from ...tests.helpers import make_simple_wcs from ..line import LinePixelRegion, LineSkyRegion from .test_common import BaseTestPixelRegion, BaseTestSkyRegion -from .utils import HAS_MATPLOTLIB # noqa +from ..utils import HAS_MATPLOTLIB # noqa @pytest.fixture(scope='session', name='wcs') diff --git a/regions/shapes/tests/test_point.py b/regions/shapes/tests/test_point.py index d431c9c90..90fe90740 100644 --- a/regions/shapes/tests/test_point.py +++ b/regions/shapes/tests/test_point.py @@ -14,7 +14,7 @@ from ...tests.helpers import make_simple_wcs from ..point import PointPixelRegion, PointSkyRegion from .test_common import BaseTestPixelRegion, BaseTestSkyRegion -from .utils import HAS_MATPLOTLIB # noqa +from ..utils import HAS_MATPLOTLIB # noqa @pytest.fixture(scope='session', name='wcs') diff --git a/regions/shapes/tests/test_polygon.py b/regions/shapes/tests/test_polygon.py index 3088f8f78..44902d012 100644 --- a/regions/shapes/tests/test_polygon.py +++ b/regions/shapes/tests/test_polygon.py @@ -15,7 +15,7 @@ from ..polygon import (PolygonPixelRegion, RegularPolygonPixelRegion, PolygonSkyRegion) from .test_common import BaseTestPixelRegion, BaseTestSkyRegion -from .utils import HAS_MATPLOTLIB # noqa +from ..utils import HAS_MATPLOTLIB # noqa @pytest.fixture(scope='session', name='wcs') diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index 106645350..fb49961d5 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -15,7 +15,7 @@ from ...tests.helpers import make_simple_wcs from ..rectangle import RectanglePixelRegion, RectangleSkyRegion from .test_common import BaseTestPixelRegion, BaseTestSkyRegion -from .utils import HAS_MATPLOTLIB # noqa +from ..utils import HAS_MATPLOTLIB, MPL_VERSION # noqa @pytest.fixture(scope='session', name='wcs') @@ -103,6 +103,7 @@ def test_rotate(self): assert_allclose(reg.center.xy, (1, 4)) assert_allclose(reg.angle.to_value('deg'), 95) + @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') @pytest.mark.parametrize('sync', (False, True)) def test_as_mpl_selector(self, sync): plt = pytest.importorskip('matplotlib.pyplot') @@ -156,12 +157,13 @@ def update_mask(reg): with pytest.raises(Exception, match=('Cannot attach more than one selector to a region.')): region.as_mpl_selector(ax) + @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') @pytest.mark.parametrize('anywhere', (False, True)) def test_mpl_selector_drag(self, anywhere): """Test dragging of entire region from central handle and anywhere.""" plt = pytest.importorskip('matplotlib.pyplot') - from matplotlib.testing.widgets import do_event # click_and_drag + from matplotlib.testing.widgets import do_event # click_and_drag # MPL_VERSION >= 36 data = np.random.random((16, 16)) mask = np.zeros_like(data) @@ -174,7 +176,14 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) - selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) + if anywhere and MPL_VERSION < 35: + pytest.skip('Requires `drag_from_anywhere` kwarg') + elif MPL_VERSION < 35: + selector = region.as_mpl_selector(ax, callback=update_mask) + else: + selector = region.as_mpl_selector(ax, callback=update_mask, drag_from_anywhere=anywhere) + assert selector.drag_from_anywhere is anywhere + assert region._mpl_selector.drag_from_anywhere is anywhere # click_and_drag(selector, start=(3, 4), end=(3.5, 4.5)) do_event(selector, 'press', xdata=3, ydata=4, button=1) @@ -206,9 +215,6 @@ def update_mask(reg): assert_equal(mask, region.to_mask(mode='subpixels', subpixels=10).to_image(data.shape)) - assert selector.drag_from_anywhere is anywhere - assert region._mpl_selector.drag_from_anywhere is anywhere - @pytest.mark.parametrize('userargs', ({'useblit': True}, {'grab_range': 20, 'minspanx': 5, 'minspany': 4}, @@ -230,6 +236,9 @@ def update_mask(reg): region = self.reg.copy(angle=0 * u.deg) + if MPL_VERSION < 35 and 'grab_range' in userargs: + userargs['maxdist'] = userargs.pop('grab_range') + if 'twit' in userargs: with pytest.raises(TypeError, match=(r'__init__.. got an unexpected keyword argument')): selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) diff --git a/regions/shapes/tests/utils.py b/regions/shapes/tests/utils.py deleted file mode 100644 index f879f6251..000000000 --- a/regions/shapes/tests/utils.py +++ /dev/null @@ -1,7 +0,0 @@ -# Licensed under a 3-clause BSD style license - see LICENSE.rst - -try: - import matplotlib # noqa - HAS_MATPLOTLIB = True -except ImportError: - HAS_MATPLOTLIB = False diff --git a/regions/shapes/utils.py b/regions/shapes/utils.py new file mode 100644 index 000000000..2dd1709de --- /dev/null +++ b/regions/shapes/utils.py @@ -0,0 +1,13 @@ +# Licensed under a 3-clause BSD style license - see LICENSE.rst + +try: + import matplotlib # noqa + HAS_MATPLOTLIB = True + MPL_VERSION = getattr(matplotlib._version, 'version', None) + if MPL_VERSION is None: + MPL_VERSION = matplotlib._version.get_versions()['version'] + MPL_VERSION = MPL_VERSION.split('.') + MPL_VERSION = 10 * int(MPL_VERSION[0]) + int(MPL_VERSION[1]) +except ImportError: + HAS_MATPLOTLIB = False + MPL_VERSION = 0 From 6d7a847955e33c98b0738f31fc0920a2df51934c Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Sun, 9 Jan 2022 16:05:42 +0100 Subject: [PATCH 06/10] Revert changes to `polygon.py` from unrelated PR --- regions/shapes/polygon.py | 84 --------------------------------------- 1 file changed, 84 deletions(-) diff --git a/regions/shapes/polygon.py b/regions/shapes/polygon.py index bc3a3cc4a..d4d81f583 100644 --- a/regions/shapes/polygon.py +++ b/regions/shapes/polygon.py @@ -176,90 +176,6 @@ def as_artist(self, origin=(0, 0), **kwargs): return Polygon(xy=xy, **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: - self._mpl_selector_callback(self) - - def as_mpl_selector(self, ax, active=True, sync=True, callback=None, **kwargs): - """ - A matplotlib editable widget for this region - (`matplotlib.widgets.PolygonSelector`). - - Parameters - ---------- - ax : `~matplotlib.axes.Axes` - The matplotlib axes to add the selector to. - active : bool, optional - Whether the selector should be active by default. - sync : bool, optional - If `True` (the default), the region will be kept in - sync with the selector. Otherwise, the selector will be - initialized with the values from the region but the two will - then be disconnected. - callback : callable, optional - If specified, this function will be called every time the - region is updated. This only has an effect if ``sync`` is - `True`. If a callback is set, it is called for the first - time once the selector has been created. - **kwargs : dict - Additional keyword arguments that are passed to - `matplotlib.widgets.PolygonSelector`. - - Returns - ------- - selector : `matplotlib.widgets.PolygonSelector` - The matplotlib selector. - - Notes - ----- - Once a selector has been created, you will need to keep a - reference to it until you no longer need it. In addition, - you can enable/disable the selector at any point by calling - ``selector.set_active(True)`` or ``selector.set_active(False)``. - """ - from matplotlib.widgets import PolygonSelector - - if hasattr(self, '_mpl_selector'): - 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 sync: - sync_callback = self._update_from_mpl_selector - else: - def sync_callback(*args, **kwargs): - pass - - props = kwargs.pop('props', {'edgecolor': self.visual.get('color', 'black'), - 'facecolor': 'none', - 'linewidth': self.visual.get('linewidth', 1), - 'linestyle': self.visual.get('linestyle', 'solid'), - 'alpha': self.visual.get('alpha', 0.3)}) - - self._mpl_selector = PolygonSelector(ax, sync_callback, interactive=True, - props=props, **kwargs) - - 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) - self._mpl_selector.set_active(active) - self._mpl_selector_callback = callback - - if sync and self._mpl_selector_callback is not None: - self._mpl_selector_callback(self) - - return self._mpl_selector - def rotate(self, center, angle): """ Rotate the region. From 69a1440403feab22b289475fcc83a0548de9d5f4 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Thu, 27 Jan 2022 16:18:41 +0100 Subject: [PATCH 07/10] Precise `_mpl_selector` exceptions; revert to Astropy 5.0.1 for intersphinx --- docs/conf.py | 1 - regions/shapes/ellipse.py | 2 +- regions/shapes/rectangle.py | 2 +- regions/shapes/tests/test_ellipse.py | 2 +- regions/shapes/tests/test_rectangle.py | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 6cc639e56..45669fd70 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -41,7 +41,6 @@ # Extend astropy intersphinx_mapping with packages we use here intersphinx_mapping['photutils'] = ('https://photutils.readthedocs.io/en/stable/', None) # noqa #intersphinx_mapping['shapely'] = ('https://shapely.readthedocs.io/en/stable/', None) # noqa -intersphinx_mapping['matplotlib'] = ('https://matplotlib.org/stable/', None) # noqa # Exclude astropy intersphinx_mapping for unused packages del intersphinx_mapping['scipy'] # noqa diff --git a/regions/shapes/ellipse.py b/regions/shapes/ellipse.py index a2c1080c3..8fa2f3cc0 100644 --- a/regions/shapes/ellipse.py +++ b/regions/shapes/ellipse.py @@ -265,7 +265,7 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, from .utils import MPL_VERSION if hasattr(self, '_mpl_selector'): - raise Exception('Cannot attach more than one selector to a region.') + raise AttributeError('Cannot attach more than one selector to a region.') if self.angle.value != 0: raise NotImplementedError('Cannot create matplotlib selector for rotated ellipse.') diff --git a/regions/shapes/rectangle.py b/regions/shapes/rectangle.py index 936363e3c..6ea634992 100644 --- a/regions/shapes/rectangle.py +++ b/regions/shapes/rectangle.py @@ -258,7 +258,7 @@ def as_mpl_selector(self, ax, active=True, sync=True, callback=None, from .utils import MPL_VERSION if hasattr(self, '_mpl_selector'): - raise Exception('Cannot attach more than one selector to a region.') + raise AttributeError('Cannot attach more than one selector to a region.') if self.angle.value != 0: raise NotImplementedError('Cannot create matplotlib selector for ' diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index 469aa282e..ddde0b2b2 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -154,7 +154,7 @@ def update_mask(reg): assert_equal(mask, 0) - with pytest.raises(Exception, match=('Cannot attach more than one selector to a region.')): + with pytest.raises(AttributeError, match=('Cannot attach more than one selector to a reg')): region.as_mpl_selector(ax) @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index fb49961d5..264c18c5e 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -154,7 +154,7 @@ def update_mask(reg): assert_equal(mask, 0) - with pytest.raises(Exception, match=('Cannot attach more than one selector to a region.')): + with pytest.raises(AttributeError, match=('Cannot attach more than one selector to a reg')): region.as_mpl_selector(ax) @pytest.mark.skipif(MPL_VERSION < 33, reason='requires `do_event`') From ff6ff37b161ee073089902774779ae40611ecf19 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Tue, 1 Feb 2022 18:53:55 +0100 Subject: [PATCH 08/10] DOC: masks example needed mpl 3.6 --- docs/masks.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/masks.rst b/docs/masks.rst index 01cf4f99b..a0229623e 100644 --- a/docs/masks.rst +++ b/docs/masks.rst @@ -321,6 +321,6 @@ and averaged flux, which is updated live in the text field of the plot as well. ellipse = EllipsePixelRegion(center=PixCoord(x=126, y=1031), width=8, height=4, angle=-0*u.deg, visual={'color': 'yellow'}) - selector = ellipse.as_mpl_selector(ax, callback=update_sel, use_data_coordinates=True) + selector = ellipse.as_mpl_selector(ax, callback=update_sel) hdulist.close() From c7445fca181a3518f26c44ddfe3210ee11cb48c9 Mon Sep 17 00:00:00 2001 From: Larry Bradley Date: Wed, 2 Feb 2022 16:26:17 -0500 Subject: [PATCH 09/10] Update mpl version check --- regions/shapes/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/regions/shapes/utils.py b/regions/shapes/utils.py index 2dd1709de..1732ae465 100644 --- a/regions/shapes/utils.py +++ b/regions/shapes/utils.py @@ -3,9 +3,9 @@ try: import matplotlib # noqa HAS_MATPLOTLIB = True - MPL_VERSION = getattr(matplotlib._version, 'version', None) + MPL_VERSION = getattr(matplotlib, '__version__', None) if MPL_VERSION is None: - MPL_VERSION = matplotlib._version.get_versions()['version'] + MPL_VERSION = matplotlib._version.version MPL_VERSION = MPL_VERSION.split('.') MPL_VERSION = 10 * int(MPL_VERSION[0]) + int(MPL_VERSION[1]) except ImportError: From c862fa40b227caeefecccdfd80c26d2a05a9f6b5 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Wed, 2 Feb 2022 23:27:16 +0100 Subject: [PATCH 10/10] TST: check for correct default colours in `mpl_selector` props --- regions/shapes/tests/test_ellipse.py | 3 ++- regions/shapes/tests/test_rectangle.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/regions/shapes/tests/test_ellipse.py b/regions/shapes/tests/test_ellipse.py index ddde0b2b2..eb8fc07f0 100644 --- a/regions/shapes/tests/test_ellipse.py +++ b/regions/shapes/tests/test_ellipse.py @@ -235,6 +235,7 @@ def update_mask(reg): mask[:] = reg.to_mask(mode='subpixels', subpixels=10).to_image(data.shape) region = self.reg.copy(angle=0 * u.deg) + region.visual = {'color': 'red'} if MPL_VERSION < 35 and 'grab_range' in userargs: userargs['maxdist'] = userargs.pop('grab_range') @@ -244,7 +245,7 @@ def update_mask(reg): selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) else: selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) - assert region._mpl_selector.artists[0].get_edgecolor() == (0, 0, 0, 1) + assert region._mpl_selector.artists[0].get_edgecolor() == (1, 0, 0, 1) if 'props' in userargs: assert region._mpl_selector.artists[0].get_facecolor() == (0, 0, 1, 1) diff --git a/regions/shapes/tests/test_rectangle.py b/regions/shapes/tests/test_rectangle.py index 264c18c5e..93de1a4ea 100644 --- a/regions/shapes/tests/test_rectangle.py +++ b/regions/shapes/tests/test_rectangle.py @@ -244,7 +244,7 @@ def update_mask(reg): selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) else: selector = region.as_mpl_selector(ax, callback=update_mask, **userargs) - assert region._mpl_selector.artists[0].get_edgecolor() == (0, 0, 0, 1) + assert region._mpl_selector.artists[0].get_edgecolor() == (0, 0, 1, 1) if 'props' in userargs: assert region._mpl_selector.artists[0].get_facecolor() == (0, 0, 1, 1)