Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow basic editing for supported shapes and display rotation for rectangle and ellipse #1427

Merged
merged 9 commits into from
Aug 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ New Features
- New popout locations display Jdaviz in a detached popup window (``popout:window``)
or browser tab (``popout:tab``). [#1503]

- Subset Tools plugin now allows basic editing and also shows rotation for certain shapes. [#1427]

Cubeviz
^^^^^^^

Expand Down
12 changes: 9 additions & 3 deletions docs/imviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ This plugin gives access to per-viewer and per-layer plotting options.
:ref:`Display Settings <imviz-display-settings>`
Documentation on various display settings in the Jdaviz viewers.


.. _imviz-subset-plugin:

Subset Tools
Expand All @@ -50,8 +49,15 @@ via the API unless an interactive region is drawn after.

If an existing subset is selected, the parameters of the subset will also be
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.
multiple disjoint regions) are displayed, the logical operations joining them
(``OR``, ``AND``, etc.) are not shown.

For a simple subset, you can edit its parameters by changing the values
in the corresponding editable text fields. Once you have entered the new
value(s), click :guilabel:`Update` to apply. You should see the subset
parameters and shape both update concurrently.

.. note:: Angle is currently uneditable.

.. _imviz-link-control:

Expand Down
126 changes: 99 additions & 27 deletions jdaviz/configs/default/plugins/subset_plugin/subset_plugin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from glue.core.message import EditSubsetMessage, SubsetUpdateMessage
from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode,
ReplaceMode, XorMode)
from glue.core.roi import CircularROI, EllipticalROI, RectangularROI
from glue.core.subset import RoiSubsetState, RangeSubsetState, CompositeSubsetState
from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu
from traitlets import List, Unicode, Bool, observe

from jdaviz.core.events import SnackbarMessage
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import TemplateMixin, SubsetSelect

Expand All @@ -24,13 +26,15 @@ class SubsetPlugin(TemplateMixin):
template_file = __file__, "subset_plugin.vue"
select = List([]).tag(sync=True)
subset_items = List([]).tag(sync=True)
subset_selected = Unicode("Create new").tag(sync=True)
subset_selected = Unicode("Create New").tag(sync=True)
mode_selected = Unicode('add').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)

is_editable = Bool(False).tag(sync=True)

kecnry marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

Expand Down Expand Up @@ -80,6 +84,10 @@ def _sync_available_from_state(self, *args):

@observe('subset_selected')
def _sync_selected_from_ui(self, change):
self.subset_definitions = []
self.subset_types = []
self.is_editable = False

if not hasattr(self, 'subset_select'):
# during initial init, this can trigger before the component is initialized
return
Expand Down Expand Up @@ -108,6 +116,7 @@ def _unpack_nested_subset(self, subset_state):
if isinstance(subset_state, CompositeSubsetState):
self._unpack_nested_subset(subset_state.state1)
self._unpack_nested_subset(subset_state.state2)
self.is_editable = False
else:
if subset_state is not None:
self._get_subset_subregion_definition(subset_state)
Expand All @@ -118,35 +127,56 @@ def _get_subset_subregion_definition(self, subset_state):
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
subset_type = ''
subset_definition = []
self.is_editable = False

if isinstance(subset_state, RoiSubsetState):
subset_classname = subset_state.roi.__class__.__name__
if subset_classname == "CircularROI":
if isinstance(subset_state.roi, CircularROI):
x, y = subset_state.roi.get_center()
subset_definition = {"X Center": x,
"Y Center": y,
"Radius": subset_state.roi.radius}
r = subset_state.roi.radius
subset_definition = [{"name": "X Center", "att": "xc", "value": x, "orig": x},
{"name": "Y Center", "att": "yc", "value": y, "orig": y},
{"name": "Radius", "att": "radius", "value": r, "orig": r}]
self.is_editable = True

elif subset_classname == "RectangularROI":
subset_definition = {}
elif isinstance(subset_state.roi, RectangularROI):
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
real_att = att.lower()
val = getattr(subset_state.roi, real_att)
subset_definition.append(
{"name": att, "att": real_att, "value": val, "orig": val})
theta = subset_state.roi.theta
subset_definition.append(
{"name": "Angle", "att": "theta", "value": theta, "orig": theta})
self.is_editable = True

elif isinstance(subset_state.roi, EllipticalROI):
xc = subset_state.roi.xc
yc = subset_state.roi.yc
rx = subset_state.roi.radius_x
ry = subset_state.roi.radius_y
theta = subset_state.roi.theta
subset_definition = [
{"name": "X Center", "att": "xc", "value": xc, "orig": xc},
{"name": "Y Center", "att": "yc", "value": yc, "orig": yc},
{"name": "X Radius", "att": "radius_x", "value": rx, "orig": rx},
{"name": "Y Radius", "att": "radius_y", "value": ry, "orig": ry},
{"name": "Angle", "att": "theta", "value": theta, "orig": theta}]
self.is_editable = True

subset_type = subset_state.roi.__class__.__name__

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:
lo = subset_state.lo
hi = subset_state.hi
subset_definition = [{"name": "Lower bound", "att": "lo", "value": lo, "orig": lo},
{"name": "Upper bound", "att": "hi", "value": hi, "orig": hi}]
self.is_editable = True
subset_type = "Range"

if len(subset_definition) > 0 and subset_definition not in self.subset_definitions:
# Note: .append() does not work for List traitlet.
self.subset_definitions = self.subset_definitions + [subset_definition]
self.subset_types = self.subset_types + [subset_type]

Expand All @@ -157,8 +187,50 @@ def _get_subset_definition(self, *args):
"""
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

self._unpack_nested_subset(subset_state)
self._unpack_nested_subset(self.subset_select.selected_subset_state)

def vue_update_subset(self, *args):
if not self.is_editable: # no-op
return

subset_state = self.subset_select.selected_subset_state

# Composite region cannot be edited, so just grab first element.
subset_type = self.subset_types[0]
subset_definition = self.subset_definitions[0]

try:
if subset_type == "Range":
sbst_obj = subset_state
else:
sbst_obj = subset_state.roi

for d_att in subset_definition:
setattr(sbst_obj, d_att["att"], d_att["value"])

# Force glue to update the Subset. This is the same call used in
# glue.core.edit_subset_mode.EditSubsetMode.update() but we do not
# want to deal with all the contract stuff tied to the update() method.
self.session.edit_subset_mode._combine_data(subset_state, override_mode=ReplaceMode)
pllim marked this conversation as resolved.
Show resolved Hide resolved
except Exception as err: # pragma: no cover
self.hub.broadcast(SnackbarMessage(
f"Failed to update Subset: {repr(err)}", color='error', sender=self))

# List of JSON-like dict is nice for front-end but a pain to look up,
# so we use these helper functions.

def _get_value_from_subset_definition(self, index, name, desired_key):
subset_definition = self.subset_definitions[index]
value = None
for item in subset_definition:
if item['name'] == name:
value = item[desired_key]
break
return value

def _set_value_in_subset_definition(self, index, name, desired_key, new_value):
for i in range(len(self.subset_definitions[index])):
if self.subset_definitions[index][i]['name'] == name:
self.subset_definitions[index][i]['value'] = new_value
break
37 changes: 26 additions & 11 deletions jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<j-tray-plugin
description='Tools for selecting and interacting with subsets.'
:link="'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#subset-tools'"
:popout_button="popout_button">
:popout_button="false">

<v-row align=center>
<v-col cols=10 justify="left">
Expand All @@ -22,23 +22,38 @@
</v-col>
</v-row>

<!-- Composite region cannot be edited, so just grab first element. -->
<div v-if="is_editable">
<v-row v-for="(item, index2) in subset_definitions[0]"
class="pt-0 pb-0 mt-0 mb-0">
<v-text-field
:label="item.name"
v-model.number="item.value"
type="number"
:disabled="item.name=='Angle'"
></v-text-field>
</v-row>

<v-row justify="end" no-gutters>
<v-btn color="primary" text @click="update_subset">Update</v-btn>
</v-row>
</div>

<div v-if="show_region_info">
<j-plugin-section-header>Subset Region Definition</j-plugin-section-header>
</v-row>
<div v-if="subset_definitions.length">
<v-row v-for="(subset_definition, index) in subset_definitions">
<v-row v-for="(subset_definition, index) in subset_definitions" no-gutters>
<v-col>
<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 class="pt-0 pb-0 mt-0 mb-0">
<v-col>Subset type:</v-col>
<v-col>{{ subset_types[index] }}</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-row v-for="(item, index2) in subset_definition"
class="pt-0 pb-0 mt-0 mb-0" no-gutters>
<v-col>{{ item.name }}:</v-col>
<v-col>
<j-number-uncertainty
:value="val"
:value="item.orig"
:defaultDigs="6"
></j-number-uncertainty>
</v-col>
Expand Down
6 changes: 6 additions & 0 deletions jdaviz/core/template_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,12 @@ def selected_obj(self):
if match is not None:
return match

@property
def selected_subset_state(self):
subset_group = [s for s in self.app.data_collection.subset_groups if
s.label == self.selected][0]
return subset_group.subset_state

def selected_min_max(self, spectrum1d):
if self.selected_obj is None:
return np.nanmin(spectrum1d.spectral_axis), np.nanmax(spectrum1d.spectral_axis)
Expand Down
Loading