From 42a39b845f8b473f51501e58ab8d84f8829e2494 Mon Sep 17 00:00:00 2001 From: "P. L. Lim" <2090236+pllim@users.noreply.github.com> Date: Thu, 23 Feb 2023 10:17:20 -0500 Subject: [PATCH 01/16] TST: Fix typo in test case --- glue/core/tests/test_units.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glue/core/tests/test_units.py b/glue/core/tests/test_units.py index e302b50e4..1e2b13a6a 100644 --- a/glue/core/tests/test_units.py +++ b/glue/core/tests/test_units.py @@ -68,7 +68,7 @@ def equivalent_units(self, data, cid, units): elif units in ['dix', 'vingt', 'trente']: return ['dix', 'vingt', 'trente'] else: - raise ValueError(f'Unrecongized unit: {units}') + raise ValueError(f'Unrecognized unit: {units}') numerical = { 'one': 1, From 61572495877ea487d806a05bd552a88c1bc44aae Mon Sep 17 00:00:00 2001 From: astrofrog Date: Mon, 20 Mar 2023 22:23:41 +0000 Subject: [PATCH 02/16] Update CHANGELOG --- CHANGES.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index cdc05b198..f9ce7ef00 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,32 @@ # Full changelog +## v1.8.0 - 2023-03-20 + + +### What's Changed + +#### New Features + +- Relax longitude range in fullsphere projections by @Carifio24 in https://github.com/glue-viz/glue/pull/2348 + +#### Bug Fixes + +- Update polar log transform test values to correct matplotlib 3.7 representation by @Carifio24 in https://github.com/glue-viz/glue/pull/2366 +- Fix broken link editor under Qt5 by @jfoster17 in https://github.com/glue-viz/glue/pull/2375 + +#### Documentation + +- DOC: Fix equivalent_units in examples by @pllim in https://github.com/glue-viz/glue/pull/2369 + +#### Other Changes + +- Update link for slack invite by @astrofrog in https://github.com/glue-viz/glue/pull/2362 +- Update stable version of standalone app to 2023.02.0 by @astrofrog in https://github.com/glue-viz/glue/pull/2363 +- MNT: drop runtime dependency on pkg_resources (setuptools) by @neutrinoceros in https://github.com/glue-viz/glue/pull/2365 +- BUG: handle deprecation warning from numpy 1.25 (np.product is deprecated in favour of np.prod) by @neutrinoceros in https://github.com/glue-viz/glue/pull/2371 + +**Full Changelog**: https://github.com/glue-viz/glue/compare/v1.7.0...v1.8.0 + ## 1.8.0 - 2023-03-20 @@ -596,43 +623,53 @@ - - - +- - - `glue.viewers.common.viewer.BaseViewer` and - - - +- - - `glue.viewers.common.viewer.Viewer`. - - - +- - - - - - +- - - `glue.viewers.common.viewer.Viewer` is now where the base logic is defined - - - +- - - for using state classes in viewers (instead of - - - +- - - `glue.viewers.common.qt.DataViewerWithState`). - - - +- - - - - - +- - - `glue.viewers.common.qt.DataViewerWithState` is now deprecated. - - - +- - - - - - - +- - Make it so that the modest image only resamples the data when the - mouse is no longer pressed - this avoids too many refreshes when - panning/zooming. https://github.com/glue-viz/glue/pull/1866 From 176585372689c6de3fb7930f6cad97389c854196 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Mon, 20 Mar 2023 23:51:36 +0000 Subject: [PATCH 03/16] Remove duplicate changelog section --- CHANGES.md | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f9ce7ef00..fcf858e5c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,33 +27,6 @@ **Full Changelog**: https://github.com/glue-viz/glue/compare/v1.7.0...v1.8.0 -## 1.8.0 - 2023-03-20 - - -### What's Changed - -#### New Features - -- Relax longitude range in fullsphere projections by @Carifio24 in https://github.com/glue-viz/glue/pull/2348 - -#### Bug Fixes - -- Update polar log transform test values to correct matplotlib 3.7 representation by @Carifio24 in https://github.com/glue-viz/glue/pull/2366 -- Fix broken link editor under Qt5 by @jfoster17 in https://github.com/glue-viz/glue/pull/2375 - -#### Documentation - -- DOC: Fix equivalent_units in examples by @pllim in https://github.com/glue-viz/glue/pull/2369 - -#### Other Changes - -- Update link for slack invite by @astrofrog in https://github.com/glue-viz/glue/pull/2362 -- Update stable version of standalone app to 2023.02.0 by @astrofrog in https://github.com/glue-viz/glue/pull/2363 -- MNT: drop runtime dependency on pkg_resources (setuptools) by @neutrinoceros in https://github.com/glue-viz/glue/pull/2365 -- BUG: handle deprecation warning from numpy 1.25 (np.product is deprecated in favour of np.prod) by @neutrinoceros in https://github.com/glue-viz/glue/pull/2371 - -**Full Changelog**: https://github.com/glue-viz/glue/compare/v1.7.0...1.8.0 - ## v1.7.0 - 2023-02-02 From c451e266c71827867a21c7a38df2498ff693e123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Robert?= Date: Tue, 21 Mar 2023 17:08:44 +0100 Subject: [PATCH 04/16] BUG: fix plugin iteration on Python 3.8 and 3.9 --- glue/_plugin_helpers.py | 13 ++++++------- setup.cfg | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/glue/_plugin_helpers.py b/glue/_plugin_helpers.py index 5f9a85551..52820b6c7 100644 --- a/glue/_plugin_helpers.py +++ b/glue/_plugin_helpers.py @@ -9,16 +9,15 @@ import os import sys from collections import defaultdict -from importlib.metadata import entry_points +if sys.version_info >= (3, 10): + from importlib.metadata import entry_points +else: + from importlib_metadata import entry_points -def iter_plugin_entry_points(): - if sys.version_info >= (3, 10): - plugins = entry_points(group='glue.plugins') - else: - plugins = entry_points().get('glue.plugins', []) - return iter(plugins) +def iter_plugin_entry_points(): + return iter(entry_points(group='glue.plugins')) class PluginConfig(object): diff --git a/setup.cfg b/setup.cfg index c9f4346bf..495653af3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -44,6 +44,7 @@ install_requires = mpl-scatter-density>=0.7 pvextractor>=0.2 importlib_resources>=1.3; python_version<'3.9' + importlib_metadata>=3.6; python_version<'3.10' [options.entry_points] glue.plugins = From ce30cd25ab4b115df72a622e29f62b6e5bdb8cb1 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Wed, 1 Mar 2023 01:09:16 +0100 Subject: [PATCH 05/16] TST: xfail misplaced legends scatter plots --- glue/tests/helpers.py | 2 ++ glue/viewers/scatter/qt/tests/test_python_export.py | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/glue/tests/helpers.py b/glue/tests/helpers.py index db26cb393..bdfc68d13 100644 --- a/glue/tests/helpers.py +++ b/glue/tests/helpers.py @@ -47,6 +47,8 @@ def mark_creator(installed, lbl, vrs): MATPLOTLIB_GE_37, xfail_matplotlib_lt_37 = make_xfailer('matplotlib', version='3.7') +MATPLOTLIB_LT_37, xfail_matplotlib_ge_37 = make_xfailer('matplotlib', version='3.7', xfail_if='ge') + ASTRODENDRO_INSTALLED, requires_astrodendro = make_skipper('astrodendro') SCIPY_INSTALLED, requires_scipy = make_skipper('scipy', label='SciPy') diff --git a/glue/viewers/scatter/qt/tests/test_python_export.py b/glue/viewers/scatter/qt/tests/test_python_export.py index ab89994a3..ad15d3650 100644 --- a/glue/viewers/scatter/qt/tests/test_python_export.py +++ b/glue/viewers/scatter/qt/tests/test_python_export.py @@ -8,6 +8,7 @@ from glue.app.qt.application import GlueApplication from glue.viewers.matplotlib.qt.tests.test_python_export import BaseTestExportPython, random_with_nan from glue.viewers.scatter.qt import ScatterViewer +from glue.tests.helpers import xfail_matplotlib_ge_37 class TestExportPython(BaseTestExportPython): @@ -33,6 +34,7 @@ def teardown_method(self, method): def test_simple(self, tmpdir): self.assert_same(tmpdir) + @xfail_matplotlib_ge_37 def test_simple_legend(self, tmpdir): self.viewer.state.legend.visible = True self.assert_same(tmpdir) @@ -48,6 +50,7 @@ def test_simple_visual(self, tmpdir): self.viewer.state.layers[0].alpha = 0.5 self.assert_same(tmpdir) + @xfail_matplotlib_ge_37 def test_simple_visual_legend(self, tmpdir): self.viewer.state.legend.visible = True self.viewer.state.layers[0].color = 'blue' @@ -77,6 +80,7 @@ def test_size_mode(self, tmpdir): self.viewer.state.layers[0].alpha = 0.7 self.assert_same(tmpdir) + @xfail_matplotlib_ge_37 def test_size_mode_legend(self, tmpdir): self.viewer.state.legend.visible = True self.viewer.state.layers[0].size_mode = 'Linear' @@ -130,6 +134,7 @@ def test_errorbarxy(self, tmpdir): self.viewer.state.layers[0].alpha = 0.5 self.assert_same(tmpdir) + @xfail_matplotlib_ge_37 def test_errorbarxy_legend(self, tmpdir): self.viewer.state.legend.visible = True self.viewer.state.layers[0].xerr_visible = True From 93d6a4bac42450a9f2d950edf54e79da63d6e129 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Wed, 1 Mar 2023 01:30:15 +0100 Subject: [PATCH 06/16] Resize `MplPolygonalROI` to keep out of legend's way --- glue/core/roi.py | 5 ++--- glue/tests/helpers.py | 2 -- glue/viewers/scatter/qt/tests/test_python_export.py | 5 ----- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/glue/core/roi.py b/glue/core/roi.py index f5f0114bc..da4425d07 100644 --- a/glue/core/roi.py +++ b/glue/core/roi.py @@ -1612,7 +1612,7 @@ def __init__(self, axes, roi=None, data_space=True): 'facecolor': PATCH_COLOR, 'alpha': 0.3} - self._patch = Polygon(np.array(list(zip([0, 1], [0, 1]))), zorder=100) + self._patch = Polygon(np.array(list(zip([0.2, 0.3], [0.2, 0.3]))), zorder=100) self._patch.set_visible(False) if not self._data_space: self._patch.set_transform(self._axes.transAxes) @@ -1621,8 +1621,7 @@ def __init__(self, axes, roi=None, data_space=True): def _sync_patch(self): if self._roi.defined(): x, y = self._roi.to_polygon() - self._patch.set_xy(list(zip(x + [x[0]], - y + [y[0]]))) + self._patch.set_xy(list(zip(x + [x[0]], y + [y[0]]))) self._patch.set_visible(True) self._patch.set(**self.plot_opts) else: diff --git a/glue/tests/helpers.py b/glue/tests/helpers.py index bdfc68d13..db26cb393 100644 --- a/glue/tests/helpers.py +++ b/glue/tests/helpers.py @@ -47,8 +47,6 @@ def mark_creator(installed, lbl, vrs): MATPLOTLIB_GE_37, xfail_matplotlib_lt_37 = make_xfailer('matplotlib', version='3.7') -MATPLOTLIB_LT_37, xfail_matplotlib_ge_37 = make_xfailer('matplotlib', version='3.7', xfail_if='ge') - ASTRODENDRO_INSTALLED, requires_astrodendro = make_skipper('astrodendro') SCIPY_INSTALLED, requires_scipy = make_skipper('scipy', label='SciPy') diff --git a/glue/viewers/scatter/qt/tests/test_python_export.py b/glue/viewers/scatter/qt/tests/test_python_export.py index ad15d3650..ab89994a3 100644 --- a/glue/viewers/scatter/qt/tests/test_python_export.py +++ b/glue/viewers/scatter/qt/tests/test_python_export.py @@ -8,7 +8,6 @@ from glue.app.qt.application import GlueApplication from glue.viewers.matplotlib.qt.tests.test_python_export import BaseTestExportPython, random_with_nan from glue.viewers.scatter.qt import ScatterViewer -from glue.tests.helpers import xfail_matplotlib_ge_37 class TestExportPython(BaseTestExportPython): @@ -34,7 +33,6 @@ def teardown_method(self, method): def test_simple(self, tmpdir): self.assert_same(tmpdir) - @xfail_matplotlib_ge_37 def test_simple_legend(self, tmpdir): self.viewer.state.legend.visible = True self.assert_same(tmpdir) @@ -50,7 +48,6 @@ def test_simple_visual(self, tmpdir): self.viewer.state.layers[0].alpha = 0.5 self.assert_same(tmpdir) - @xfail_matplotlib_ge_37 def test_simple_visual_legend(self, tmpdir): self.viewer.state.legend.visible = True self.viewer.state.layers[0].color = 'blue' @@ -80,7 +77,6 @@ def test_size_mode(self, tmpdir): self.viewer.state.layers[0].alpha = 0.7 self.assert_same(tmpdir) - @xfail_matplotlib_ge_37 def test_size_mode_legend(self, tmpdir): self.viewer.state.legend.visible = True self.viewer.state.layers[0].size_mode = 'Linear' @@ -134,7 +130,6 @@ def test_errorbarxy(self, tmpdir): self.viewer.state.layers[0].alpha = 0.5 self.assert_same(tmpdir) - @xfail_matplotlib_ge_37 def test_errorbarxy_legend(self, tmpdir): self.viewer.state.legend.visible = True self.viewer.state.layers[0].xerr_visible = True From 463352629b65cf67ceaea2d88fa24602da11d019 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Fri, 3 Mar 2023 04:02:28 +0100 Subject: [PATCH 07/16] Add ROI patches only to axes if defined() --- glue/core/roi.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/glue/core/roi.py b/glue/core/roi.py index da4425d07..2a0a72b55 100644 --- a/glue/core/roi.py +++ b/glue/core/roi.py @@ -1147,7 +1147,6 @@ def __init__(self, axes, data_space=True): self._patch.set_visible(False) if not self._data_space: self._patch.set_transform(self._axes.transAxes) - self._axes.add_patch(self._patch) def start_selection(self, event): if event.inaxes != self._axes: @@ -1229,7 +1228,10 @@ def _sync_patch(self): self._patch.set_height(height) self._patch.set(**self.plot_opts) self._patch.set_visible(True) + self._axes.add_patch(self._patch) else: + if self._patch in self._axes.patches: + self._axes._remove_method(self._patch) self._patch.set_visible(False) def __str__(self): @@ -1264,7 +1266,6 @@ def __init__(self, axes, data_space=True): trans = self._axes.transAxes self._patch = Rectangle((0., 0.), 1., 1., transform=trans, zorder=100) self._patch.set_visible(False) - self._axes.add_patch(self._patch) def start_selection(self, event): @@ -1336,7 +1337,10 @@ def _sync_patch(self): self._patch.set_height(1) self._patch.set(**self.plot_opts) self._patch.set_visible(True) + self._axes.add_patch(self._patch) else: + if self._patch in self._axes.patches: + self._axes._remove_method(self._patch) self._patch.set_visible(False) @@ -1368,7 +1372,6 @@ def __init__(self, axes, data_space=True): trans = self._axes.transAxes self._patch = Rectangle((0., 0.), 1., 1., transform=trans, zorder=100) self._patch.set_visible(False) - self._axes.add_patch(self._patch) def start_selection(self, event): @@ -1442,7 +1445,10 @@ def _sync_patch(self): self._patch.set_width(1) self._patch.set(**self.plot_opts) self._patch.set_visible(True) + self._axes.add_patch(self._patch) else: + if self._patch in self._axes.patches: + self._axes._remove_method(self._patch) self._patch.set_visible(False) @@ -1476,7 +1482,6 @@ def __init__(self, axes, data_space=True): self._patch = Ellipse((0., 0.), transform=IdentityTransform(), width=0., height=0., zorder=100) self._patch.set_visible(False) - self._axes.add_patch(self._patch) def _sync_patch(self): if self._roi.defined(): @@ -1487,7 +1492,10 @@ def _sync_patch(self): self._patch.height = 2. * r self._patch.set(**self.plot_opts) self._patch.set_visible(True) + self._axes.add_patch(self._patch) else: + if self._patch in self._axes.patches: + self._axes._remove_method(self._patch) self._patch.set_visible(False) def start_selection(self, event): @@ -1612,11 +1620,10 @@ def __init__(self, axes, roi=None, data_space=True): 'facecolor': PATCH_COLOR, 'alpha': 0.3} - self._patch = Polygon(np.array(list(zip([0.2, 0.3], [0.2, 0.3]))), zorder=100) + self._patch = Polygon(np.array(list(zip([0, 1], [0, 1]))), zorder=100) self._patch.set_visible(False) if not self._data_space: self._patch.set_transform(self._axes.transAxes) - self._axes.add_patch(self._patch) def _sync_patch(self): if self._roi.defined(): @@ -1624,7 +1631,10 @@ def _sync_patch(self): self._patch.set_xy(list(zip(x + [x[0]], y + [y[0]]))) self._patch.set_visible(True) self._patch.set(**self.plot_opts) + self._axes.add_patch(self._patch) else: + if self._patch in self._axes.patches: + self._axes._remove_method(self._patch) self._patch.set_visible(False) def start_selection(self, event, scrubbing=False): From 522d92680fb4d612454ea23acd5d2521f2f6b8c9 Mon Sep 17 00:00:00 2001 From: Derek Homeier Date: Tue, 21 Mar 2023 16:17:33 +0100 Subject: [PATCH 08/16] Let `self._patch` remove itself Co-authored by: Jon Carifio --- glue/core/roi.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/glue/core/roi.py b/glue/core/roi.py index 2a0a72b55..23629d206 100644 --- a/glue/core/roi.py +++ b/glue/core/roi.py @@ -1231,7 +1231,7 @@ def _sync_patch(self): self._axes.add_patch(self._patch) else: if self._patch in self._axes.patches: - self._axes._remove_method(self._patch) + self._patch._remove_method(self._patch) self._patch.set_visible(False) def __str__(self): @@ -1340,7 +1340,7 @@ def _sync_patch(self): self._axes.add_patch(self._patch) else: if self._patch in self._axes.patches: - self._axes._remove_method(self._patch) + self._patch._remove_method(self._patch) self._patch.set_visible(False) @@ -1448,7 +1448,7 @@ def _sync_patch(self): self._axes.add_patch(self._patch) else: if self._patch in self._axes.patches: - self._axes._remove_method(self._patch) + self._patch._remove_method(self._patch) self._patch.set_visible(False) @@ -1495,7 +1495,7 @@ def _sync_patch(self): self._axes.add_patch(self._patch) else: if self._patch in self._axes.patches: - self._axes._remove_method(self._patch) + self._patch._remove_method(self._patch) self._patch.set_visible(False) def start_selection(self, event): @@ -1634,7 +1634,7 @@ def _sync_patch(self): self._axes.add_patch(self._patch) else: if self._patch in self._axes.patches: - self._axes._remove_method(self._patch) + self._patch._remove_method(self._patch) self._patch.set_visible(False) def start_selection(self, event, scrubbing=False): From 2e83801f3090d5c0a1836ca859678d357d2e858d Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Sun, 5 Feb 2023 14:20:33 -0500 Subject: [PATCH 09/16] Add pretransform so that selections work correctly with wrapped angles. --- glue/core/roi_pretransforms.py | 22 +++++++++++++++++++ .../scatter/qt/tests/test_data_viewer.py | 4 ++-- glue/viewers/scatter/viewer.py | 4 +++- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/glue/core/roi_pretransforms.py b/glue/core/roi_pretransforms.py index ffbf73a83..ef973b94f 100644 --- a/glue/core/roi_pretransforms.py +++ b/glue/core/roi_pretransforms.py @@ -57,3 +57,25 @@ def __gluestate__(self, context): def __setgluestate__(cls, rec, context): state = context.object(rec['state']) return cls(state['coords'], state['next_transform']) + + +class FullSphereLongitudeTransform(object): + + def __init__(self, next_transform=None): + self._next_transform = next_transform + self._state = {"next_transform": next_transform} + + def __call__(self, x, y): + x = np.mod(x + np.pi, 2 * np.pi) - np.pi + if self._next_transform is not None: + return self._next_transform(x, y) + else: + return x, y + + @classmethod + def __setgluestate__(cls, rec, context): + state = context.object(rec['state']) + return cls(state['next_transform']) + + def __gluestate__(self, context): + return dict(state=context.do(self._state)) diff --git a/glue/viewers/scatter/qt/tests/test_data_viewer.py b/glue/viewers/scatter/qt/tests/test_data_viewer.py index cc25329f7..16c1dd77f 100644 --- a/glue/viewers/scatter/qt/tests/test_data_viewer.py +++ b/glue/viewers/scatter/qt/tests/test_data_viewer.py @@ -12,7 +12,7 @@ from glue.core.message import SubsetUpdateMessage from glue.core import HubListener, Data from glue.core.roi import XRangeROI, RectangularROI, CircularROI -from glue.core.roi_pretransforms import ProjectionMplTransform +from glue.core.roi_pretransforms import ProjectionMplTransform, FullSphereLongitudeTransform from glue.core.subset import RoiSubsetState, AndState from glue import core from glue.core.component_id import ComponentID @@ -965,7 +965,7 @@ def test_apply_roi_fullsphere(self): assert isinstance(state, RoiSubsetState) assert state.pretransform pretrans = state.pretransform - assert isinstance(pretrans, ProjectionMplTransform) + assert isinstance(pretrans, FullSphereLongitudeTransform) assert pretrans._state['projection'] == proj assert_allclose(pretrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) assert_allclose(pretrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) diff --git a/glue/viewers/scatter/viewer.py b/glue/viewers/scatter/viewer.py index d1215c25c..8f25dc11b 100644 --- a/glue/viewers/scatter/viewer.py +++ b/glue/viewers/scatter/viewer.py @@ -1,6 +1,6 @@ from glue.core.subset import roi_to_subset_state from glue.core.util import update_ticks -from glue.core.roi_pretransforms import ProjectionMplTransform, RadianTransform +from glue.core.roi_pretransforms import FullSphereLongitudeTransform, ProjectionMplTransform, RadianTransform from glue.utils import mpl_to_datetime64 from glue.viewers.scatter.compat import update_scatter_viewer_state @@ -170,6 +170,8 @@ def apply_roi(self, roi, override_mode=None): if self.state.using_degrees: coords = ['x'] if self.using_polar() else ['x', 'y'] transform = RadianTransform(coords=coords, next_transform=transform) + if self.state.using_full_sphere: + transform = FullSphereLongitudeTransform(next_transform=transform) subset_state.pretransform = transform self.apply_subset_state(subset_state, override_mode=override_mode) From 7bc7fc04207db2d3c112ec0157adda245a368224 Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Mon, 6 Feb 2023 13:51:03 -0500 Subject: [PATCH 10/16] Update fullsphere test to account for changes. Updated the similar polar test while I was at it to make it a bit more robust. --- .../scatter/qt/tests/test_data_viewer.py | 117 +++++++++++------- 1 file changed, 73 insertions(+), 44 deletions(-) diff --git a/glue/viewers/scatter/qt/tests/test_data_viewer.py b/glue/viewers/scatter/qt/tests/test_data_viewer.py index 16c1dd77f..53b522606 100644 --- a/glue/viewers/scatter/qt/tests/test_data_viewer.py +++ b/glue/viewers/scatter/qt/tests/test_data_viewer.py @@ -12,7 +12,7 @@ from glue.core.message import SubsetUpdateMessage from glue.core import HubListener, Data from glue.core.roi import XRangeROI, RectangularROI, CircularROI -from glue.core.roi_pretransforms import ProjectionMplTransform, FullSphereLongitudeTransform +from glue.core.roi_pretransforms import FullSphereLongitudeTransform, ProjectionMplTransform, RadianTransform from glue.core.subset import RoiSubsetState, AndState from glue import core from glue.core.component_id import ComponentID @@ -914,61 +914,90 @@ def test_limits_log_widget_fullsphere(self): def test_apply_roi_polar(self): self.viewer.add_data(self.data) viewer_state = self.viewer.state - roi = RectangularROI(0, 0.5, 0, 0.5) + roi = RectangularROI(0.5, 1, 0.5, 1) viewer_state.plot_mode = 'polar' viewer_state.full_circle() assert len(self.viewer.layers) == 1 - self.viewer.apply_roi(roi) - - assert len(self.viewer.layers) == 2 - assert len(self.data.subsets) == 1 - - assert_allclose(self.data.subsets[0].to_mask(), [1, 0, 0, 0]) - - state = self.data.subsets[0].subset_state - assert isinstance(state, RoiSubsetState) - assert state.pretransform - pretrans = state.pretransform - assert isinstance(pretrans, ProjectionMplTransform) - assert pretrans._state['projection'] == 'polar' - assert_allclose(pretrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) - assert_allclose(pretrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) - assert pretrans._state['x_scale'] == 'linear' - assert pretrans._state['y_scale'] == 'linear' - self.data.subsets[0].delete() + expected_mask = { + 'degrees': [1, 1, 0, 1], + 'radians': [0, 0, 0, 1] + } - viewer_state.y_log = True - self.viewer.apply_roi(roi) - state = self.data.subsets[0].subset_state - assert state.pretransform - pretrans = state.pretransform - assert isinstance(pretrans, ProjectionMplTransform) - assert pretrans._state['y_scale'] == 'log' + for angle_unit in ['radians', 'degrees']: - def test_apply_roi_fullsphere(self): - self.viewer.add_data(self.data) - viewer_state = self.viewer.state - roi = RectangularROI(0, 0.5, 0, 0.5) - - for proj in fullsphere_projections: - viewer_state.plot_mode = proj - assert len(self.viewer.layers) == 1 + viewer_state.angle_unit = angle_unit self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.data.subsets) == 1 - subset = self.data.subsets[0] - state = subset.subset_state + assert_allclose(self.data.subsets[0].to_mask(), expected_mask[angle_unit]) + + state = self.data.subsets[0].subset_state assert isinstance(state, RoiSubsetState) assert state.pretransform pretrans = state.pretransform - assert isinstance(pretrans, FullSphereLongitudeTransform) - assert pretrans._state['projection'] == proj - assert_allclose(pretrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) - assert_allclose(pretrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) - assert pretrans._state['x_scale'] == 'linear' - assert pretrans._state['y_scale'] == 'linear' - subset.delete() + if angle_unit == 'radians': + assert isinstance(pretrans, ProjectionMplTransform) + projtrans = pretrans + elif angle_unit == 'degrees': + assert isinstance(pretrans, RadianTransform) + projtrans = pretrans._next_transform + assert isinstance(projtrans, ProjectionMplTransform) + assert projtrans._state['projection'] == 'polar' + assert_allclose(projtrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) + assert_allclose(projtrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) + assert projtrans._state['x_scale'] == 'linear' + assert projtrans._state['y_scale'] == 'linear' + self.data.subsets[0].delete() + + viewer_state.y_log = True + self.viewer.apply_roi(roi) + state = self.data.subsets[0].subset_state + assert state.pretransform + pretrans = state.pretransform + if angle_unit == 'radians': + assert isinstance(pretrans, ProjectionMplTransform) + projtrans = pretrans + elif angle_unit == 'degrees': + assert isinstance(pretrans, RadianTransform) + projtrans = pretrans._next_transform + assert isinstance(projtrans, ProjectionMplTransform) + assert projtrans._state['y_scale'] == 'log' + viewer_state.y_log = False + + def test_apply_roi_fullsphere(self): + self.viewer.add_data(self.data) + viewer_state = self.viewer.state + roi = RectangularROI(0, 0.5, 0, 0.5) + + for angle_unit in ['radians', 'degrees']: + viewer_state.angle_unit = angle_unit + for proj in fullsphere_projections: + viewer_state.plot_mode = proj + assert len(self.viewer.layers) == 1 + + self.viewer.apply_roi(roi) + + assert len(self.viewer.layers) == 2 + assert len(self.data.subsets) == 1 + + subset = self.data.subsets[0] + state = subset.subset_state + assert isinstance(state, RoiSubsetState) + assert state.pretransform + pretrans = state.pretransform + assert isinstance(pretrans, FullSphereLongitudeTransform) + projtrans = pretrans._next_transform + if angle_unit == 'degrees': + projtrans = projtrans._next_transform + assert isinstance(projtrans, ProjectionMplTransform) + + assert projtrans._state['projection'] == proj + assert_allclose(projtrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) + assert_allclose(projtrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) + assert projtrans._state['x_scale'] == 'linear' + assert projtrans._state['y_scale'] == 'linear' + subset.delete() From 0f43ee051f68ce8da697d3b9c14e86879537240d Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Wed, 15 Feb 2023 11:03:32 -0500 Subject: [PATCH 11/16] Parametrize polar and fullsphere ROI tests. --- .../scatter/qt/tests/test_data_viewer.py | 130 +++++++++--------- 1 file changed, 62 insertions(+), 68 deletions(-) diff --git a/glue/viewers/scatter/qt/tests/test_data_viewer.py b/glue/viewers/scatter/qt/tests/test_data_viewer.py index 53b522606..8eee885c2 100644 --- a/glue/viewers/scatter/qt/tests/test_data_viewer.py +++ b/glue/viewers/scatter/qt/tests/test_data_viewer.py @@ -911,7 +911,8 @@ def test_limits_log_widget_fullsphere(self): assert ui.valuetext_y_max.isEnabled() assert ui.button_full_circle.isHidden() - def test_apply_roi_polar(self): + @pytest.mark.parametrize('angle_unit,expected_mask', [('radians', [0, 0, 0, 1]), ('degrees', [0, 0, 0, 1])]) + def test_apply_roi_polar(self, angle_unit, expected_mask): self.viewer.add_data(self.data) viewer_state = self.viewer.state roi = RectangularROI(0.5, 1, 0.5, 1) @@ -919,85 +920,78 @@ def test_apply_roi_polar(self): viewer_state.full_circle() assert len(self.viewer.layers) == 1 - expected_mask = { - 'degrees': [1, 1, 0, 1], - 'radians': [0, 0, 0, 1] - } + viewer_state.angle_unit = angle_unit - for angle_unit in ['radians', 'degrees']: + self.viewer.apply_roi(roi) + + assert len(self.viewer.layers) == 2 + assert len(self.data.subsets) == 1 + + assert_allclose(self.data.subsets[0].to_mask(), expected_mask) + + state = self.data.subsets[0].subset_state + assert isinstance(state, RoiSubsetState) + assert state.pretransform + pretrans = state.pretransform + if angle_unit == 'radians': + assert isinstance(pretrans, ProjectionMplTransform) + projtrans = pretrans + elif angle_unit == 'degrees': + assert isinstance(pretrans, RadianTransform) + projtrans = pretrans._next_transform + assert isinstance(projtrans, ProjectionMplTransform) + assert projtrans._state['projection'] == 'polar' + assert_allclose(projtrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) + assert_allclose(projtrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) + assert projtrans._state['x_scale'] == 'linear' + assert projtrans._state['y_scale'] == 'linear' + self.data.subsets[0].delete() + + viewer_state.y_log = True + self.viewer.apply_roi(roi) + state = self.data.subsets[0].subset_state + assert state.pretransform + pretrans = state.pretransform + if angle_unit == 'radians': + assert isinstance(pretrans, ProjectionMplTransform) + projtrans = pretrans + elif angle_unit == 'degrees': + assert isinstance(pretrans, RadianTransform) + projtrans = pretrans._next_transform + assert isinstance(projtrans, ProjectionMplTransform) + assert projtrans._state['y_scale'] == 'log' + viewer_state.y_log = False + + @pytest.mark.parametrize('angle_unit', ['radians', 'degrees']) + def test_apply_roi_fullsphere(self, angle_unit): + self.viewer.add_data(self.data) + viewer_state = self.viewer.state + roi = RectangularROI(0, 0.5, 0, 0.5) - viewer_state.angle_unit = angle_unit + viewer_state.angle_unit = angle_unit + for proj in fullsphere_projections: + viewer_state.plot_mode = proj + assert len(self.viewer.layers) == 1 self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 assert len(self.data.subsets) == 1 - assert_allclose(self.data.subsets[0].to_mask(), expected_mask[angle_unit]) - - state = self.data.subsets[0].subset_state + subset = self.data.subsets[0] + state = subset.subset_state assert isinstance(state, RoiSubsetState) assert state.pretransform pretrans = state.pretransform - if angle_unit == 'radians': - assert isinstance(pretrans, ProjectionMplTransform) - projtrans = pretrans - elif angle_unit == 'degrees': - assert isinstance(pretrans, RadianTransform) - projtrans = pretrans._next_transform - assert isinstance(projtrans, ProjectionMplTransform) - assert projtrans._state['projection'] == 'polar' + assert isinstance(pretrans, FullSphereLongitudeTransform) + projtrans = pretrans._next_transform + if angle_unit == 'degrees': + projtrans = projtrans._next_transform + assert isinstance(projtrans, ProjectionMplTransform) + + assert projtrans._state['projection'] == proj assert_allclose(projtrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) assert_allclose(projtrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) assert projtrans._state['x_scale'] == 'linear' assert projtrans._state['y_scale'] == 'linear' - self.data.subsets[0].delete() - - viewer_state.y_log = True - self.viewer.apply_roi(roi) - state = self.data.subsets[0].subset_state - assert state.pretransform - pretrans = state.pretransform - if angle_unit == 'radians': - assert isinstance(pretrans, ProjectionMplTransform) - projtrans = pretrans - elif angle_unit == 'degrees': - assert isinstance(pretrans, RadianTransform) - projtrans = pretrans._next_transform - assert isinstance(projtrans, ProjectionMplTransform) - assert projtrans._state['y_scale'] == 'log' - viewer_state.y_log = False - - def test_apply_roi_fullsphere(self): - self.viewer.add_data(self.data) - viewer_state = self.viewer.state - roi = RectangularROI(0, 0.5, 0, 0.5) - - for angle_unit in ['radians', 'degrees']: - viewer_state.angle_unit = angle_unit - for proj in fullsphere_projections: - viewer_state.plot_mode = proj - assert len(self.viewer.layers) == 1 - - self.viewer.apply_roi(roi) - - assert len(self.viewer.layers) == 2 - assert len(self.data.subsets) == 1 - - subset = self.data.subsets[0] - state = subset.subset_state - assert isinstance(state, RoiSubsetState) - assert state.pretransform - pretrans = state.pretransform - assert isinstance(pretrans, FullSphereLongitudeTransform) - projtrans = pretrans._next_transform - if angle_unit == 'degrees': - projtrans = projtrans._next_transform - assert isinstance(projtrans, ProjectionMplTransform) - - assert projtrans._state['projection'] == proj - assert_allclose(projtrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) - assert_allclose(projtrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) - assert projtrans._state['x_scale'] == 'linear' - assert projtrans._state['y_scale'] == 'linear' - subset.delete() + subset.delete() From 5e6da43ed63cebfb322142d50f735f3ce3c0c5f6 Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Wed, 15 Feb 2023 19:43:42 -0500 Subject: [PATCH 12/16] Fix mistake in pre-transform order. --- glue/viewers/scatter/viewer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glue/viewers/scatter/viewer.py b/glue/viewers/scatter/viewer.py index 8f25dc11b..9608d0ac2 100644 --- a/glue/viewers/scatter/viewer.py +++ b/glue/viewers/scatter/viewer.py @@ -167,11 +167,11 @@ def apply_roi(self, roi, override_mode=None): self.axes.get_yscale()) # If we're using degrees, we need to staple on the degrees -> radians conversion beforehand + if self.state.using_full_sphere: + transform = FullSphereLongitudeTransform(next_transform=transform) if self.state.using_degrees: coords = ['x'] if self.using_polar() else ['x', 'y'] transform = RadianTransform(coords=coords, next_transform=transform) - if self.state.using_full_sphere: - transform = FullSphereLongitudeTransform(next_transform=transform) subset_state.pretransform = transform self.apply_subset_state(subset_state, override_mode=override_mode) From 8996710aab83a69c9caadf9460e2a7189f05b1f4 Mon Sep 17 00:00:00 2001 From: Jon Carifio Date: Wed, 15 Feb 2023 19:44:06 -0500 Subject: [PATCH 13/16] Update fullsphere test to check masks. --- .../scatter/qt/tests/test_data_viewer.py | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/glue/viewers/scatter/qt/tests/test_data_viewer.py b/glue/viewers/scatter/qt/tests/test_data_viewer.py index 8eee885c2..e41c6a021 100644 --- a/glue/viewers/scatter/qt/tests/test_data_viewer.py +++ b/glue/viewers/scatter/qt/tests/test_data_viewer.py @@ -43,6 +43,8 @@ def setup_method(self, method): y=[3.2, 3.3, 3.4, 3.5], z=['a', 'b', 'c', 'a']) self.data_2d = Data(label='d2', a=[[1, 2], [3, 4]], b=[[5, 6], [7, 8]], x=[[3, 5], [5.4, 1]], y=[[1.2, 4], [7, 8]]) + self.data_fullsphere = Data(label='d3', x=[6.9, -1.1, 1.2, -3.7], + y=[-0.2, 1.0, 0.5, -1.1]) self.app = GlueApplication() self.session = self.app.session @@ -51,6 +53,7 @@ def setup_method(self, method): self.data_collection = self.session.data_collection self.data_collection.append(self.data) self.data_collection.append(self.data_2d) + self.data_collection.append(self.data_fullsphere) self.viewer = self.app.new_data_viewer(ScatterViewer) @@ -962,11 +965,11 @@ def test_apply_roi_polar(self, angle_unit, expected_mask): assert projtrans._state['y_scale'] == 'log' viewer_state.y_log = False - @pytest.mark.parametrize('angle_unit', ['radians', 'degrees']) - def test_apply_roi_fullsphere(self, angle_unit): - self.viewer.add_data(self.data) + @pytest.mark.parametrize('angle_unit,expected_mask', [('radians', [1, 0, 0, 1]), ('degrees', [1, 0, 0, 0])]) + def test_apply_roi_fullsphere(self, angle_unit, expected_mask): + self.viewer.add_data(self.data_fullsphere) viewer_state = self.viewer.state - roi = RectangularROI(0, 0.5, 0, 0.5) + roi = RectangularROI(0.5, 1, 0, 0.5) viewer_state.angle_unit = angle_unit for proj in fullsphere_projections: @@ -976,19 +979,23 @@ def test_apply_roi_fullsphere(self, angle_unit): self.viewer.apply_roi(roi) assert len(self.viewer.layers) == 2 - assert len(self.data.subsets) == 1 + assert len(self.data_fullsphere.subsets) == 1 - subset = self.data.subsets[0] + subset = self.data_fullsphere.subsets[0] state = subset.subset_state assert isinstance(state, RoiSubsetState) + assert state.pretransform pretrans = state.pretransform + if angle_unit == 'degrees': + assert isinstance(pretrans, RadianTransform) + pretrans = pretrans._next_transform assert isinstance(pretrans, FullSphereLongitudeTransform) projtrans = pretrans._next_transform - if angle_unit == 'degrees': - projtrans = projtrans._next_transform assert isinstance(projtrans, ProjectionMplTransform) + assert_allclose(subset.to_mask(), expected_mask) + assert projtrans._state['projection'] == proj assert_allclose(projtrans._state['x_lim'], [viewer_state.x_min, viewer_state.x_max]) assert_allclose(projtrans._state['y_lim'], [viewer_state.y_min, viewer_state.y_max]) From 1fdd5e94f7d102c64e784a227445c02bf911b694 Mon Sep 17 00:00:00 2001 From: Jonathan Carifio Date: Mon, 20 Feb 2023 01:10:16 -0500 Subject: [PATCH 14/16] Use correct expected mask for degrees polar ROI test. --- glue/viewers/scatter/qt/tests/test_data_viewer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glue/viewers/scatter/qt/tests/test_data_viewer.py b/glue/viewers/scatter/qt/tests/test_data_viewer.py index e41c6a021..c0452d098 100644 --- a/glue/viewers/scatter/qt/tests/test_data_viewer.py +++ b/glue/viewers/scatter/qt/tests/test_data_viewer.py @@ -914,7 +914,7 @@ def test_limits_log_widget_fullsphere(self): assert ui.valuetext_y_max.isEnabled() assert ui.button_full_circle.isHidden() - @pytest.mark.parametrize('angle_unit,expected_mask', [('radians', [0, 0, 0, 1]), ('degrees', [0, 0, 0, 1])]) + @pytest.mark.parametrize('angle_unit,expected_mask', [('radians', [0, 0, 0, 1]), ('degrees', [1, 1, 0, 1])]) def test_apply_roi_polar(self, angle_unit, expected_mask): self.viewer.add_data(self.data) viewer_state = self.viewer.state From 2ade3ea21cfaf5253fae24ebf53f1bc6f58bf656 Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Sat, 4 Mar 2023 17:12:05 -0500 Subject: [PATCH 15/16] Don't reset table model if not changing data layer. Account for current order in _update_visible. --- glue/viewers/table/qt/data_viewer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/glue/viewers/table/qt/data_viewer.py b/glue/viewers/table/qt/data_viewer.py index d975fc9d9..674989123 100644 --- a/glue/viewers/table/qt/data_viewer.py +++ b/glue/viewers/table/qt/data_viewer.py @@ -166,7 +166,7 @@ def _update_visible(self): visible = np.zeros(self.order.shape, dtype=bool) for layer_artist in self._table_viewer.layers: if layer_artist.visible: - mask = layer_artist.layer.to_mask() + mask = layer_artist.layer.to_mask()[self.order] if DASK_INSTALLED and isinstance(mask, da.Array): mask = mask.compute() visible |= mask @@ -295,6 +295,13 @@ def _on_layers_changed(self, *args): break else: return + + # If we aren't changing the data layer, we don't need to + # reset the model, just update visible rows + if layer_state.layer == self.data: + self.model._update_visible() + return + self.data = layer_state.layer self.setUpdatesEnabled(False) self.model = DataTableModel(self) From 19fc3f981953fb9a79b337f357e911ea3b36187e Mon Sep 17 00:00:00 2001 From: Carifio24 Date: Wed, 22 Mar 2023 19:09:05 -0400 Subject: [PATCH 16/16] Add test of model/sort-preserving functionality. --- .../table/qt/tests/test_data_viewer.py | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/glue/viewers/table/qt/tests/test_data_viewer.py b/glue/viewers/table/qt/tests/test_data_viewer.py index 2f9b78671..07314fac7 100644 --- a/glue/viewers/table/qt/tests/test_data_viewer.py +++ b/glue/viewers/table/qt/tests/test_data_viewer.py @@ -549,3 +549,65 @@ def test_table_with_dask_column(): colors = ['#aa0000', '#aa0000', '#aa0000', None, None] check_values_and_color(model, data, colors) + + +def test_table_preserve_model_after_selection(): + + # Regression test for a bug that caused table viewers to return + # to default sorting after a new subset was created with the row + # selection tool. This occurred because the model was reset. + + app = get_qapp() # noqa + + d = Data(a=[1, 2, 3, 4, 5], + b=[3.2, 1.2, 4.5, 3.3, 2.2], + c=['e', 'b', 'c', 'a', 'f'], label='test') + + dc = DataCollection([d]) + + gapp = GlueApplication(dc) + + viewer = gapp.new_data_viewer(TableViewer) + viewer.add_data(d) + + model = viewer.ui.table.model() + + model.sort(1, Qt.AscendingOrder) + + data = {'a': [2, 5, 1, 4, 3], + 'b': [1.2, 2.2, 3.2, 3.3, 4.5], + 'c': ['b', 'f', 'e', 'a', 'c']} + colors = [None for _ in range(5)] + + check_values_and_color(model, data, colors) + + # Create a new subset using the row selection tool + + subset_mode = gapp._session.edit_subset_mode + subset_mode.edit_subset = None + viewer.toolbar.actions['table:rowselect'].toggle() + + def press_key(key): + event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, key, Qt.NoModifier) + app.postEvent(viewer.ui.table, event) + app.processEvents() + + # Select the second row + press_key(Qt.Key_Tab) + press_key(Qt.Key_Down) + press_key(Qt.Key_Enter) + + process_events() + + # Check that the table model is still the same, which it + # should be since we aren't changing the viewer Data + + post_model = viewer.ui.table.model() + assert post_model == model + + # Check that the order is still the same + + color = d.subsets[0].style.color + colors[1] = color + + check_values_and_color(post_model, data, colors)