diff --git a/CHANGES.rst b/CHANGES.rst index e2144f3fd8..7d46055b77 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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] diff --git a/docs/specviz/plugins.rst b/docs/specviz/plugins.rst index 10c7c96e80..d54fbace9f 100644 --- a/docs/specviz/plugins.rst +++ b/docs/specviz/plugins.rst @@ -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: diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py index 902002df9e..5644589d8f 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py +++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py @@ -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 @@ -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): @@ -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") @@ -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: @@ -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) diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue index 807b3f6e0f..93de7a32c4 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue +++ b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue @@ -27,24 +27,28 @@