Skip to content

Commit

Permalink
Merge pull request #1378 from rosteen/compound-subset-info
Browse files Browse the repository at this point in the history
Display compound subset info in subset plugin
  • Loading branch information
pllim authored Jun 21, 2022
2 parents 6de3773 + 498b72d commit 3db487f
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 68 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ New Features

- Snackbar queue priority and history access. [#1352]

- Subset Tools plugin now shows information for composite subsets. [#1378]

- Plot options are simplified and include an advanced mode to act on multiple viewers/layers
simultaneously. [#1343]

Expand Down
5 changes: 3 additions & 2 deletions docs/specviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ applied by the selector tool. Note that these are synched with the subset tools
in the app-level toolbar.

If an existing subset is selected, the parameters of the subset will also be
shown. Note that parameters compound regions (e.g., a subset with three disjoint
regions) are not currently displayed.
shown. Note that while parameters for compound regions (e.g., a subset with
three disjoint regions) are displayed, the logical operations joining them
(``OR``, ``AND``, etc.) are not.

.. _gaussian-smooth:

Expand Down
106 changes: 65 additions & 41 deletions jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
from glue.core.message import EditSubsetMessage, SubsetUpdateMessage
from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode,
ReplaceMode, XorMode)
from glue.core.subset import (RoiSubsetState, RangeSubsetState,
OrState, AndState, XorState, InvertState)
from glue.core.subset import RoiSubsetState, RangeSubsetState, CompositeSubsetState
from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu
from traitlets import List, Unicode, Bool, Dict, observe
from traitlets import List, Unicode, Bool, observe

from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import TemplateMixin, SubsetSelect
Expand All @@ -27,9 +26,9 @@ class SubsetPlugin(TemplateMixin):
subset_items = List([]).tag(sync=True)
subset_selected = Unicode("Create new").tag(sync=True)
mode_selected = Unicode('add').tag(sync=True)
show_region_info = Bool(False).tag(sync=True)
subset_classname = Unicode('').tag(sync=True)
subset_definition = Dict({}).tag(sync=True)
show_region_info = Bool(True).tag(sync=True)
subset_types = List([]).tag(sync=True)
subset_definitions = List([]).tag(sync=True)
has_subset_details = Bool(False).tag(sync=True)

def __init__(self, *args, **kwargs):
Expand Down Expand Up @@ -67,7 +66,7 @@ def _sync_selected_from_state(self, *args):

def _on_subset_update(self, *args):
self._sync_selected_from_state(*args)
self._get_region_definition(*args)
self._get_subset_definition(*args)
subset_to_update = self.session.edit_subset_mode.edit_subset[0]
self.subset_select._update_subset(subset_to_update, attribute="type")

Expand All @@ -86,7 +85,7 @@ def _sync_selected_from_ui(self, change):
return

if change['new'] != self.subset_select.default_text:
self._get_region_definition(change['new'])
self._get_subset_definition(change['new'])
self.show_region_info = change['new'] != self.subset_select.default_text
m = [s for s in self.app.data_collection.subset_groups if s.label == change['new']]
if m != self.session.edit_subset_mode.edit_subset:
Expand All @@ -101,40 +100,65 @@ def _mode_selected_changed(self, event={}):
self.session.edit_subset_mode = self.mode_selected
'''

def _get_region_definition(self, *args):
self.subset_definition = {}
def _unpack_nested_subset(self, subset_state):
'''
Navigate through the tree of subset states for composite
subsets made up of multiple regions.
'''
if isinstance(subset_state, CompositeSubsetState):
self._unpack_nested_subset(subset_state.state1)
self._unpack_nested_subset(subset_state.state2)
else:
if subset_state is not None:
self._get_subset_subregion_definition(subset_state)

def _get_subset_subregion_definition(self, subset_state):
"""
Get the type and parameters for a single region in the subset. Note that
the string type and operation (if in a composite subset) need to be stored
separately from the float parameters for display reasons.
"""
subset_type = {}
subset_definition = None

if isinstance(subset_state, RoiSubsetState):
subset_classname = subset_state.roi.__class__.__name__
if subset_classname == "CircularROI":
x, y = subset_state.roi.get_center()
subset_definition = {"X Center": x,
"Y Center": y,
"Radius": subset_state.roi.radius}

elif subset_classname == "RectangularROI":
subset_definition = {}
for att in ("Xmin", "Xmax", "Ymin", "Ymax"):
subset_definition[att] = getattr(subset_state.roi, att.lower())

elif subset_classname == "EllipticalROI":
subset_definition = {"X Center": subset_state.roi.xc,
"Y Center": subset_state.roi.yc,
"X Radius": subset_state.roi.radius_x,
"Y Radius": subset_state.roi.radius_y}
subset_type["Subset type"] = subset_classname

elif isinstance(subset_state, RangeSubsetState):
subset_definition = {"Upper bound": subset_state.hi,
"Lower bound": subset_state.lo}
subset_type["Subset type"] = "Range"

if subset_definition is not None and subset_definition not in self.subset_definitions:
self.subset_definitions = self.subset_definitions + [subset_definition]
self.subset_types = self.subset_types + [subset_type]

def _get_subset_definition(self, *args):
"""
Retrieve the parameters defining the selected subset, for example the
upper and lower bounds for a simple spectral subset.
"""
self.subset_definitions = []
self.subset_types = []
subset_group = [s for s in self.app.data_collection.subset_groups if
s.label == self.subset_selected][0]
subset_state = subset_group.subset_state
subset_class = subset_state.__class__

if subset_class in (OrState, AndState, XorState, InvertState):
self.subset_classname = "Compound Subset"
self.has_subset_details = False
else:
if isinstance(subset_state, RoiSubsetState):
self.subset_classname = subset_state.roi.__class__.__name__
self.has_subset_details = True
if self.subset_classname == "CircularROI":
x, y = subset_state.roi.get_center()
self.subset_definition = {"X Center": x,
"Y Center": y,
"Radius": subset_state.roi.radius}
elif self.subset_classname == "RectangularROI":
temp_def = {}
for att in ("Xmin", "Xmax", "Ymin", "Ymax"):
temp_def[att] = getattr(subset_state.roi, att.lower())
self.subset_definition = temp_def
elif self.subset_classname == "EllipticalROI":
self.subset_definition = {"X Center": subset_state.roi.xc,
"Y Center": subset_state.roi.yc,
"X Radius": subset_state.roi.radius_x,
"Y Radius": subset_state.roi.radius_y}
elif isinstance(subset_state, RangeSubsetState):
self.subset_classname = "Range"
self.subset_definition = {"Upper bound": subset_state.hi,
"Lower bound": subset_state.lo}
self.has_subset_details = True
else:
self.subset_classname = subset_class.__name__
self.has_subset_details = False
self._unpack_nested_subset(subset_state)
30 changes: 17 additions & 13 deletions jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,28 @@

<div v-if="show_region_info">
<j-plugin-section-header>Subset Region Definition</j-plugin-section-header>
<v-row>
<v-col>Subset Type: </v-col>
<v-col>{{ subset_classname }}</v-col>
</v-row>
<div v-if="has_subset_details">
<v-row v-for="(val, key, index) in subset_definition">
<v-col>{{ key }}:</v-col>
<div v-if="subset_definitions.length">
<v-row v-for="(subset_definition, index) in subset_definitions">
<v-col>
<j-number-uncertainty
:value="val"
:defaultDigs="6"
></j-number-uncertainty>
<v-row v-for="(val, key, index2) in subset_types[index]"
class="pt-0 pb-0 mt-0 mb-0">
<v-col>{{ key }}:</v-col>
<v-col>{{ val }}</v-col>
</v-row>
<v-row v-for="(val, key, index2) in subset_definition"
class="pt-0 pb-0 mt-0 mb-0">
<v-col>{{ key }}:</v-col>
<v-col>
<j-number-uncertainty
:value="val"
:defaultDigs="6"
></j-number-uncertainty>
</v-col>
</v-row>
</v-col>
</v-row>
</div>
<div v-else>
<v-row>Could not retrieve subset parameters for this subset type</v-row>
</div>
</div>
</j-tray-plugin>
</template>
32 changes: 20 additions & 12 deletions jdaviz/tests/test_subsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,22 @@ def test_region_from_subset_3d(cubeviz_helper):
assert_allclose(reg.height, 3.5)

assert subset_plugin.subset_selected == "Subset 1"
assert subset_plugin.subset_classname == "RectangularROI"
assert subset_plugin.subset_definition["Xmin"] == 1
assert subset_plugin.subset_definition["Xmax"] == 3.5
assert subset_plugin.subset_definition["Ymin"] == -0.2
assert subset_plugin.subset_definition["Ymax"] == 3.3
assert subset_plugin.subset_types == [{"Subset type": "RectangularROI"}]
assert subset_plugin.subset_definitions[0]["Xmin"] == 1
assert subset_plugin.subset_definitions[0]["Xmax"] == 3.5
assert subset_plugin.subset_definitions[0]["Ymin"] == -0.2
assert subset_plugin.subset_definitions[0]["Ymax"] == 3.3

# Circular Subset
flux_viewer = cubeviz_helper.app.get_viewer("flux-viewer")
# We set the active tool here to trigger a reset of the Subset state to "Create new"
flux_viewer.toolbar.active_tool = flux_viewer.toolbar.tools['bqplot:circle']
cubeviz_helper.app.get_viewer('flux-viewer').apply_roi(CircularROI(xc=3, yc=4, radius=2.4))
assert subset_plugin.subset_selected == "Subset 2"
assert subset_plugin.subset_classname == "CircularROI"
assert subset_plugin.subset_definition["X Center"] == 3
assert subset_plugin.subset_definition["Y Center"] == 4
assert subset_plugin.subset_definition["Radius"] == 2.4
assert subset_plugin.subset_types == [{"Subset type": "CircularROI"}]
assert subset_plugin.subset_definitions[0]["X Center"] == 3
assert subset_plugin.subset_definitions[0]["Y Center"] == 4
assert subset_plugin.subset_definitions[0]["Radius"] == 2.4


def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs):
Expand All @@ -93,9 +93,9 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs):
assert_quantity_allclose(reg.upper, 15.0 * u.Hz)

assert subset_plugin.subset_selected == "Subset 1"
assert subset_plugin.subset_classname == "Range"
assert subset_plugin.subset_definition["Lower bound"] == 5
assert subset_plugin.subset_definition["Upper bound"] == 15.5
assert subset_plugin.subset_types == [{"Subset type": "Range"}]
assert subset_plugin.subset_definitions[0]["Lower bound"] == 5
assert subset_plugin.subset_definitions[0]["Upper bound"] == 15.5


def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs):
Expand Down Expand Up @@ -134,6 +134,7 @@ def test_region_spectral_spatial(cubeviz_helper, spectral_cube_wcs):


def test_disjoint_spectral_subset(cubeviz_helper, spectral_cube_wcs):
subset_plugin = SubsetPlugin(app=cubeviz_helper.app)
data = Data(flux=np.ones((128, 128, 256)), label='Test Flux', coords=spectral_cube_wcs)
cubeviz_helper.app.data_collection.append(data)

Expand All @@ -156,3 +157,10 @@ def test_disjoint_spectral_subset(cubeviz_helper, spectral_cube_wcs):
assert_quantity_allclose(reg[0].upper, 15.0*u.Hz)
assert_quantity_allclose(reg[1].lower, 30.0*u.Hz)
assert_quantity_allclose(reg[1].upper, 35.0*u.Hz)

assert subset_plugin.subset_selected == "Subset 1"
assert subset_plugin.subset_types == [{"Subset type": "Range"}, {"Subset type": "Range"}]
assert subset_plugin.subset_definitions[0]["Lower bound"] == 30
assert subset_plugin.subset_definitions[0]["Upper bound"] == 35
assert subset_plugin.subset_definitions[1]["Lower bound"] == 5
assert subset_plugin.subset_definitions[1]["Upper bound"] == 15.5

0 comments on commit 3db487f

Please sign in to comment.