diff --git a/CHANGES.rst b/CHANGES.rst index a6c24df5de..296f82eb5a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -29,7 +29,7 @@ API Changes ----------- - Removed API access to plugins that have passed the deprecation period: Links Control, Canvas Rotation, Export Plot. [#3270] -- Renamed the ``Subset Tools`` plugin to ``Subsets`` which now exposes the ``subset``, ``combination_mode``, ``get_center``, and ``set_center`` in the user API. [#3293] +- Subset Tools plugin now exposes the ``subset``, ``combination_mode``, ``get_center``, and ``set_center`` in the user API. [#3293, #3304] - Metadata plugin: ``metadata_plugin.metadata`` API has been deprecated; use ``metadata_plugin.meta`` instead, which will return a Python dictionary instead of diff --git a/docs/cubeviz/import_data.rst b/docs/cubeviz/import_data.rst index 8a5ce40389..27ced24856 100644 --- a/docs/cubeviz/import_data.rst +++ b/docs/cubeviz/import_data.rst @@ -197,7 +197,7 @@ can load the regions into Cubeviz as follows: .. code-block:: python - cubeviz.plugins['Subsets'].import_region("/path/to/data/myregions.reg") + cubeviz.plugins['Subset Tools'].import_region("/path/to/data/myregions.reg") Unsupported regions will be skipped and trigger a warning. Those that failed to load, if any, can be returned as a list of tuples of the @@ -205,7 +205,7 @@ form ``(region, reason)``: .. code-block:: python - bad_regions = cubeviz.plugins['Subsets'].import_region("/path/to/data/myregions.reg", return_bad_regions=True) + bad_regions = cubeviz.plugins['Subset Tools'].import_region("/path/to/data/myregions.reg", return_bad_regions=True) .. note:: Sky regions are currently unsupported in Cubeviz, unlike Imviz. diff --git a/docs/cubeviz/plugins.rst b/docs/cubeviz/plugins.rst index 7f6649ec69..55fa3c0276 100644 --- a/docs/cubeviz/plugins.rst +++ b/docs/cubeviz/plugins.rst @@ -61,12 +61,12 @@ Visualize data quality arrays for spectral cubes from JWST. .. _cubeviz-subset-plugin: -Subsets -======= +Subset Tools +============ .. seealso:: - :ref:`Subsets ` + :ref:`Subset Tools ` Imviz documentation describing the concept of subsets in Jdaviz. diff --git a/docs/imviz/import_data.rst b/docs/imviz/import_data.rst index 1179475c8b..4b59bebf3c 100644 --- a/docs/imviz/import_data.rst +++ b/docs/imviz/import_data.rst @@ -218,7 +218,7 @@ can load the regions into Imviz as follows: .. code-block:: python - imviz.plugins['Subsets'].import_region("/path/to/data/myregions.reg") + imviz.plugins['Subset Tools'].import_region("/path/to/data/myregions.reg") Unsupported regions will be skipped and trigger a warning. Those that failed to load, if any, can be returned as a list of tuples of the @@ -226,7 +226,7 @@ form ``(region, reason)``: .. code-block:: python - bad_regions = imviz.plugins['Subsets'].import_region("/path/to/data/myregions.reg", return_bad_regions=True) + bad_regions = imviz.plugins['Subset Tools'].import_region("/path/to/data/myregions.reg", return_bad_regions=True) You could also define :ref:`regions:shapes` programmatically and load them; e.g.: diff --git a/docs/imviz/plugins.rst b/docs/imviz/plugins.rst index aaab253aed..57801499f6 100644 --- a/docs/imviz/plugins.rst +++ b/docs/imviz/plugins.rst @@ -94,8 +94,8 @@ in the "Filter by bits" dropdown, or from the API we could: .. _imviz-subset-plugin: -Subsets -======= +Subset Tools +============ This plugin allows you to select an existing subset to modify, or to select :guilabel:`Create new` to create a new subset by selecting and using the region selector diff --git a/docs/mosviz/plugins.rst b/docs/mosviz/plugins.rst index 670d85e0f6..c49ba83f67 100644 --- a/docs/mosviz/plugins.rst +++ b/docs/mosviz/plugins.rst @@ -36,12 +36,12 @@ This plugin gives access to per-viewer and per-layer plotting options. .. _mosviz-subset-plugin: -Subsets -======= +Subset Tools +============ .. seealso:: - :ref:`Subsets ` + :ref:`Subset Tools ` Imviz documentation describing the concept of subsets in Jdaviz. .. _imviz_export_markers: diff --git a/docs/rampviz/plugins.rst b/docs/rampviz/plugins.rst index 5c15dfff15..d911f9a5f2 100644 --- a/docs/rampviz/plugins.rst +++ b/docs/rampviz/plugins.rst @@ -30,12 +30,12 @@ To show axes on image viewers, toggle on the "Show axes" option at the bottom of .. _rampviz-subset-plugin: -Subsets -======= +Subset Tools +============ .. seealso:: - :ref:`Subsets ` + :ref:`Subset Tools ` Imviz documentation describing the concept of subsets in Jdaviz. diff --git a/docs/reference/api_plugins.rst b/docs/reference/api_plugins.rst index 2c15d1ea1e..7c6a1679a8 100644 --- a/docs/reference/api_plugins.rst +++ b/docs/reference/api_plugins.rst @@ -33,9 +33,6 @@ Plugins API .. automodapi:: jdaviz.configs.default.plugins.plot_options.plot_options :no-inheritance-diagram: -.. automodapi:: jdaviz.configs.default.plugins.subset_plugin.subset_plugin - :no-inheritance-diagram: - .. automodapi:: jdaviz.configs.default.plugins.subset_tools.subset_tools :no-inheritance-diagram: diff --git a/docs/specviz/plugins.rst b/docs/specviz/plugins.rst index 75653d944e..8770eb7be8 100644 --- a/docs/specviz/plugins.rst +++ b/docs/specviz/plugins.rst @@ -42,12 +42,12 @@ Plot Options .. _specviz-subset-plugin: -Subsets -======= +Subset Tools +============ .. seealso:: - :ref:`Subsets ` + :ref:`Subset Tools ` Imviz documentation describing the concept of subsets in Jdaviz. Subsets in Specviz are strictly spectral subsets and do not support rotation or recentering. diff --git a/docs/specviz2d/plugins.rst b/docs/specviz2d/plugins.rst index 471e448842..d7639dd97e 100644 --- a/docs/specviz2d/plugins.rst +++ b/docs/specviz2d/plugins.rst @@ -30,12 +30,12 @@ Plot Options .. _specviz2d-subset-plugin: -Subsets -======= +Subset Tools +============ .. seealso:: - :ref:`Subsets ` + :ref:`Subset Tools ` Imviz documentation describing the concept of subsets in Jdaviz. Markers diff --git a/jdaviz/components/data_menu_subset_edit.vue b/jdaviz/components/data_menu_subset_edit.vue index cf1b233376..58e6cd5ed2 100644 --- a/jdaviz/components/data_menu_subset_edit.vue +++ b/jdaviz/components/data_menu_subset_edit.vue @@ -25,7 +25,7 @@ - + ` for more details. - - Only the following attributes and methods are available through the - :ref:`public plugin API `: - - * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show` - * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` - * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray` - * ``subset`` (:class:`~jdaviz.core.template_mixin.SubsetSelect`): - Manages subset selection and creation - * ``combination_mode`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`): - Allows selection of combination modes for subsets - * :meth:`get_center` - * :meth:`set_center` - * :meth:`import_region` - """ - template_file = __file__, "subset_plugin.vue" - select = List([]).tag(sync=True) - subset_items = List([]).tag(sync=True) - subset_selected = Any().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) - glue_state_types = List([]).tag(sync=True) - has_subset_details = Bool(False).tag(sync=True) - - subplugins_opened = Any().tag(sync=True) - - multiselect = Bool(False).tag(sync=True) # multiselect only for subset - is_centerable = Bool(False).tag(sync=True) - can_simplify = Bool(False).tag(sync=True) - can_freeze = Bool(False).tag(sync=True) - - icon_replace = Unicode(read_icon(os.path.join(icon_path("glue_replace", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa - icon_or = Unicode(read_icon(os.path.join(icon_path("glue_or", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa - icon_and = Unicode(read_icon(os.path.join(icon_path("glue_and", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa - icon_xor = Unicode(read_icon(os.path.join(icon_path("glue_xor", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa - icon_andnot = Unicode(read_icon(os.path.join(icon_path("glue_andnot", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa - - icon_radialtocheck = Unicode(read_icon(os.path.join(ICON_DIR, 'radialtocheck.svg'), 'svg+xml')).tag(sync=True) # noqa - icon_checktoradial = Unicode(read_icon(os.path.join(ICON_DIR, 'checktoradial.svg'), 'svg+xml')).tag(sync=True) # noqa - - combination_items = List([]).tag(sync=True) - combination_selected = Any().tag(sync=True) - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # description displayed under plugin title in tray - config = self.app.config - if config in ['cubeviz', 'specviz2d', 'mosviz', 'rampviz']: - self._plugin_description = 'Select and interact with spectral/spatial subsets.' - elif config == 'imviz': - self._plugin_description = 'Select and interact with spatial subsets.' - elif config == 'specviz': - self._plugin_description = 'Select and interact with spectral subsets.' - - self.components = { - 'g-subset-mode': SelectionModeMenu(session=self.session) - } - - self.session.hub.subscribe(self, EditSubsetMessage, - handler=self._sync_selected_from_state) - self.session.hub.subscribe(self, SubsetUpdateMessage, - handler=self._on_subset_update) - self.session.hub.subscribe(self, GlobalDisplayUnitChanged, - handler=self._on_display_unit_changed) - self.session.hub.subscribe(self, LinkUpdatedMessage, - handler=self._on_link_update) - - self.subset = SubsetSelect(self, - 'subset_items', - 'subset_selected', - multiselect='multiselect', - default_text="Create New") - self.subset_states = [] - self.spectral_display_unit = None - - align_by = getattr(self.app, '_align_by', None) - self.display_sky_coordinates = (align_by == 'wcs' and not self.multiselect) - - self.combination_mode = SelectPluginComponent(self, - items='combination_items', - selected='combination_selected', - manual_options=COMBO_OPTIONS) - - @property - def user_api(self): - expose = ['subset', 'combination_mode', 'get_center', 'set_center', 'import_region'] - return PluginUserApi(self, expose) - - def _on_link_update(self, *args): - """When linking is changed pixels<>wcs, change display units of the - subset plugin from pixel (for pixel linking) to sky (for WCS linking). - If there is an active selection in the subset plugin, push this change - to the UI upon link change by calling _get_subset_definition, which - will re-determine how to display subset information.""" - - align_by = getattr(self.app, '_align_by', None) - self.display_sky_coordinates = (align_by == 'wcs') - - if self.subset_selected != self.subset.default_text: - self._get_subset_definition(*args) - - def _sync_selected_from_state(self, *args): - if not hasattr(self, 'subset') or self.multiselect: - # during initial init, this can trigger before the component is initialized - return - if self.session.edit_subset_mode.edit_subset == []: - if self.subset_selected != self.subset.default_text: - self.subset_selected = self.subset.default_text - self.show_region_info = False - else: - new_label = self.session.edit_subset_mode.edit_subset[0].label - if new_label != self.subset_selected: - if new_label not in [s['label'] for s in self.subset_items]: - self._sync_available_from_state() - self.subset_selected = self.session.edit_subset_mode.edit_subset[0].label - self.show_region_info = True - self._update_combination_mode() - - def _on_subset_update(self, *args): - self._sync_selected_from_state(*args) - if 'Create New' in self.subset_selected: - return - subsets_avail = [sg.label for sg in self.app.data_collection.subset_groups] - if self.subset_selected not in subsets_avail: - # subset selection should re-default after processing the deleted subset, - # for now we can safely ignore - return - self._get_subset_definition(*args) - subset_to_update = self.session.edit_subset_mode.edit_subset[0] - self.subset._update_subset(subset_to_update, attribute="type") - - def _sync_available_from_state(self, *args): - if not hasattr(self, 'subset'): - # during initial init, this can trigger before the component is initialized - return - self.subset_items = [{'label': self.subset.default_text}] + [ - self.subset._subset_to_dict(subset) for subset in - self.data_collection.subset_groups] - - @observe('subset_selected') - def _sync_selected_from_ui(self, change): - self.subset_definitions = [] - self.subset_types = [] - self.glue_state_types = [] - self.is_centerable = False - - if not hasattr(self, 'subset'): - # during initial init, this can trigger before the component is initialized - return - if change['new'] != self.subset.default_text: - self._get_subset_definition(change['new']) - self.show_region_info = change['new'] != self.subset.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: - self.session.edit_subset_mode.edit_subset = m - - def _unpack_get_subsets_for_ui(self): - """ - Convert what app.get_subsets returns into something the UI of this plugin - can display. - """ - if self.multiselect: - self.is_centerable = True - return - - include_sky_region = bool(self.display_sky_coordinates) - subset_information = self.app.get_subsets(self.subset_selected, - simplify_spectral=False, - use_display_units=True, - include_sky_region=include_sky_region) - - _around_decimals = 6 # Avoid 30 degrees from coming back as 29.999999999999996 - if not subset_information: - return - if ((len(subset_information) == 1) and - (isinstance(subset_information[0]["subset_state"], RangeSubsetState) or - (isinstance(subset_information[0]["subset_state"], RoiSubsetState) and - isinstance(subset_information[0]["subset_state"].roi, - (CircularROI, RectangularROI, EllipticalROI))))): - self.is_centerable = True - else: - self.is_centerable = False - - for spec in subset_information: - - subset_definition = [] - subset_type = '' - subset_state = spec["subset_state"] - glue_state = spec["glue_state"] - - if isinstance(subset_state, RoiSubsetState): - subset_definition.append({ - "name": "Parent", "att": "parent", - "value": subset_state.xatt.parent.label, - "orig": subset_state.xatt.parent.label}) - - sky_region = spec['sky_region'] - if self.display_sky_coordinates and (sky_region is not None): - subset_definition += utils._sky_region_to_subset_def(sky_region) - - else: - if isinstance(subset_state.roi, CircularROI): - x, y = subset_state.roi.center() - r = subset_state.roi.radius - subset_definition += [ - {"name": "X Center (pixels)", "att": "xc", - "value": x, "orig": x}, - {"name": "Y Center (pixels)", "att": "yc", - "value": y, "orig": y}, - {"name": "Radius (pixels)", "att": "radius", - "value": r, "orig": r}] - - elif isinstance(subset_state.roi, RectangularROI): - for att in ("Xmin", "Xmax", "Ymin", "Ymax"): - real_att = att.lower() - val = getattr(subset_state.roi, real_att) - subset_definition.append( - {"name": att + " (pixels)", "att": real_att, - "value": val, "orig": val}) - - theta = np.around(np.degrees(subset_state.roi.theta), - decimals=_around_decimals) - subset_definition.append({"name": "Angle", "att": "theta", - "value": theta, "orig": theta}) - - elif isinstance(subset_state.roi, EllipticalROI): - xc, yc = subset_state.roi.center() - rx = subset_state.roi.radius_x - ry = subset_state.roi.radius_y - theta = np.around(np.degrees(subset_state.roi.theta), - decimals=_around_decimals) - - subset_definition += [ - {"name": "X Center (pixels)", "att": "xc", - "value": xc, "orig": xc}, - {"name": "Y Center (pixels)", "att": "yc", - "value": yc, "orig": yc}, - {"name": "X Radius (pixels)", "att": "radius_x", - "value": rx, "orig": rx}, - {"name": "Y Radius (pixels)", "att": "radius_y", - "value": ry, "orig": ry}, - {"name": "Angle", "att": "theta", - "value": theta, "orig": theta}] - - elif isinstance(subset_state.roi, CircularAnnulusROI): - xc, yc = subset_state.roi.center() - inner_r = subset_state.roi.inner_radius - outer_r = subset_state.roi.outer_radius - subset_definition += [{"name": "X Center (pixels)", - "att": "xc", "value": xc, "orig": xc}, - {"name": "Y Center (pixels)", - "att": "yc", "value": yc, "orig": yc}, - {"name": "Inner Radius (pixels)", - "att": "inner_radius", - "value": inner_r, "orig": inner_r}, - {"name": "Outer Radius (pixels)", - "att": "outer_radius", - "value": outer_r, "orig": outer_r}] - - else: # pragma: no cover - raise NotImplementedError(f"Unable to translate {subset_state.roi.__class__.__name__}") # noqa: E501 - - subset_type = subset_state.roi.__class__.__name__ - - elif isinstance(subset_state, RangeSubsetState): - region = spec['region'] - if isinstance(region, Time): - lo = region.min() - hi = region.max() - subset_definition = [{"name": "Lower bound", "att": "lo", - "value": lo.value, "orig": lo.value}, - {"name": "Upper bound", "att": "hi", - "value": hi.value, "orig": hi.value}] - else: - lo = region.lower - hi = region.upper - subset_definition = [{"name": "Lower bound", "att": "lo", "value": lo.value, - "orig": lo.value, "unit": str(lo.unit)}, - {"name": "Upper bound", "att": "hi", "value": hi.value, - "orig": hi.value, "unit": str(hi.unit)}] - subset_type = "Range" - - elif isinstance(subset_state, MultiMaskSubsetState): - total_masked = subset_state.total_masked_first_data() - subset_definition = [{"name": "Masked values", "att": "masked", - "value": total_masked, - "orig": total_masked}] - subset_type = "Mask" - if len(subset_definition) > 0: - # Note: .append() does not work for List traitlet. - self.subset_definitions = self.subset_definitions + [subset_definition] - self.subset_types = self.subset_types + [subset_type] - self.glue_state_types = self.glue_state_types + [glue_state] - self.subset_states = self.subset_states + [subset_state] - - simplifiable_states = set(['AndState', 'XorState', 'AndNotState']) - # Check if the subset has more than one subregion, is a range subset - # type, and uses one of the states that can be simplified. Mask subset - # types cannot be simplified so subsets contained that are skipped. - if 'Mask' in self.subset_types: - self.can_simplify = False - elif ((len(self.subset_states) > 1) and isinstance(self.subset_states[0], RangeSubsetState) - and ((len(simplifiable_states - set(self.glue_state_types)) < 3) - or self.app.is_there_overlap_spectral_subset(self.subset_selected))): - self.can_simplify = True - else: - self.can_simplify = False - - 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 = [] - self.glue_state_types = [] - self.subset_states = [] - - self._unpack_get_subsets_for_ui() - - def vue_freeze_subset(self, *args): - sgs = {sg.label: sg for sg in self.app.data_collection.subset_groups} - sg = sgs.get(self.subset_selected) - - masks = {} - for data in self.app.data_collection: - masks[data.uuid] = sg.subset_state.to_mask(data) - - sg.subset_state = MultiMaskSubsetState(masks) - - def vue_simplify_subset(self, *args): - if self.multiselect: - self.hub.broadcast(SnackbarMessage("Cannot simplify spectral subset " - "when multiselect is active", color='warning', - sender=self)) - return - if len(self.subset_states) < 2: - self.hub.broadcast(SnackbarMessage("Cannot simplify spectral subset " - "of length less than 2", color='warning', - sender=self)) - return - att = self.subset_states[0].att - self.app.simplify_spectral_subset(subset_name=self.subset_selected, att=att, - overwrite=True) - - def _on_display_unit_changed(self, msg): - # We only care about the spectral units, since flux units don't affect spectral subsets - if msg.axis == "spectral": - self.spectral_display_unit = msg.unit - if self.subset_selected != self.subset.default_text: - self._get_subset_definition(self.subset_selected) - - def vue_update_subset(self, *args): - - if self.multiselect: - self.hub.broadcast(SnackbarMessage("Cannot update subset " - "when multiselect is active", color='warning', - sender=self)) - return - - status, reason = self._check_input() - if not status: - self.hub.broadcast(SnackbarMessage(reason, color='error', sender=self)) - return - - for index, sub in enumerate(self.subset_definitions): - if len(self.subset_states) <= index: - return - sub_states = self.subset_states[index] - - # we need to push updates to subset in pixels. to do this when wcs - # linked, convert the updated subset parameters from sky to pix - wcs = None - - if self.display_sky_coordinates: - wcs = self.app._get_wcs_from_subset(sub_states) - - if wcs is not None: - # convert newly entered sky coords to pixel - updated_skyreg = utils._subset_def_to_region(self.subset_types[index], sub) # noqa - updated_pixreg_attrs = utils._get_pixregion_params_in_dict(updated_skyreg.to_pixel(wcs)) # noqa - # convert previous entered sky coords to pixel - orig_skyreg = utils._subset_def_to_region(self.subset_types[index], sub, val='orig') # noqa - orig_pixreg_attrs = utils._get_pixregion_params_in_dict(orig_skyreg.to_pixel(wcs)) # noqa - - for d_att in sub: - if d_att["att"] == 'parent': # Read-only - continue - if self.display_sky_coordinates and (wcs is not None): - d_att["value"] = updated_pixreg_attrs[d_att["att"]] - d_att["orig"] = orig_pixreg_attrs[d_att["att"]] - - if (d_att["att"] == 'theta') and (self.display_sky_coordinates is False): - # Humans use degrees but glue uses radians - # We've already enforced this in wcs linking in _get_pixregion_params_in_dict - d_val = np.radians(d_att["value"]) - else: - d_val = float(d_att["value"]) - - # Convert from display unit to original unit if necessary - if self.subset_types[index] == "Range": - if self.spectral_display_unit is not None: - x_att = sub_states.att - # since this is a spectrum range subset, we can get the native units - # from the current reference data in the spectrum viewer - sv = self.spectrum_viewer - base_units = sv.state.reference_data.get_component(x_att).units - if self.spectral_display_unit != base_units: - d_val = d_val*u.Unit(self.spectral_display_unit) - d_val = d_val.to(u.Unit(base_units)) - d_val = d_val.value - - if float(d_att["orig"]) != d_val: - if self.subset_types[index] == "Range": - setattr(sub_states, d_att["att"], d_val) - else: - setattr(sub_states.roi, d_att["att"], d_val) - - self._push_update_to_ui() - - def _push_update_to_ui(self, subset_name=None): - """ - Forces the UI to update how it represents the subset. - - Parameters - ---------- - subset_name : str - The name of the subset that is being updated. - - """ - if not subset_name: - subset_name = self.subset_selected - try: - dc = self.data_collection - subsets = dc.subset_groups - subset_to_update = subsets[[x.label for x in subsets].index(subset_name)] - self.session.edit_subset_mode.edit_subset = [subset_to_update] - self.session.edit_subset_mode._combine_data(subset_to_update.subset_state, - override_mode=ReplaceMode) - except Exception as err: # pragma: no cover - self.hub.broadcast(SnackbarMessage( - f"Failed to update Subset: {repr(err)}", color='error', sender=self)) - - def _check_input(self): - status = True - reason = "" - for index, sub in enumerate(self.subset_definitions): - lo = hi = xmin = xmax = ymin = ymax = None - inner_radius = outer_radius = None - for d_att in sub: - if d_att["att"] == "lo": - lo = d_att["value"] - elif d_att["att"] == "hi": - hi = d_att["value"] - elif d_att["att"] == "radius" and d_att["value"] <= 0: - status = False - reason = "Failed to update Subset: radius must be a positive scalar" - break - elif d_att["att"] == "xmin": - xmin = d_att["value"] - elif d_att["att"] == "xmax": - xmax = d_att["value"] - elif d_att["att"] == "ymin": - ymin = d_att["value"] - elif d_att["att"] == "ymax": - ymax = d_att["value"] - elif d_att["att"] == "outer_radius": - outer_radius = d_att["value"] - elif d_att["att"] == "inner_radius": - inner_radius = d_att["value"] - - if lo and hi and hi <= lo: - status = False - reason = "Failed to update Subset: lower bound must be less than upper bound" - break - elif xmin and xmax and ymin and ymax and (xmax - xmin <= 0 or ymax - ymin <= 0): - status = False - reason = "Failed to update Subset: width and length must be positive scalars" - break - elif inner_radius and outer_radius and inner_radius >= outer_radius: - status = False - reason = "Failed to update Subset: inner radius must be less than outer radius" - break - - return status, reason - - def vue_recenter_subset(self, *args): - # Composite region cannot be centered. This only works for Imviz. - if not self.is_centerable or self.config != 'imviz': # no-op - raise NotImplementedError( - f'Cannot recenter: is_centerable={self.is_centerable}, config={self.config}') - - from astropy.wcs.utils import pixel_to_pixel - from photutils.aperture import ApertureStats - from jdaviz.core.region_translators import regions2aperture, _get_region_from_spatial_subset - - def _do_recentering(subset, subset_state): - try: - reg = _get_region_from_spatial_subset(self, subset_state) - aperture = regions2aperture(reg) - data = self.dataset.selected_dc_item - comp = data.get_component(data.main_components[0]) - comp_data = comp.data - phot_aperstats = ApertureStats(comp_data, aperture, wcs=data.coords) - - # Sky region from WCS linking, need to convert centroid back to pixels. - if hasattr(reg, "to_pixel"): - # Centroid was calculated in selected data. - # However, Subset is always defined w.r.t. its parent, - # so we need to convert back. - x, y = pixel_to_pixel( - data.coords, - subset_state.xatt.parent.coords, - phot_aperstats.xcentroid, - phot_aperstats.ycentroid) - else: - x = phot_aperstats.xcentroid - y = phot_aperstats.ycentroid - if not np.all(np.isfinite((x, y))): - raise ValueError(f'Invalid centroid ({x}, {y})') - except Exception as err: - self.set_center(self.get_center(subset_name=subset), subset_name=subset, - update=False) - self.hub.broadcast(SnackbarMessage( - f"Failed to calculate centroid: {repr(err)}", color='error', sender=self)) - else: - self.set_center((x, y), subset_name=subset, update=True) - - if not self.multiselect: - _do_recentering(self.subset_selected, self.subset.selected_subset_state) - else: - for sub, subset_state in zip(self.subset_selected, - self.subset.selected_subset_state): - if (sub != self.subset.default_text and - not isinstance(subset_state, CompositeSubsetState)): - self.is_centerable = True - _do_recentering(sub, subset_state) - elif (sub != self.subset.default_text and - isinstance(subset_state, CompositeSubsetState)): - self.hub.broadcast(SnackbarMessage(f"Unable to recenter " - f"composite subset {sub}", - color='error', sender=self)) - - def _get_subset_state(self, subset_name=None): - if self.multiselect and not subset_name: - raise ValueError("Please include subset_name in when in multiselect mode") - - if subset_name is not None: - return self.subset._get_subset_state(subset_name) - # guaranteed to only return a single entry because of check above - return self.subset.selected_subset_state - - def get_center(self, subset_name=None): - """Return the center of the Subset. - This may or may not be the centroid obtain from data. - - Parameters - ---------- - subset_name : str - The name of the subset that is being updated. - - Returns - ------- - cen : number, tuple of numbers, or `None` - The center of the Subset in ``x`` or ``(x, y)``, - depending on the Subset type, if applicable. - If Subset is not centerable, this returns `None`. - - """ - # Composite region cannot be centered. - if not self.is_centerable: # no-op - return - - subset_state = self._get_subset_state(subset_name) - return subset_state.center() - - def set_center(self, new_cen, subset_name=None, update=False): - """Set the desired center for the selected Subset, if applicable. - If Subset is not centerable, nothing is done. - - Parameters - ---------- - new_cen : number or tuple of numbers - The new center defined either as ``x`` or ``(x, y)``, - depending on the Subset type. - subset_name : str - The name of the subset that is being updated. - update : bool - If `True`, the Subset is also moved to the new center. - Otherwise, only the relevant editable fields are updated but the - Subset is not moved. - - Raises - ------ - NotImplementedError - Subset type is not supported. - - """ - # Composite region cannot be centered, so just grab first element. - if not self.is_centerable: # no-op - return - - subset_state = self._get_subset_state(subset_name) - - if isinstance(subset_state, RoiSubsetState): - x, y = new_cen - # x and y are arrays so this converts them back to floats - x = float(x) - y = float(y) - sbst_obj = subset_state.roi - if isinstance(sbst_obj, (CircularROI, CircularAnnulusROI, EllipticalROI)): - sbst_obj.move_to(x, y) - elif isinstance(sbst_obj, RectangularROI): - sbst_obj.move_to(x, y) - else: # pragma: no cover - raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported') - - elif isinstance(subset_state, RangeSubsetState): - subset_state.move_to(new_cen) - - else: # pragma: no cover - raise NotImplementedError( - f'Getting center of {subset_state.__class__} is not supported') - - if update: - self._push_update_to_ui(subset_name=subset_name) - else: - # Force UI to update on browser without changing the subset. - tmp = self.subset_definitions - self.subset_definitions = [] - self.subset_definitions = tmp - - # 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 - - def import_region(self, region, combination_mode=None, max_num_regions=None, - refdata_label=None, return_bad_regions=False, **kwargs): - """ - Method for creating subsets from regions or region files. - - Parameters - ---------- - region : region, list of region objects, or str - A region object can be one of the following: - - * Astropy ``regions`` object - * ``photutils`` apertures (limited support until ``photutils`` - fully supports ``regions``) - * specutils ``SpectralRegion`` object - - A string which represents a ``regions`` or ``SpectralRegion`` file. - If given as a list, it can only contain spectral or non-spectral regions, not both. - - combination_mode : list, str, or `None` - The way that regions are created or combined. If a list, then it must be the - same length as regions. If `None`, then it will follow the default glue - functionality for subset creation. - - max_num_regions : int or `None` - Maximum number of regions to load, starting from top of the list. - Default is to load everything. If you are providing a large file/list - input for ``region``, it is recommended - - refdata_label : str or `None` - **This is only applicable to non-spectral regions.** - Label of data to use for sky-to-pixel conversion for a region, or - mask creation. Data must already be loaded into Jdaviz. - If `None`, defaults to the reference data in the default viewer. - Choice of this data is particularly important when sky - region is involved. - - return_bad_regions : bool - If `True`, return the regions that failed to load (see ``bad_regions``); - This is useful for debugging. If `False`, do not return anything (`None`). - - Returns - ------- - bad_regions : list of (obj, str) or `None` - If requested (see ``return_bad_regions`` option), return a - list of ``(region, reason)`` tuples for region objects that failed to load. - If all the regions loaded successfully, this list will be empty. - If not requested, return `None`. - - """ - if isinstance(region, str): - if os.path.exists(region): - from regions import Regions - region_format = kwargs.pop('region_format', None) - try: - raw_regs = Regions.read(region, format=region_format) - except Exception: # nosec - raw_regs = SpectralRegion.read(region) - - return self._load_regions(raw_regs, combination_mode, max_num_regions, - refdata_label, return_bad_regions, **kwargs) - else: - return self._load_regions(region, combination_mode, max_num_regions, refdata_label, - return_bad_regions, **kwargs) - - def _load_regions(self, regions, combination_mode=None, max_num_regions=None, - refdata_label=None, return_bad_regions=False, **kwargs): - """Load given region(s) into the viewer. - WCS-to-pixel translation and mask creation, if needed, is relative - to the image defined by ``refdata_label``. Meanwhile, the rest of - the Subset operations are based on reference image as defined by Glue. - - .. note:: Loading too many regions will affect performance. - - A valid region can be loaded into one of the following categories: - - * An interactive Subset, as if it was drawn by hand. This is - always done for supported shapes. Its label will be - ``'Subset N'``, where ``N`` is an integer. - * A masked Subset that will display on the image but cannot be - modified once loaded. This is done if the shape cannot be - made interactive. Its label will be ``'MaskedSubset N'``, - where ``N`` is an integer. - - Parameters - ---------- - regions : list of obj - A list of region objects. A region object can be one of the following: - - * Astropy ``regions`` object - * ``photutils`` apertures (limited support until ``photutils`` - fully supports ``regions``) - * specutils ``SpectralRegion`` object - - combination_mode : list, str, or `None` - The way that regions are created or combined. If a list, then it must be the - same length as regions. Each element describes how the corresponding region - in regions will be applied. If `None`, then it will follow the default glue - functionality for subset creation. Options are ['new', 'replace', 'or', 'and', - 'xor', 'andnot'] - - max_num_regions : int or `None` - Maximum number of regions to load, starting from top of the list. - Default is to load everything. - - refdata_label : str or `None` - Label of data to use for sky-to-pixel conversion for a region, or - mask creation. Data must already be loaded into Jdaviz. - If `None`, defaults to the reference data in the default viewer. - Choice of this data is particularly important when sky - region is involved. - - return_bad_regions : bool - If `True`, return the regions that failed to load (see ``bad_regions``); - This is useful for debugging. If `False`, do not return anything (`None`). - - kwargs : dict - Extra keywords to be passed into the region's ``to_mask`` method. - **This is ignored if the region can be made interactive.** - - Returns - ------- - bad_regions : list of (obj, str) or `None` - If requested (see ``return_bad_regions`` option), return a - list of ``(region, reason)`` tuples for region objects that failed to load. - If all the regions loaded successfully, this list will be empty. - If not requested, return `None`. - - """ - if len(self.app.data_collection) == 0: - raise ValueError('Cannot load regions without data.') - - if not isinstance(regions, (list, tuple, Regions, SpectralRegion)): - regions = [regions] - - n_loaded = 0 - bad_regions = [] - - # To keep track of masked subsets. - msg_prefix = 'MaskedSubset' - msg_count = _next_subset_num(msg_prefix, self.app.data_collection.subset_groups) - - viewer_parameter = kwargs.pop('viewer', None) - viewer_name = viewer_parameter or list(self.app._jdaviz_helper.viewers.keys())[0] - viewer = self.app.get_viewer(viewer_name) - # Subset is global but reference data is viewer-dependent. - if refdata_label is None: - data = viewer.state.reference_data - else: - data = self.app.data_collection[refdata_label] - - has_wcs = data_has_valid_wcs(data, ndim=2) - - combo_mode_is_list = isinstance(combination_mode, list) - if combo_mode_is_list and len(combination_mode) != (len(regions)): - raise ValueError("list of mode must be size of regions") - elif combo_mode_is_list: - unknown_options = list(set(combination_mode) - set(COMBO_OPTIONS)) - if len(unknown_options) > 0: - raise ValueError(f"{unknown_options} not one of {COMBO_OPTIONS}") - - previous_mode = self.app.session.edit_subset_mode.mode - - for index, region in enumerate(regions): - # Set combination mode for how region will be applied to current subset - # or created as a new subset - if combo_mode_is_list: - combo_mode = combination_mode[index] - else: - combo_mode = combination_mode - - if combo_mode == 'new': - # Remove selection of subset so that new one will be created - self.app.session.edit_subset_mode.edit_subset = None # No overwrite next iteration - self.app.session.edit_subset_mode.mode = SUBSET_MODES_PRETTY['new'] - elif combo_mode: - self.combination_mode.selected = combo_mode - - if (isinstance(region, (SkyCircularAperture, SkyEllipticalAperture, - SkyRectangularAperture, SkyCircularAnnulus, - CircleSkyRegion, EllipseSkyRegion, - RectangleSkyRegion, CircleAnnulusSkyRegion)) - and not has_wcs): - bad_regions.append((region, 'Sky region provided but data has no valid WCS')) - continue - - if (isinstance(region, (CircularAperture, EllipticalAperture, - RectangularAperture, CircularAnnulus, - CirclePixelRegion, EllipsePixelRegion, - RectanglePixelRegion, CircleAnnulusPixelRegion)) - and (hasattr(self.app, '_link_type') and self.app._link_type == "wcs")): - bad_regions.append((region, 'Pixel region provided by data is linked by WCS')) - continue - - # photutils: Convert to region shape first - if isinstance(region, (CircularAperture, SkyCircularAperture, - EllipticalAperture, SkyEllipticalAperture, - RectangularAperture, SkyRectangularAperture, - CircularAnnulus, SkyCircularAnnulus)): - region = aperture2regions(region) - - # region: Convert to ROI. - # NOTE: Out-of-bounds ROI will succeed; this is native glue behavior. - if isinstance(region, (CirclePixelRegion, CircleSkyRegion, - EllipsePixelRegion, EllipseSkyRegion, - RectanglePixelRegion, RectangleSkyRegion, - CircleAnnulusPixelRegion, CircleAnnulusSkyRegion)): - state = regions2roi(region, wcs=data.coords) - viewer.apply_roi(state) - - elif isinstance(region, (CircularROI, CircularAnnulusROI, - EllipticalROI, RectangularROI)): - viewer.apply_roi(region) - - elif isinstance(region, SpectralRegion): - # Use viewer_name if provided in kwarg, otherwise use - # default spectrum viewer name - viewer_name = (viewer_parameter or - self.app._jdaviz_helper._default_spectrum_viewer_reference_name) - range_viewer = self.app.get_viewer(viewer_name) - - s = RangeSubsetState(lo=region.lower.value, hi=region.upper.value, - att=range_viewer.state.x_att) - range_viewer.apply_subset_state(s) - - # Last resort: Masked Subset that is static (if data is not a cube) - elif data.ndim == 2: - im = None - if hasattr(region, 'to_pixel'): # Sky region: Convert to pixel region - if not has_wcs: - bad_region.append((region, 'Sky region provided but data has no valid WCS')) # noqa - continue - region = region.to_pixel(data.coords) - - if hasattr(region, 'to_mask'): - try: - mask = region.to_mask(**kwargs) - im = mask.to_image(data.shape) # Can be None - except Exception as e: # pragma: no cover - bad_regions.append((region, f'Failed to load: {repr(e)}')) - continue - - # Boolean mask as input is supported but not advertised. - elif (isinstance(region, np.ndarray) and region.shape == data.shape - and region.dtype == np.bool_): - im = region - - if im is None: - bad_regions.append((region, 'Mask creation failed')) - continue - - # NOTE: Region creation info is thus lost. - try: - subset_label = f'{msg_prefix} {msg_count}' - state = MaskSubsetState(im, data.pixel_component_ids) - self.app.data_collection.new_subset_group(subset_label, state) - msg_count += 1 - except Exception as e: # pragma: no cover - bad_regions.append((region, f'Failed to load: {repr(e)}')) - continue - else: - bad_regions.append((region, 'Mask creation failed')) - continue - n_loaded += 1 - if max_num_regions is not None and n_loaded >= max_num_regions: - break - - # Revert edit mode to before the import_region call - self.app.session.edit_subset_mode.mode = previous_mode - - n_reg_in = len(regions) - n_reg_bad = len(bad_regions) - if n_loaded == 0: - snack_color = "error" - elif n_reg_bad > 0: - snack_color = "warning" - else: - snack_color = "success" - self.app.hub.broadcast(SnackbarMessage( - f"Loaded {n_loaded}/{n_reg_in} regions, max_num_regions={max_num_regions}, " - f"bad={n_reg_bad}", color=snack_color, timeout=8000, sender=self.app)) - - if return_bad_regions: - return bad_regions - - @observe('combination_selected') - def _combination_selected_updated(self, change): - self.app.session.edit_subset_mode.mode = SUBSET_MODES_PRETTY[change['new']] - - def _update_combination_mode(self): - if self.app.session.edit_subset_mode.mode in SUBSET_TO_PRETTY.keys(): - self.combination_mode.selected = SUBSET_TO_PRETTY[ - self.app.session.edit_subset_mode.mode] diff --git a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue b/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue deleted file mode 100644 index 00c70a694c..0000000000 --- a/jdaviz/configs/default/plugins/subset_plugin/subset_plugin.vue +++ /dev/null @@ -1,148 +0,0 @@ - diff --git a/jdaviz/configs/default/plugins/subset_tools/subset_tools.py b/jdaviz/configs/default/plugins/subset_tools/subset_tools.py index f4b478d42e..d536d1a5cf 100644 --- a/jdaviz/configs/default/plugins/subset_tools/subset_tools.py +++ b/jdaviz/configs/default/plugins/subset_tools/subset_tools.py @@ -1,44 +1,1014 @@ -from glue.core.message import EditSubsetMessage +import os + +import numpy as np + +from astropy.time import Time +import astropy.units as u +from glue.core.message import EditSubsetMessage, SubsetUpdateMessage from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode, - ReplaceMode, XorMode) + ReplaceMode, XorMode, NewMode) +from glue.core.roi import CircularROI, CircularAnnulusROI, EllipticalROI, RectangularROI +from glue.core.subset import (RoiSubsetState, RangeSubsetState, CompositeSubsetState, + MaskSubsetState) +from glue.icons import icon_path from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu -from glue_jupyter.widgets.subset_select_vuetify import SubsetSelect -from traitlets import Int, List +from glue_jupyter.common.toolbar_vuetify import read_icon +from traitlets import Any, List, Unicode, Bool, observe + +from specutils import SpectralRegion +from photutils.aperture import (CircularAperture, SkyCircularAperture, + EllipticalAperture, SkyEllipticalAperture, + RectangularAperture, SkyRectangularAperture, + CircularAnnulus, SkyCircularAnnulus) +from regions import (Regions, CirclePixelRegion, CircleSkyRegion, + EllipsePixelRegion, EllipseSkyRegion, + RectanglePixelRegion, RectangleSkyRegion, + CircleAnnulusPixelRegion, CircleAnnulusSkyRegion) + +from jdaviz.core.region_translators import regions2roi, aperture2regions +from jdaviz.core.events import SnackbarMessage, GlobalDisplayUnitChanged, LinkUpdatedMessage +from jdaviz.core.registries import tray_registry +from jdaviz.core.template_mixin import (PluginTemplateMixin, DatasetSelectMixin, + SubsetSelect, SelectPluginComponent) +from jdaviz.core.tools import ICON_DIR +from jdaviz.core.user_api import PluginUserApi +from jdaviz.core.helpers import _next_subset_num +from jdaviz.utils import MultiMaskSubsetState, data_has_valid_wcs + +from jdaviz.configs.default.plugins.subset_tools import utils -from jdaviz.core.registries import tool_registry -from jdaviz.core.template_mixin import TemplateMixin __all__ = ['SubsetTools'] SUBSET_MODES = { + 'new': NewMode, 'replace': ReplaceMode, - 'add': OrMode, + 'OrState': OrMode, + 'AndState': AndMode, + 'XorState': XorMode, + 'AndNotState': AndNotMode, + 'RangeSubsetState': RangeSubsetState, + 'RoiSubsetState': RoiSubsetState +} +SUBSET_MODES_PRETTY = { + 'new': NewMode, + 'replace': ReplaceMode, + 'or': OrMode, 'and': AndMode, 'xor': XorMode, - 'remove': AndNotMode + 'andnot': AndNotMode, } +SUBSET_TO_PRETTY = {v: k for k, v in SUBSET_MODES_PRETTY.items()} +COMBO_OPTIONS = list(SUBSET_MODES_PRETTY.keys()) + + +@tray_registry('g-subset-tools', label="Subset Tools") +class SubsetTools(PluginTemplateMixin, DatasetSelectMixin): + """ + See the :ref:`Subset Tools ` for more details. + Only the following attributes and methods are available through the + :ref:`public plugin API `: -@tool_registry('g-subset-tools') -class SubsetTools(TemplateMixin): + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray` + * :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray` + * ``subset`` (:class:`~jdaviz.core.template_mixin.SubsetSelect`): + Manages subset selection and creation + * ``combination_mode`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`): + Allows selection of combination modes for subsets + * :meth:`get_center` + * :meth:`set_center` + * :meth:`import_region` + """ template_file = __file__, "subset_tools.vue" select = List([]).tag(sync=True) - subset_mode = Int(0).tag(sync=True) + subset_items = List([]).tag(sync=True) + subset_selected = Any().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) + glue_state_types = List([]).tag(sync=True) + has_subset_details = Bool(False).tag(sync=True) + + subplugins_opened = Any().tag(sync=True) + + multiselect = Bool(False).tag(sync=True) # multiselect only for subset + is_centerable = Bool(False).tag(sync=True) + can_simplify = Bool(False).tag(sync=True) + can_freeze = Bool(False).tag(sync=True) + + icon_replace = Unicode(read_icon(os.path.join(icon_path("glue_replace", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa + icon_or = Unicode(read_icon(os.path.join(icon_path("glue_or", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa + icon_and = Unicode(read_icon(os.path.join(icon_path("glue_and", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa + icon_xor = Unicode(read_icon(os.path.join(icon_path("glue_xor", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa + icon_andnot = Unicode(read_icon(os.path.join(icon_path("glue_andnot", icon_format="svg")), 'svg+xml')).tag(sync=True) # noqa + + icon_radialtocheck = Unicode(read_icon(os.path.join(ICON_DIR, 'radialtocheck.svg'), 'svg+xml')).tag(sync=True) # noqa + icon_checktoradial = Unicode(read_icon(os.path.join(ICON_DIR, 'checktoradial.svg'), 'svg+xml')).tag(sync=True) # noqa + + combination_items = List([]).tag(sync=True) + combination_selected = Any().tag(sync=True) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + # description displayed under plugin title in tray + config = self.app.config + if config in ['cubeviz', 'specviz2d', 'mosviz', 'rampviz']: + self._plugin_description = 'Select and interact with spectral/spatial subsets.' + elif config == 'imviz': + self._plugin_description = 'Select and interact with spatial subsets.' + elif config == 'specviz': + self._plugin_description = 'Select and interact with spectral subsets.' + self.components = { - 'g-subset-select': SubsetSelect(session=self.session), 'g-subset-mode': SelectionModeMenu(session=self.session) } - self.hub.subscribe(self, EditSubsetMessage, - handler=self._subset_edit_event) + self.session.hub.subscribe(self, EditSubsetMessage, + handler=self._sync_selected_from_state) + self.session.hub.subscribe(self, SubsetUpdateMessage, + handler=self._on_subset_update) + self.session.hub.subscribe(self, GlobalDisplayUnitChanged, + handler=self._on_display_unit_changed) + self.session.hub.subscribe(self, LinkUpdatedMessage, + handler=self._on_link_update) + + self.subset = SubsetSelect(self, + 'subset_items', + 'subset_selected', + multiselect='multiselect', + default_text="Create New") + self.subset_states = [] + self.spectral_display_unit = None + + align_by = getattr(self.app, '_align_by', None) + self.display_sky_coordinates = (align_by == 'wcs' and not self.multiselect) + + self.combination_mode = SelectPluginComponent(self, + items='combination_items', + selected='combination_selected', + manual_options=COMBO_OPTIONS) + + @property + def user_api(self): + expose = ['subset', 'combination_mode', 'get_center', 'set_center', 'import_region'] + return PluginUserApi(self, expose) + + def _on_link_update(self, *args): + """When linking is changed pixels<>wcs, change display units of the + subset plugin from pixel (for pixel linking) to sky (for WCS linking). + If there is an active selection in the subset plugin, push this change + to the UI upon link change by calling _get_subset_definition, which + will re-determine how to display subset information.""" + + align_by = getattr(self.app, '_align_by', None) + self.display_sky_coordinates = (align_by == 'wcs') + + if self.subset_selected != self.subset.default_text: + self._get_subset_definition(*args) + + def _sync_selected_from_state(self, *args): + if not hasattr(self, 'subset') or self.multiselect: + # during initial init, this can trigger before the component is initialized + return + if self.session.edit_subset_mode.edit_subset == []: + if self.subset_selected != self.subset.default_text: + self.subset_selected = self.subset.default_text + self.show_region_info = False + else: + new_label = self.session.edit_subset_mode.edit_subset[0].label + if new_label != self.subset_selected: + if new_label not in [s['label'] for s in self.subset_items]: + self._sync_available_from_state() + self.subset_selected = self.session.edit_subset_mode.edit_subset[0].label + self.show_region_info = True + self._update_combination_mode() + + def _on_subset_update(self, *args): + self._sync_selected_from_state(*args) + if 'Create New' in self.subset_selected: + return + subsets_avail = [sg.label for sg in self.app.data_collection.subset_groups] + if self.subset_selected not in subsets_avail: + # subset selection should re-default after processing the deleted subset, + # for now we can safely ignore + return + self._get_subset_definition(*args) + subset_to_update = self.session.edit_subset_mode.edit_subset[0] + self.subset._update_subset(subset_to_update, attribute="type") + + def _sync_available_from_state(self, *args): + if not hasattr(self, 'subset'): + # during initial init, this can trigger before the component is initialized + return + self.subset_items = [{'label': self.subset.default_text}] + [ + self.subset._subset_to_dict(subset) for subset in + self.data_collection.subset_groups] + + @observe('subset_selected') + def _sync_selected_from_ui(self, change): + self.subset_definitions = [] + self.subset_types = [] + self.glue_state_types = [] + self.is_centerable = False + + if not hasattr(self, 'subset'): + # during initial init, this can trigger before the component is initialized + return + if change['new'] != self.subset.default_text: + self._get_subset_definition(change['new']) + self.show_region_info = change['new'] != self.subset.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: + self.session.edit_subset_mode.edit_subset = m + + def _unpack_get_subsets_for_ui(self): + """ + Convert what app.get_subsets returns into something the UI of this plugin + can display. + """ + if self.multiselect: + self.is_centerable = True + return + + include_sky_region = bool(self.display_sky_coordinates) + subset_information = self.app.get_subsets(self.subset_selected, + simplify_spectral=False, + use_display_units=True, + include_sky_region=include_sky_region) + + _around_decimals = 6 # Avoid 30 degrees from coming back as 29.999999999999996 + if not subset_information: + return + if ((len(subset_information) == 1) and + (isinstance(subset_information[0]["subset_state"], RangeSubsetState) or + (isinstance(subset_information[0]["subset_state"], RoiSubsetState) and + isinstance(subset_information[0]["subset_state"].roi, + (CircularROI, RectangularROI, EllipticalROI))))): + self.is_centerable = True + else: + self.is_centerable = False + + for spec in subset_information: + + subset_definition = [] + subset_type = '' + subset_state = spec["subset_state"] + glue_state = spec["glue_state"] + + if isinstance(subset_state, RoiSubsetState): + subset_definition.append({ + "name": "Parent", "att": "parent", + "value": subset_state.xatt.parent.label, + "orig": subset_state.xatt.parent.label}) + + sky_region = spec['sky_region'] + if self.display_sky_coordinates and (sky_region is not None): + subset_definition += utils._sky_region_to_subset_def(sky_region) + + else: + if isinstance(subset_state.roi, CircularROI): + x, y = subset_state.roi.center() + r = subset_state.roi.radius + subset_definition += [ + {"name": "X Center (pixels)", "att": "xc", + "value": x, "orig": x}, + {"name": "Y Center (pixels)", "att": "yc", + "value": y, "orig": y}, + {"name": "Radius (pixels)", "att": "radius", + "value": r, "orig": r}] + + elif isinstance(subset_state.roi, RectangularROI): + for att in ("Xmin", "Xmax", "Ymin", "Ymax"): + real_att = att.lower() + val = getattr(subset_state.roi, real_att) + subset_definition.append( + {"name": att + " (pixels)", "att": real_att, + "value": val, "orig": val}) + + theta = np.around(np.degrees(subset_state.roi.theta), + decimals=_around_decimals) + subset_definition.append({"name": "Angle", "att": "theta", + "value": theta, "orig": theta}) + + elif isinstance(subset_state.roi, EllipticalROI): + xc, yc = subset_state.roi.center() + rx = subset_state.roi.radius_x + ry = subset_state.roi.radius_y + theta = np.around(np.degrees(subset_state.roi.theta), + decimals=_around_decimals) + + subset_definition += [ + {"name": "X Center (pixels)", "att": "xc", + "value": xc, "orig": xc}, + {"name": "Y Center (pixels)", "att": "yc", + "value": yc, "orig": yc}, + {"name": "X Radius (pixels)", "att": "radius_x", + "value": rx, "orig": rx}, + {"name": "Y Radius (pixels)", "att": "radius_y", + "value": ry, "orig": ry}, + {"name": "Angle", "att": "theta", + "value": theta, "orig": theta}] + + elif isinstance(subset_state.roi, CircularAnnulusROI): + xc, yc = subset_state.roi.center() + inner_r = subset_state.roi.inner_radius + outer_r = subset_state.roi.outer_radius + subset_definition += [{"name": "X Center (pixels)", + "att": "xc", "value": xc, "orig": xc}, + {"name": "Y Center (pixels)", + "att": "yc", "value": yc, "orig": yc}, + {"name": "Inner Radius (pixels)", + "att": "inner_radius", + "value": inner_r, "orig": inner_r}, + {"name": "Outer Radius (pixels)", + "att": "outer_radius", + "value": outer_r, "orig": outer_r}] + + else: # pragma: no cover + raise NotImplementedError(f"Unable to translate {subset_state.roi.__class__.__name__}") # noqa: E501 + + subset_type = subset_state.roi.__class__.__name__ + + elif isinstance(subset_state, RangeSubsetState): + region = spec['region'] + if isinstance(region, Time): + lo = region.min() + hi = region.max() + subset_definition = [{"name": "Lower bound", "att": "lo", + "value": lo.value, "orig": lo.value}, + {"name": "Upper bound", "att": "hi", + "value": hi.value, "orig": hi.value}] + else: + lo = region.lower + hi = region.upper + subset_definition = [{"name": "Lower bound", "att": "lo", "value": lo.value, + "orig": lo.value, "unit": str(lo.unit)}, + {"name": "Upper bound", "att": "hi", "value": hi.value, + "orig": hi.value, "unit": str(hi.unit)}] + subset_type = "Range" + + elif isinstance(subset_state, MultiMaskSubsetState): + total_masked = subset_state.total_masked_first_data() + subset_definition = [{"name": "Masked values", "att": "masked", + "value": total_masked, + "orig": total_masked}] + subset_type = "Mask" + if len(subset_definition) > 0: + # Note: .append() does not work for List traitlet. + self.subset_definitions = self.subset_definitions + [subset_definition] + self.subset_types = self.subset_types + [subset_type] + self.glue_state_types = self.glue_state_types + [glue_state] + self.subset_states = self.subset_states + [subset_state] + + simplifiable_states = set(['AndState', 'XorState', 'AndNotState']) + # Check if the subset has more than one subregion, is a range subset + # type, and uses one of the states that can be simplified. Mask subset + # types cannot be simplified so subsets contained that are skipped. + if 'Mask' in self.subset_types: + self.can_simplify = False + elif ((len(self.subset_states) > 1) and isinstance(self.subset_states[0], RangeSubsetState) + and ((len(simplifiable_states - set(self.glue_state_types)) < 3) + or self.app.is_there_overlap_spectral_subset(self.subset_selected))): + self.can_simplify = True + else: + self.can_simplify = False + + 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 = [] + self.glue_state_types = [] + self.subset_states = [] + + self._unpack_get_subsets_for_ui() + + def vue_freeze_subset(self, *args): + sgs = {sg.label: sg for sg in self.app.data_collection.subset_groups} + sg = sgs.get(self.subset_selected) + + masks = {} + for data in self.app.data_collection: + masks[data.uuid] = sg.subset_state.to_mask(data) + + sg.subset_state = MultiMaskSubsetState(masks) + + def vue_simplify_subset(self, *args): + if self.multiselect: + self.hub.broadcast(SnackbarMessage("Cannot simplify spectral subset " + "when multiselect is active", color='warning', + sender=self)) + return + if len(self.subset_states) < 2: + self.hub.broadcast(SnackbarMessage("Cannot simplify spectral subset " + "of length less than 2", color='warning', + sender=self)) + return + att = self.subset_states[0].att + self.app.simplify_spectral_subset(subset_name=self.subset_selected, att=att, + overwrite=True) + + def _on_display_unit_changed(self, msg): + # We only care about the spectral units, since flux units don't affect spectral subsets + if msg.axis == "spectral": + self.spectral_display_unit = msg.unit + if self.subset_selected != self.subset.default_text: + self._get_subset_definition(self.subset_selected) + + def vue_update_subset(self, *args): + + if self.multiselect: + self.hub.broadcast(SnackbarMessage("Cannot update subset " + "when multiselect is active", color='warning', + sender=self)) + return + + status, reason = self._check_input() + if not status: + self.hub.broadcast(SnackbarMessage(reason, color='error', sender=self)) + return + + for index, sub in enumerate(self.subset_definitions): + if len(self.subset_states) <= index: + return + sub_states = self.subset_states[index] + + # we need to push updates to subset in pixels. to do this when wcs + # linked, convert the updated subset parameters from sky to pix + wcs = None + + if self.display_sky_coordinates: + wcs = self.app._get_wcs_from_subset(sub_states) + + if wcs is not None: + # convert newly entered sky coords to pixel + updated_skyreg = utils._subset_def_to_region(self.subset_types[index], sub) # noqa + updated_pixreg_attrs = utils._get_pixregion_params_in_dict(updated_skyreg.to_pixel(wcs)) # noqa + # convert previous entered sky coords to pixel + orig_skyreg = utils._subset_def_to_region(self.subset_types[index], sub, val='orig') # noqa + orig_pixreg_attrs = utils._get_pixregion_params_in_dict(orig_skyreg.to_pixel(wcs)) # noqa + + for d_att in sub: + if d_att["att"] == 'parent': # Read-only + continue + if self.display_sky_coordinates and (wcs is not None): + d_att["value"] = updated_pixreg_attrs[d_att["att"]] + d_att["orig"] = orig_pixreg_attrs[d_att["att"]] + + if (d_att["att"] == 'theta') and (self.display_sky_coordinates is False): + # Humans use degrees but glue uses radians + # We've already enforced this in wcs linking in _get_pixregion_params_in_dict + d_val = np.radians(d_att["value"]) + else: + d_val = float(d_att["value"]) + + # Convert from display unit to original unit if necessary + if self.subset_types[index] == "Range": + if self.spectral_display_unit is not None: + x_att = sub_states.att + # since this is a spectrum range subset, we can get the native units + # from the current reference data in the spectrum viewer + sv = self.spectrum_viewer + base_units = sv.state.reference_data.get_component(x_att).units + if self.spectral_display_unit != base_units: + d_val = d_val*u.Unit(self.spectral_display_unit) + d_val = d_val.to(u.Unit(base_units)) + d_val = d_val.value + + if float(d_att["orig"]) != d_val: + if self.subset_types[index] == "Range": + setattr(sub_states, d_att["att"], d_val) + else: + setattr(sub_states.roi, d_att["att"], d_val) + + self._push_update_to_ui() + + def _push_update_to_ui(self, subset_name=None): + """ + Forces the UI to update how it represents the subset. + + Parameters + ---------- + subset_name : str + The name of the subset that is being updated. + + """ + if not subset_name: + subset_name = self.subset_selected + try: + dc = self.data_collection + subsets = dc.subset_groups + subset_to_update = subsets[[x.label for x in subsets].index(subset_name)] + self.session.edit_subset_mode.edit_subset = [subset_to_update] + self.session.edit_subset_mode._combine_data(subset_to_update.subset_state, + override_mode=ReplaceMode) + except Exception as err: # pragma: no cover + self.hub.broadcast(SnackbarMessage( + f"Failed to update Subset: {repr(err)}", color='error', sender=self)) + + def _check_input(self): + status = True + reason = "" + for index, sub in enumerate(self.subset_definitions): + lo = hi = xmin = xmax = ymin = ymax = None + inner_radius = outer_radius = None + for d_att in sub: + if d_att["att"] == "lo": + lo = d_att["value"] + elif d_att["att"] == "hi": + hi = d_att["value"] + elif d_att["att"] == "radius" and d_att["value"] <= 0: + status = False + reason = "Failed to update Subset: radius must be a positive scalar" + break + elif d_att["att"] == "xmin": + xmin = d_att["value"] + elif d_att["att"] == "xmax": + xmax = d_att["value"] + elif d_att["att"] == "ymin": + ymin = d_att["value"] + elif d_att["att"] == "ymax": + ymax = d_att["value"] + elif d_att["att"] == "outer_radius": + outer_radius = d_att["value"] + elif d_att["att"] == "inner_radius": + inner_radius = d_att["value"] + + if lo and hi and hi <= lo: + status = False + reason = "Failed to update Subset: lower bound must be less than upper bound" + break + elif xmin and xmax and ymin and ymax and (xmax - xmin <= 0 or ymax - ymin <= 0): + status = False + reason = "Failed to update Subset: width and length must be positive scalars" + break + elif inner_radius and outer_radius and inner_radius >= outer_radius: + status = False + reason = "Failed to update Subset: inner radius must be less than outer radius" + break + + return status, reason + + def vue_recenter_subset(self, *args): + # Composite region cannot be centered. This only works for Imviz. + if not self.is_centerable or self.config != 'imviz': # no-op + raise NotImplementedError( + f'Cannot recenter: is_centerable={self.is_centerable}, config={self.config}') + + from astropy.wcs.utils import pixel_to_pixel + from photutils.aperture import ApertureStats + from jdaviz.core.region_translators import regions2aperture, _get_region_from_spatial_subset + + def _do_recentering(subset, subset_state): + try: + reg = _get_region_from_spatial_subset(self, subset_state) + aperture = regions2aperture(reg) + data = self.dataset.selected_dc_item + comp = data.get_component(data.main_components[0]) + comp_data = comp.data + phot_aperstats = ApertureStats(comp_data, aperture, wcs=data.coords) + + # Sky region from WCS linking, need to convert centroid back to pixels. + if hasattr(reg, "to_pixel"): + # Centroid was calculated in selected data. + # However, Subset is always defined w.r.t. its parent, + # so we need to convert back. + x, y = pixel_to_pixel( + data.coords, + subset_state.xatt.parent.coords, + phot_aperstats.xcentroid, + phot_aperstats.ycentroid) + else: + x = phot_aperstats.xcentroid + y = phot_aperstats.ycentroid + if not np.all(np.isfinite((x, y))): + raise ValueError(f'Invalid centroid ({x}, {y})') + except Exception as err: + self.set_center(self.get_center(subset_name=subset), subset_name=subset, + update=False) + self.hub.broadcast(SnackbarMessage( + f"Failed to calculate centroid: {repr(err)}", color='error', sender=self)) + else: + self.set_center((x, y), subset_name=subset, update=True) + + if not self.multiselect: + _do_recentering(self.subset_selected, self.subset.selected_subset_state) + else: + for sub, subset_state in zip(self.subset_selected, + self.subset.selected_subset_state): + if (sub != self.subset.default_text and + not isinstance(subset_state, CompositeSubsetState)): + self.is_centerable = True + _do_recentering(sub, subset_state) + elif (sub != self.subset.default_text and + isinstance(subset_state, CompositeSubsetState)): + self.hub.broadcast(SnackbarMessage(f"Unable to recenter " + f"composite subset {sub}", + color='error', sender=self)) + + def _get_subset_state(self, subset_name=None): + if self.multiselect and not subset_name: + raise ValueError("Please include subset_name in when in multiselect mode") + + if subset_name is not None: + return self.subset._get_subset_state(subset_name) + # guaranteed to only return a single entry because of check above + return self.subset.selected_subset_state + + def get_center(self, subset_name=None): + """Return the center of the Subset. + This may or may not be the centroid obtain from data. + + Parameters + ---------- + subset_name : str + The name of the subset that is being updated. + + Returns + ------- + cen : number, tuple of numbers, or `None` + The center of the Subset in ``x`` or ``(x, y)``, + depending on the Subset type, if applicable. + If Subset is not centerable, this returns `None`. + + """ + # Composite region cannot be centered. + if not self.is_centerable: # no-op + return + + subset_state = self._get_subset_state(subset_name) + return subset_state.center() + + def set_center(self, new_cen, subset_name=None, update=False): + """Set the desired center for the selected Subset, if applicable. + If Subset is not centerable, nothing is done. + + Parameters + ---------- + new_cen : number or tuple of numbers + The new center defined either as ``x`` or ``(x, y)``, + depending on the Subset type. + subset_name : str + The name of the subset that is being updated. + update : bool + If `True`, the Subset is also moved to the new center. + Otherwise, only the relevant editable fields are updated but the + Subset is not moved. + + Raises + ------ + NotImplementedError + Subset type is not supported. + + """ + # Composite region cannot be centered, so just grab first element. + if not self.is_centerable: # no-op + return + + subset_state = self._get_subset_state(subset_name) + + if isinstance(subset_state, RoiSubsetState): + x, y = new_cen + # x and y are arrays so this converts them back to floats + x = float(x) + y = float(y) + sbst_obj = subset_state.roi + if isinstance(sbst_obj, (CircularROI, CircularAnnulusROI, EllipticalROI)): + sbst_obj.move_to(x, y) + elif isinstance(sbst_obj, RectangularROI): + sbst_obj.move_to(x, y) + else: # pragma: no cover + raise NotImplementedError(f'Recentering of {sbst_obj.__class__} is not supported') + + elif isinstance(subset_state, RangeSubsetState): + subset_state.move_to(new_cen) + + else: # pragma: no cover + raise NotImplementedError( + f'Getting center of {subset_state.__class__} is not supported') + + if update: + self._push_update_to_ui(subset_name=subset_name) + else: + # Force UI to update on browser without changing the subset. + tmp = self.subset_definitions + self.subset_definitions = [] + self.subset_definitions = tmp + + # 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 + + def import_region(self, region, combination_mode=None, max_num_regions=None, + refdata_label=None, return_bad_regions=False, **kwargs): + """ + Method for creating subsets from regions or region files. + + Parameters + ---------- + region : region, list of region objects, or str + A region object can be one of the following: + + * Astropy ``regions`` object + * ``photutils`` apertures (limited support until ``photutils`` + fully supports ``regions``) + * specutils ``SpectralRegion`` object + + A string which represents a ``regions`` or ``SpectralRegion`` file. + If given as a list, it can only contain spectral or non-spectral regions, not both. + + combination_mode : list, str, or `None` + The way that regions are created or combined. If a list, then it must be the + same length as regions. If `None`, then it will follow the default glue + functionality for subset creation. + + max_num_regions : int or `None` + Maximum number of regions to load, starting from top of the list. + Default is to load everything. If you are providing a large file/list + input for ``region``, it is recommended + + refdata_label : str or `None` + **This is only applicable to non-spectral regions.** + Label of data to use for sky-to-pixel conversion for a region, or + mask creation. Data must already be loaded into Jdaviz. + If `None`, defaults to the reference data in the default viewer. + Choice of this data is particularly important when sky + region is involved. + + return_bad_regions : bool + If `True`, return the regions that failed to load (see ``bad_regions``); + This is useful for debugging. If `False`, do not return anything (`None`). + + Returns + ------- + bad_regions : list of (obj, str) or `None` + If requested (see ``return_bad_regions`` option), return a + list of ``(region, reason)`` tuples for region objects that failed to load. + If all the regions loaded successfully, this list will be empty. + If not requested, return `None`. + + """ + if isinstance(region, str): + if os.path.exists(region): + from regions import Regions + region_format = kwargs.pop('region_format', None) + try: + raw_regs = Regions.read(region, format=region_format) + except Exception: # nosec + raw_regs = SpectralRegion.read(region) + + return self._load_regions(raw_regs, combination_mode, max_num_regions, + refdata_label, return_bad_regions, **kwargs) + else: + return self._load_regions(region, combination_mode, max_num_regions, refdata_label, + return_bad_regions, **kwargs) + + def _load_regions(self, regions, combination_mode=None, max_num_regions=None, + refdata_label=None, return_bad_regions=False, **kwargs): + """Load given region(s) into the viewer. + WCS-to-pixel translation and mask creation, if needed, is relative + to the image defined by ``refdata_label``. Meanwhile, the rest of + the Subset operations are based on reference image as defined by Glue. + + .. note:: Loading too many regions will affect performance. + + A valid region can be loaded into one of the following categories: + + * An interactive Subset, as if it was drawn by hand. This is + always done for supported shapes. Its label will be + ``'Subset N'``, where ``N`` is an integer. + * A masked Subset that will display on the image but cannot be + modified once loaded. This is done if the shape cannot be + made interactive. Its label will be ``'MaskedSubset N'``, + where ``N`` is an integer. + + Parameters + ---------- + regions : list of obj + A list of region objects. A region object can be one of the following: + + * Astropy ``regions`` object + * ``photutils`` apertures (limited support until ``photutils`` + fully supports ``regions``) + * specutils ``SpectralRegion`` object + + combination_mode : list, str, or `None` + The way that regions are created or combined. If a list, then it must be the + same length as regions. Each element describes how the corresponding region + in regions will be applied. If `None`, then it will follow the default glue + functionality for subset creation. Options are ['new', 'replace', 'or', 'and', + 'xor', 'andnot'] + + max_num_regions : int or `None` + Maximum number of regions to load, starting from top of the list. + Default is to load everything. + + refdata_label : str or `None` + Label of data to use for sky-to-pixel conversion for a region, or + mask creation. Data must already be loaded into Jdaviz. + If `None`, defaults to the reference data in the default viewer. + Choice of this data is particularly important when sky + region is involved. + + return_bad_regions : bool + If `True`, return the regions that failed to load (see ``bad_regions``); + This is useful for debugging. If `False`, do not return anything (`None`). + + kwargs : dict + Extra keywords to be passed into the region's ``to_mask`` method. + **This is ignored if the region can be made interactive.** + + Returns + ------- + bad_regions : list of (obj, str) or `None` + If requested (see ``return_bad_regions`` option), return a + list of ``(region, reason)`` tuples for region objects that failed to load. + If all the regions loaded successfully, this list will be empty. + If not requested, return `None`. + + """ + if len(self.app.data_collection) == 0: + raise ValueError('Cannot load regions without data.') + + if not isinstance(regions, (list, tuple, Regions, SpectralRegion)): + regions = [regions] + + n_loaded = 0 + bad_regions = [] + + # To keep track of masked subsets. + msg_prefix = 'MaskedSubset' + msg_count = _next_subset_num(msg_prefix, self.app.data_collection.subset_groups) + + viewer_parameter = kwargs.pop('viewer', None) + viewer_name = viewer_parameter or list(self.app._jdaviz_helper.viewers.keys())[0] + viewer = self.app.get_viewer(viewer_name) + # Subset is global but reference data is viewer-dependent. + if refdata_label is None: + data = viewer.state.reference_data + else: + data = self.app.data_collection[refdata_label] + + has_wcs = data_has_valid_wcs(data, ndim=2) + + combo_mode_is_list = isinstance(combination_mode, list) + if combo_mode_is_list and len(combination_mode) != (len(regions)): + raise ValueError("list of mode must be size of regions") + elif combo_mode_is_list: + unknown_options = list(set(combination_mode) - set(COMBO_OPTIONS)) + if len(unknown_options) > 0: + raise ValueError(f"{unknown_options} not one of {COMBO_OPTIONS}") + + previous_mode = self.app.session.edit_subset_mode.mode + + for index, region in enumerate(regions): + # Set combination mode for how region will be applied to current subset + # or created as a new subset + if combo_mode_is_list: + combo_mode = combination_mode[index] + else: + combo_mode = combination_mode + + if combo_mode == 'new': + # Remove selection of subset so that new one will be created + self.app.session.edit_subset_mode.edit_subset = None # No overwrite next iteration + self.app.session.edit_subset_mode.mode = SUBSET_MODES_PRETTY['new'] + elif combo_mode: + self.combination_mode.selected = combo_mode + + if (isinstance(region, (SkyCircularAperture, SkyEllipticalAperture, + SkyRectangularAperture, SkyCircularAnnulus, + CircleSkyRegion, EllipseSkyRegion, + RectangleSkyRegion, CircleAnnulusSkyRegion)) + and not has_wcs): + bad_regions.append((region, 'Sky region provided but data has no valid WCS')) + continue + + if (isinstance(region, (CircularAperture, EllipticalAperture, + RectangularAperture, CircularAnnulus, + CirclePixelRegion, EllipsePixelRegion, + RectanglePixelRegion, CircleAnnulusPixelRegion)) + and (hasattr(self.app, '_link_type') and self.app._link_type == "wcs")): + bad_regions.append((region, 'Pixel region provided by data is linked by WCS')) + continue + + # photutils: Convert to region shape first + if isinstance(region, (CircularAperture, SkyCircularAperture, + EllipticalAperture, SkyEllipticalAperture, + RectangularAperture, SkyRectangularAperture, + CircularAnnulus, SkyCircularAnnulus)): + region = aperture2regions(region) + + # region: Convert to ROI. + # NOTE: Out-of-bounds ROI will succeed; this is native glue behavior. + if isinstance(region, (CirclePixelRegion, CircleSkyRegion, + EllipsePixelRegion, EllipseSkyRegion, + RectanglePixelRegion, RectangleSkyRegion, + CircleAnnulusPixelRegion, CircleAnnulusSkyRegion)): + state = regions2roi(region, wcs=data.coords) + viewer.apply_roi(state) + + elif isinstance(region, (CircularROI, CircularAnnulusROI, + EllipticalROI, RectangularROI)): + viewer.apply_roi(region) + + elif isinstance(region, SpectralRegion): + # Use viewer_name if provided in kwarg, otherwise use + # default spectrum viewer name + viewer_name = (viewer_parameter or + self.app._jdaviz_helper._default_spectrum_viewer_reference_name) + range_viewer = self.app.get_viewer(viewer_name) + + s = RangeSubsetState(lo=region.lower.value, hi=region.upper.value, + att=range_viewer.state.x_att) + range_viewer.apply_subset_state(s) + + # Last resort: Masked Subset that is static (if data is not a cube) + elif data.ndim == 2: + im = None + if hasattr(region, 'to_pixel'): # Sky region: Convert to pixel region + if not has_wcs: + bad_region.append((region, 'Sky region provided but data has no valid WCS')) # noqa + continue + region = region.to_pixel(data.coords) + + if hasattr(region, 'to_mask'): + try: + mask = region.to_mask(**kwargs) + im = mask.to_image(data.shape) # Can be None + except Exception as e: # pragma: no cover + bad_regions.append((region, f'Failed to load: {repr(e)}')) + continue + + # Boolean mask as input is supported but not advertised. + elif (isinstance(region, np.ndarray) and region.shape == data.shape + and region.dtype == np.bool_): + im = region + + if im is None: + bad_regions.append((region, 'Mask creation failed')) + continue + + # NOTE: Region creation info is thus lost. + try: + subset_label = f'{msg_prefix} {msg_count}' + state = MaskSubsetState(im, data.pixel_component_ids) + self.app.data_collection.new_subset_group(subset_label, state) + msg_count += 1 + except Exception as e: # pragma: no cover + bad_regions.append((region, f'Failed to load: {repr(e)}')) + continue + else: + bad_regions.append((region, 'Mask creation failed')) + continue + n_loaded += 1 + if max_num_regions is not None and n_loaded >= max_num_regions: + break + + # Revert edit mode to before the import_region call + self.app.session.edit_subset_mode.mode = previous_mode + + n_reg_in = len(regions) + n_reg_bad = len(bad_regions) + if n_loaded == 0: + snack_color = "error" + elif n_reg_bad > 0: + snack_color = "warning" + else: + snack_color = "success" + self.app.hub.broadcast(SnackbarMessage( + f"Loaded {n_loaded}/{n_reg_in} regions, max_num_regions={max_num_regions}, " + f"bad={n_reg_bad}", color=snack_color, timeout=8000, sender=self.app)) + + if return_bad_regions: + return bad_regions + + @observe('combination_selected') + def _combination_selected_updated(self, change): + self.app.session.edit_subset_mode.mode = SUBSET_MODES_PRETTY[change['new']] - def _subset_edit_event(self, msg): - # NOTE: here we'll assume that only a single subset is selected (glue supports - # multiple subsets being selected, but jdaviz doesn't) - if not (len(msg.subset)): - # then changed to "Create New", we want to revert the mode to ReplaceMode - self.session.edit_subset_mode.mode = ReplaceMode + def _update_combination_mode(self): + if self.app.session.edit_subset_mode.mode in SUBSET_TO_PRETTY.keys(): + self.combination_mode.selected = SUBSET_TO_PRETTY[ + self.app.session.edit_subset_mode.mode] diff --git a/jdaviz/configs/default/plugins/subset_tools/subset_tools.vue b/jdaviz/configs/default/plugins/subset_tools/subset_tools.vue index 48ee6da3bc..00c70a694c 100644 --- a/jdaviz/configs/default/plugins/subset_tools/subset_tools.vue +++ b/jdaviz/configs/default/plugins/subset_tools/subset_tools.vue @@ -1,7 +1,148 @@ \ No newline at end of file + + + +
+
+
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + Recenter + + + + + Recenter + + + + + + + +
+ + Subregion {{ index }} + +
+ {{ subset_types[index] }} applied with +
+
+
+ + replace mode +
+
+ + and mode +
+
+ + add mode +
+
+ + remove mode +
+
+ + xor mode +
+
+
+ +
+ + + + + + +
+
+ + + + + Freeze + + + + + Simplify + + + + Update + + +
+ diff --git a/jdaviz/configs/default/plugins/subset_plugin/__init__.py b/jdaviz/configs/default/plugins/subset_tools/tests/__init__.py similarity index 100% rename from jdaviz/configs/default/plugins/subset_plugin/__init__.py rename to jdaviz/configs/default/plugins/subset_tools/tests/__init__.py diff --git a/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py b/jdaviz/configs/default/plugins/subset_tools/tests/test_subset_tools.py similarity index 95% rename from jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py rename to jdaviz/configs/default/plugins/subset_tools/tests/test_subset_tools.py index 774af4c52b..01c75bd658 100644 --- a/jdaviz/configs/default/plugins/subset_plugin/tests/test_subset_plugin.py +++ b/jdaviz/configs/default/plugins/subset_tools/tests/test_subset_tools.py @@ -9,14 +9,14 @@ from glue.core.edit_subset_mode import ReplaceMode, OrMode from numpy.testing import assert_allclose -from jdaviz.configs.default.plugins.subset_plugin import utils +from jdaviz.configs.default.plugins.subset_tools import utils from jdaviz.core.region_translators import regions2roi @pytest.mark.filterwarnings('ignore') def test_plugin(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - p = specviz_helper.plugins['Subsets'] + p = specviz_helper.plugins['Subset Tools'] # regression test for https://github.com/spacetelescope/jdaviz/issues/1693 unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) @@ -33,7 +33,7 @@ def test_subset_definition_with_composite_subset(cubeviz_helper, spectrum1d_cube with warnings.catch_warnings(): warnings.simplefilter('ignore') cubeviz_helper.load_data(spectrum1d_cube) - cubeviz_helper.app.get_tray_item_from_name('g-subset-plugin') + cubeviz_helper.app.get_tray_item_from_name('g-subset-tools') circle_subset_info = {'xc': {'pixel_name': 'X Center (pixels)', 'wcs_name': @@ -99,10 +99,10 @@ def test_circle_recenter_linking(roi_class, subset_info, imviz_helper, image_2d_ # apply subset roi_params = {key: subset_info[key]['initial_value'] for key in subset_info} - imviz_helper.plugins['Subsets'].import_region(roi_class(**roi_params)) + imviz_helper.plugins['Subset Tools'].import_region(roi_class(**roi_params)) # get plugin and check that attribute tracking link type is set properly - plugin = imviz_helper.plugins['Subsets']._obj + plugin = imviz_helper.plugins['Subset Tools']._obj assert not plugin.display_sky_coordinates # get initial subset definitions from ROI applied @@ -146,7 +146,7 @@ def test_circle_recenter_linking(roi_class, subset_info, imviz_helper, image_2d_ img_wcs = imviz_helper.app.data_collection['Default orientation'].coords new_pix_region = original_sky_region.to_pixel(img_wcs) new_roi = regions2roi(new_pix_region) - imviz_helper.plugins['Subsets'].import_region(new_roi) + imviz_helper.plugins['Subset Tools'].import_region(new_roi) # get subset definitions again, which should now be in sky coordinates subset_defs = plugin.subset_definitions @@ -194,7 +194,7 @@ def test_circle_recenter_linking(roi_class, subset_info, imviz_helper, image_2d_ def test_import_spectral_region(cubeviz_helper, spectrum1d_cube, spec_regions, mode, len_subsets, len_subregions): cubeviz_helper.load_data(spectrum1d_cube) - plg = cubeviz_helper.plugins['Subsets'] + plg = cubeviz_helper.plugins['Subset Tools'] plg.import_region(spec_regions, combination_mode=mode) subsets = cubeviz_helper.app.get_subsets() assert len(subsets) == len_subsets @@ -204,7 +204,7 @@ def test_import_spectral_region(cubeviz_helper, spectrum1d_cube, spec_regions, m def test_import_spectral_regions_file(cubeviz_helper, spectrum1d_cube, tmp_path): cubeviz_helper.load_data(spectrum1d_cube) - plg = cubeviz_helper.plugins['Subsets'] + plg = cubeviz_helper.plugins['Subset Tools'] s = SpectralRegion(5*u.um, 6*u.um) local_path = str(tmp_path / 'spectral_region.ecsv') s.write(local_path) diff --git a/jdaviz/configs/default/plugins/subset_plugin/utils.py b/jdaviz/configs/default/plugins/subset_tools/utils.py similarity index 100% rename from jdaviz/configs/default/plugins/subset_plugin/utils.py rename to jdaviz/configs/default/plugins/subset_tools/utils.py diff --git a/jdaviz/configs/default/plugins/subset_plugin/tests/__init__.py b/jdaviz/configs/default/plugins/subset_widget/__init__.py similarity index 100% rename from jdaviz/configs/default/plugins/subset_plugin/tests/__init__.py rename to jdaviz/configs/default/plugins/subset_widget/__init__.py diff --git a/jdaviz/configs/default/plugins/subset_widget/subset_widget.py b/jdaviz/configs/default/plugins/subset_widget/subset_widget.py new file mode 100644 index 0000000000..be3ac9148f --- /dev/null +++ b/jdaviz/configs/default/plugins/subset_widget/subset_widget.py @@ -0,0 +1,44 @@ +from glue.core.message import EditSubsetMessage +from glue.core.edit_subset_mode import (AndMode, AndNotMode, OrMode, + ReplaceMode, XorMode) +from glue_jupyter.widgets.subset_mode_vuetify import SelectionModeMenu +from glue_jupyter.widgets.subset_select_vuetify import SubsetSelect +from traitlets import Int, List + +from jdaviz.core.registries import tool_registry +from jdaviz.core.template_mixin import TemplateMixin + +__all__ = ['SubsetAppBarWidget'] + +SUBSET_MODES = { + 'replace': ReplaceMode, + 'add': OrMode, + 'and': AndMode, + 'xor': XorMode, + 'remove': AndNotMode +} + + +@tool_registry('g-subset-tools') +class SubsetAppBarWidget(TemplateMixin): + template_file = __file__, "subset_widget.vue" + select = List([]).tag(sync=True) + subset_mode = Int(0).tag(sync=True) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.components = { + 'g-subset-select': SubsetSelect(session=self.session), + 'g-subset-mode': SelectionModeMenu(session=self.session) + } + + self.hub.subscribe(self, EditSubsetMessage, + handler=self._subset_edit_event) + + def _subset_edit_event(self, msg): + # NOTE: here we'll assume that only a single subset is selected (glue supports + # multiple subsets being selected, but jdaviz doesn't) + if not (len(msg.subset)): + # then changed to "Create New", we want to revert the mode to ReplaceMode + self.session.edit_subset_mode.mode = ReplaceMode diff --git a/jdaviz/configs/default/plugins/subset_widget/subset_widget.vue b/jdaviz/configs/default/plugins/subset_widget/subset_widget.vue new file mode 100644 index 0000000000..48ee6da3bc --- /dev/null +++ b/jdaviz/configs/default/plugins/subset_widget/subset_widget.vue @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/jdaviz/configs/default/tests/test_data_menu.py b/jdaviz/configs/default/tests/test_data_menu.py index ecc354d0a3..8d83ff4427 100644 --- a/jdaviz/configs/default/tests/test_data_menu.py +++ b/jdaviz/configs/default/tests/test_data_menu.py @@ -24,7 +24,7 @@ def test_data_menu_toggles(specviz_helper, spectrum1d): assert len(dm._obj.visible_layers) == 1 # add a subset and make sure it appears for the first data entry but not the second - specviz_helper.plugins['Subsets'].import_region( + specviz_helper.plugins['Subset Tools'].import_region( SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 6500 * spectrum1d.spectral_axis.unit)) assert len(dm._obj.layer_items) == 3 @@ -119,7 +119,7 @@ def test_data_menu_remove_subset(specviz_helper, spectrum1d): specviz_helper.load_data(new_spec, data_label="test2") dm = specviz_helper.viewers['spectrum-viewer']._obj.data_menu - sp = specviz_helper.plugins['Subsets'] + sp = specviz_helper.plugins['Subset Tools'] sp.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 6100 * spectrum1d.spectral_axis.unit), @@ -153,7 +153,7 @@ def test_data_menu_subset_appearance(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, data_label="test") dm = specviz_helper.viewers['spectrum-viewer']._obj.data_menu - sp = specviz_helper.plugins['Subsets'] + sp = specviz_helper.plugins['Subset Tools'] sp.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 6100 * spectrum1d.spectral_axis.unit)) @@ -169,7 +169,7 @@ def test_data_menu_view_info(specviz_helper, spectrum1d): dm = specviz_helper.viewers['spectrum-viewer']._obj.data_menu mp = specviz_helper.plugins['Metadata'] - sp = specviz_helper.plugins['Subsets'] + sp = specviz_helper.plugins['Subset Tools'] sp.import_region(SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 6100 * spectrum1d.spectral_axis.unit), diff --git a/jdaviz/configs/imviz/imviz.yaml b/jdaviz/configs/imviz/imviz.yaml index 9f0c49732a..7d1a2ea6c2 100644 --- a/jdaviz/configs/imviz/imviz.yaml +++ b/jdaviz/configs/imviz/imviz.yaml @@ -23,7 +23,7 @@ tray: - imviz-orientation - g-plot-options - g-data-quality - - g-subset-plugin + - g-subset-tools - g-markers - imviz-compass - imviz-line-profile-xy diff --git a/jdaviz/configs/imviz/plugins/parsers.py b/jdaviz/configs/imviz/plugins/parsers.py index e1871218cf..7decc4b6a2 100644 --- a/jdaviz/configs/imviz/plugins/parsers.py +++ b/jdaviz/configs/imviz/plugins/parsers.py @@ -126,8 +126,8 @@ def parse_data(app, file_obj, ext=None, data_label=None, elif file_obj_lower.endswith('.reg'): # This will load DS9 regions as Subset but only if there is already data. - app.get_tray_item_from_name('g-subset-plugin').import_region(file_obj, - combination_mode='new') + app.get_tray_item_from_name('g-subset-tools').import_region(file_obj, + combination_mode='new') else: # Assume FITS with fits.open(file_obj) as pf: diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py index d1122057a6..3726ee75cb 100644 --- a/jdaviz/configs/imviz/plugins/viewers.py +++ b/jdaviz/configs/imviz/plugins/viewers.py @@ -190,7 +190,7 @@ def _get_real_xy(self, image, x, y, reverse=False): When `True`, it sets the coords, otherwise it resets. ``reverse=True`` is only for internal roundtripping (e.g., centroiding - in Subsets plugin). Never use this for coordinates display panel. + in Subset Tools plugin). Never use this for coordinates display panel. """ # By default we'll assume the coordinates are valid and within any applicable bounding box. diff --git a/jdaviz/configs/imviz/tests/test_delete_data.py b/jdaviz/configs/imviz/tests/test_delete_data.py index 11f855c9e6..62b8ca08e4 100644 --- a/jdaviz/configs/imviz/tests/test_delete_data.py +++ b/jdaviz/configs/imviz/tests/test_delete_data.py @@ -29,11 +29,11 @@ def test_delete_with_subset_wcs(self): # Add a subset reg = CirclePixelRegion(PixCoord(2, 2), 3).to_sky(self.wcs_1) - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].import_region(reg) - self.imviz.plugins['Subsets'].combination_mode = 'new' + self.imviz.plugins['Subset Tools'].combination_mode = 'new' reg = RectanglePixelRegion(PixCoord(1, 1), 2, 2).to_sky(self.wcs_1) - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].import_region(reg) assert len(self.imviz.app.data_collection.subset_groups) == 2 @@ -92,7 +92,7 @@ def test_delete_wcs_layer_with_subset(self): # Create a rotated ellipse. reg = EllipsePixelRegion( PixCoord(3.5, 4.5), width=2, height=5, angle=Angle(30, 'deg')).to_sky(self.wcs_1) - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].import_region(reg) # Switch back to Default Orientation. self.imviz.app._change_reference_data("Default orientation") diff --git a/jdaviz/configs/imviz/tests/test_linking.py b/jdaviz/configs/imviz/tests/test_linking.py index 326f36e5ff..9e0b98e152 100644 --- a/jdaviz/configs/imviz/tests/test_linking.py +++ b/jdaviz/configs/imviz/tests/test_linking.py @@ -101,8 +101,8 @@ def test_wcslink_affine_with_extras(self): # Add subsets, both interactive and static. self.imviz._apply_interactive_region('bqplot:truecircle', (1.5, 2.5), (3.6, 4.6)) - self.imviz.plugins['Subsets'].combination_mode = 'new' - self.imviz.plugins['Subsets'].import_region([ + self.imviz.plugins['Subset Tools'].combination_mode = 'new' + self.imviz.plugins['Subset Tools'].import_region([ CirclePixelRegion(center=PixCoord(x=6, y=2), radius=5).to_sky(self.wcs_1), PolygonPixelRegion(vertices=PixCoord(x=[1, 2, 2], y=[1, 1, 2])).to_sky(self.wcs_1), PolygonPixelRegion(vertices=PixCoord(x=[2, 3, 3], y=[2, 2, 3])).to_sky(self.wcs_1)]) diff --git a/jdaviz/configs/imviz/tests/test_orientation.py b/jdaviz/configs/imviz/tests/test_orientation.py index 6f6e9f6429..96d4a4737c 100644 --- a/jdaviz/configs/imviz/tests/test_orientation.py +++ b/jdaviz/configs/imviz/tests/test_orientation.py @@ -197,7 +197,7 @@ def test_delete_orientation_with_subset(self, klass, angle, sbst_theta): # Create rotated shape reg = klass(center=SkyCoord(ra=337.51931488, dec=-20.83187472, unit="deg"), width=2.4 * u.arcsec, height=1.2 * u.arcsec, angle=angle) - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].import_region(reg) # Switch to N-up E-right lc_plugin.set_north_up_east_right() diff --git a/jdaviz/configs/imviz/tests/test_regions.py b/jdaviz/configs/imviz/tests/test_regions.py index 25e5c577d5..4d8c53093a 100644 --- a/jdaviz/configs/imviz/tests/test_regions.py +++ b/jdaviz/configs/imviz/tests/test_regions.py @@ -199,7 +199,7 @@ def test_ds9_load_all(self, imviz_helper): self.viewer = imviz_helper.default_viewer._obj imviz_helper.load_data(self.arr, data_label='my_image') - bad_regions = imviz_helper.plugins['Subsets'].import_region( + bad_regions = imviz_helper.plugins['Subset Tools'].import_region( self.region_file, combination_mode='new', return_bad_regions=True) assert len(bad_regions) == 1 @@ -214,7 +214,7 @@ def test_ds9_load_all(self, imviz_helper): def test_ds9_load_two_good(self, imviz_helper): self.viewer = imviz_helper.default_viewer._obj imviz_helper.load_data(self.arr, data_label='my_image') - bad_regions = imviz_helper.plugins['Subsets'].import_region( + bad_regions = imviz_helper.plugins['Subset Tools'].import_region( self.region_file, combination_mode='new', max_num_regions=2, return_bad_regions=True) assert len(bad_regions) == 0 subsets = imviz_helper.get_interactive_regions() @@ -224,7 +224,7 @@ def test_ds9_load_two_good(self, imviz_helper): def test_ds9_load_one_bad(self, imviz_helper): self.viewer = imviz_helper.default_viewer._obj imviz_helper.load_data(self.arr, data_label='my_image') - bad_regions = imviz_helper.plugins['Subsets'].import_region( + bad_regions = imviz_helper.plugins['Subset Tools'].import_region( self.raw_regions[6], return_bad_regions=True) assert len(bad_regions) == 1 assert imviz_helper.get_interactive_regions() == {} @@ -233,7 +233,7 @@ def test_ds9_load_one_bad(self, imviz_helper): def test_ds9_load_one_good_one_bad(self, imviz_helper): self.viewer = imviz_helper.default_viewer._obj imviz_helper.load_data(self.arr, data_label='my_image') - bad_regions = imviz_helper.plugins['Subsets'].import_region( + bad_regions = imviz_helper.plugins['Subset Tools'].import_region( [self.raw_regions[3], self.raw_regions[6]], return_bad_regions=True) assert len(bad_regions) == 1 diff --git a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py index f8dd2c18ae..81468d7923 100644 --- a/jdaviz/configs/imviz/tests/test_simple_aper_phot.py +++ b/jdaviz/configs/imviz/tests/test_simple_aper_phot.py @@ -25,7 +25,7 @@ def test_plugin_wcs_dithered(self): self.imviz.link_data(align_by='wcs') # They are dithered by 1 pixel on X reg = CirclePixelRegion(center=PixCoord(x=4.5, y=4.5), radius=4.5).to_sky(self.wcs_1) - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].import_region(reg) phot_plugin = self.imviz.app.get_tray_item_from_name('imviz-aper-phot-simple') @@ -120,8 +120,8 @@ def test_plugin_wcs_dithered(self): # Make sure it also works on an ellipse subset. reg = EllipsePixelRegion(center=PixCoord(x=4.5, y=2.0), width=9.0, height=4.0).to_sky(self.wcs_1) # noqa: E501 - self.imviz.plugins['Subsets'].combination_mode = 'new' - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].combination_mode = 'new' + self.imviz.plugins['Subset Tools'].import_region(reg) phot_plugin.dataset_selected = 'has_wcs_1[SCI,1]' phot_plugin.aperture_selected = 'Subset 2' @@ -144,8 +144,8 @@ def test_plugin_wcs_dithered(self): # Make sure it also works on a rectangle subset. # We also subtract off background from itself here. reg = RectanglePixelRegion(center=PixCoord(x=4.5, y=4.5), width=9, height=9).to_sky(self.wcs_1) # noqa: E501 - self.imviz.plugins['Subsets'].combination_mode = 'new' - self.imviz.plugins['Subsets'].import_region(reg) + self.imviz.plugins['Subset Tools'].combination_mode = 'new' + self.imviz.plugins['Subset Tools'].import_region(reg) phot_plugin.dataset_selected = 'has_wcs_1[SCI,1]' phot_plugin.aperture_selected = 'Subset 3' @@ -282,7 +282,7 @@ def setup_class(self, imviz_helper): w = imviz_helper.app.data_collection[0].coords # Regions to be used for aperture photometry - imviz_helper.plugins['Subsets'].import_region([ + imviz_helper.plugins['Subset Tools'].import_region([ CirclePixelRegion(center=PixCoord(x=145.1, y=168.3), radius=5).to_sky(w), CirclePixelRegion(center=PixCoord(x=48.3, y=200.3), radius=5).to_sky(w), EllipsePixelRegion(center=PixCoord(x=84.7, y=224.1), width=23, height=9, angle=2.356 * u.rad).to_sky(w), # noqa: E501 @@ -361,8 +361,8 @@ def test_annulus_background(imviz_helper): # Load annulus (this used to be part of the plugin but no longer) annulus_1 = CircleAnnulusPixelRegion( PixCoord(x=150, y=25), inner_radius=7, outer_radius=17) - imviz_helper.plugins['Subsets'].import_region([circle_1, annulus_1], - combination_mode='new') + imviz_helper.plugins['Subset Tools'].import_region([circle_1, annulus_1], + combination_mode='new') phot_plugin.aperture_selected = 'Subset 1' phot_plugin.background_selected = 'Subset 2' @@ -379,8 +379,8 @@ def test_annulus_background(imviz_helper): # Load annulus (this used to be part of the plugin but no longer) annulus_2 = CircleAnnulusPixelRegion( PixCoord(x=20.5, y=37.5), inner_radius=20.5, outer_radius=30.5) - imviz_helper.plugins['Subsets'].import_region([ellipse_1, annulus_2], - combination_mode='new') + imviz_helper.plugins['Subset Tools'].import_region([ellipse_1, annulus_2], + combination_mode='new') # Subset 4 (annulus) should be available in both sets of choices, but invalid for selection as # aperture @@ -412,7 +412,7 @@ def test_annulus_background(imviz_helper): assert_allclose(phot_plugin.background_value, bg_4gauss_2) # Edit the annulus and make sure background updates - subset_plugin = imviz_helper.plugins["Subsets"]._obj + subset_plugin = imviz_helper.plugins['Subset Tools']._obj subset_plugin.subset_selected = "Subset 4" subset_plugin._set_value_in_subset_definition(0, "X Center (pixels)", "value", 25.5) subset_plugin._set_value_in_subset_definition(0, "Y Center (pixels)", "value", 42.5) @@ -431,7 +431,7 @@ def test_fit_radial_profile_with_nan(imviz_helper): # Mark an object of interest circle_1 = CirclePixelRegion(center=PixCoord(x=150, y=25), radius=7) - imviz_helper.plugins['Subsets'].import_region( + imviz_helper.plugins['Subset Tools'].import_region( [circle_1], combination_mode='new') phot_plugin = imviz_helper.app.get_tray_item_from_name('imviz-aper-phot-simple') @@ -528,7 +528,7 @@ def test_cubeviz_batch(cubeviz_helper, spectrum1d_cube_fluxunit_jy_per_steradian cubeviz_helper.load_data(spectrum1d_cube_fluxunit_jy_per_steradian, data_label='test') phot_plugin = cubeviz_helper.plugins['Aperture Photometry']._obj uc_plugin = cubeviz_helper.plugins['Unit Conversion'] - subset_plugin = cubeviz_helper.plugins['Subsets'] + subset_plugin = cubeviz_helper.plugins['Subset Tools'] subset_plugin.import_region(CirclePixelRegion(center=PixCoord(x=5, y=5), radius=2), combination_mode='new') diff --git a/jdaviz/configs/imviz/tests/test_subset_centroid.py b/jdaviz/configs/imviz/tests/test_subset_centroid.py index 02da7e54ff..7221986520 100644 --- a/jdaviz/configs/imviz/tests/test_subset_centroid.py +++ b/jdaviz/configs/imviz/tests/test_subset_centroid.py @@ -9,7 +9,7 @@ class TestImvizSpatialSubsetCentroidPixelLinked(BaseImviz_WCS_GWCS): def test_centroiding_pixel(self): reg = CirclePixelRegion(PixCoord(2, 2), 3) - plg = self.imviz.plugins['Subsets']._obj + plg = self.imviz.plugins['Subset Tools']._obj plg.import_region(reg) plg.subset_selected = 'Subset 1' @@ -40,7 +40,7 @@ def test_centroiding_wcs(self): self.imviz.link_data(align_by='wcs') reg = CirclePixelRegion(PixCoord(2, 2), 3).to_sky(self.wcs_1) - plg = self.imviz.plugins['Subsets']._obj + plg = self.imviz.plugins['Subset Tools']._obj plg.import_region(reg) plg.subset_selected = 'Subset 1' diff --git a/jdaviz/configs/imviz/tests/test_viewers.py b/jdaviz/configs/imviz/tests/test_viewers.py index 95ffb19823..c948a96e2c 100644 --- a/jdaviz/configs/imviz/tests/test_viewers.py +++ b/jdaviz/configs/imviz/tests/test_viewers.py @@ -67,7 +67,7 @@ def test_destroy_viewer_with_subset(imviz_helper): # Create a Subset. reg = CirclePixelRegion(center=PixCoord(x=4, y=4), radius=2) - imviz_helper.plugins['Subsets'].import_region(reg) + imviz_helper.plugins['Subset Tools'].import_region(reg) # Remove the second viewer. imviz_helper.destroy_viewer('second') diff --git a/jdaviz/configs/imviz/tests/utils.py b/jdaviz/configs/imviz/tests/utils.py index eb091bc148..0fc99981a4 100644 --- a/jdaviz/configs/imviz/tests/utils.py +++ b/jdaviz/configs/imviz/tests/utils.py @@ -42,7 +42,7 @@ def setup_class(self, imviz_helper): self.wcs = WCS(hdu.header) self.imviz = imviz_helper self.viewer = imviz_helper.default_viewer._obj - self.subset_plugin = self.imviz.plugins['Subsets'] + self.subset_plugin = self.imviz.plugins['Subset Tools'] # Since we are not really displaying, need this to test zoom. self.viewer.shape = (100, 100) diff --git a/jdaviz/configs/mosviz/mosviz.yaml b/jdaviz/configs/mosviz/mosviz.yaml index 220e6d2cc0..ba0344e5ac 100644 --- a/jdaviz/configs/mosviz/mosviz.yaml +++ b/jdaviz/configs/mosviz/mosviz.yaml @@ -18,7 +18,7 @@ toolbar: tray: - g-metadata-viewer - g-plot-options - - g-subset-plugin + - g-subset-tools - g-markers - g-gaussian-smooth - g-slit-overlay diff --git a/jdaviz/configs/rampviz/rampviz.yaml b/jdaviz/configs/rampviz/rampviz.yaml index d961561107..2ba60541fa 100644 --- a/jdaviz/configs/rampviz/rampviz.yaml +++ b/jdaviz/configs/rampviz/rampviz.yaml @@ -21,7 +21,7 @@ tray: - g-metadata-viewer - g-plot-options - g-data-quality - - g-subset-plugin + - g-subset-tools - ramp-extraction - g-markers - cube-slice diff --git a/jdaviz/configs/rampviz/tests/test_ramp_extraction.py b/jdaviz/configs/rampviz/tests/test_ramp_extraction.py index 527d27f854..b77aca9689 100644 --- a/jdaviz/configs/rampviz/tests/test_ramp_extraction.py +++ b/jdaviz/configs/rampviz/tests/test_ramp_extraction.py @@ -18,7 +18,7 @@ def _ramp_extraction_previews(_rampviz_helper, _ramp_file): # add subset: region = CirclePixelRegion(center=PixCoord(12.5, 15.5), radius=2) - _rampviz_helper.plugins['Subsets'].import_region(region) + _rampviz_helper.plugins['Subset Tools'].import_region(region) ramp_extr = _rampviz_helper.plugins['Ramp Extraction']._obj subsets = _rampviz_helper.app.get_subsets() diff --git a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py index f96d12fde1..3dc7e1f059 100644 --- a/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py +++ b/jdaviz/configs/specviz/plugins/line_analysis/tests/test_line_analysis.py @@ -32,8 +32,8 @@ def test_plugin(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7400 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7400 * unit)) specviz_helper.app.state.drawer = True assert 'Subset 1' in plugin.spectral_subset.labels @@ -58,14 +58,14 @@ def test_spatial_subset(cubeviz_helper, image_cube_hdu_obj): cubeviz_helper.load_data(image_cube_hdu_obj, data_label="Test Cube") # add a spatial region - cubeviz_helper.plugins['Subsets'].import_region( + cubeviz_helper.plugins['Subset Tools'].import_region( RectanglePixelRegion(center=PixCoord(x=3, y=5), width=4, height=6)) # create a spectral region unit = u.Unit(cubeviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - cubeviz_helper.plugins['Subsets'].combination_mode = 'new' - cubeviz_helper.plugins['Subsets'].import_region(SpectralRegion(3.623e-7 * unit, - 3.627e-7 * unit)) + cubeviz_helper.plugins['Subset Tools'].combination_mode = 'new' + cubeviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(3.623e-7 * unit, + 3.627e-7 * unit)) cubeviz_helper.app.state.drawer = True plugin = cubeviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -130,8 +130,8 @@ def test_user_api(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, data_label=label) unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7400 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7400 * unit)) la = specviz_helper.plugins['Line Analysis'] la.keep_active = True @@ -247,8 +247,8 @@ def test_continuum_surrounding_spectral_subset(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7400 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7400 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -276,8 +276,8 @@ def test_continuum_spectral_same_value(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7400 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7400 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -305,8 +305,8 @@ def test_continuum_surrounding_invalid_width(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7400 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7400 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -332,8 +332,8 @@ def test_continuum_subset_spectral_entire(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7400 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7400 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -361,13 +361,13 @@ def test_continuum_subset_spectral_subset2(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6200 * unit, - 7000 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6200 * unit, + 7000 * unit)) specviz_helper.app.state.drawer = True - specviz_helper.plugins['Subsets'].combination_mode = 'new' - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(7100 * unit, - 7700 * unit)) + specviz_helper.plugins['Subset Tools'].combination_mode = 'new' + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(7100 * unit, + 7700 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -396,8 +396,8 @@ def test_continuum_surrounding_no_right(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 8000 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 8000 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -426,8 +426,8 @@ def test_continuum_surrounding_no_left(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6000 * unit, - 7500 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6000 * unit, + 7500 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -456,8 +456,8 @@ def test_subset_changed(specviz_helper, spectrum1d): # add a region and rerun stats for that region unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6000 * unit, - 7500 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6000 * unit, + 7500 * unit)) specviz_helper.app.state.drawer = True plugin = specviz_helper.app.get_tray_item_from_name('specviz-line-analysis') @@ -467,8 +467,8 @@ def test_subset_changed(specviz_helper, spectrum1d): plugin.continuum_subset_selected = 'Surrounding' plugin.width = 3 - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(6500 * unit, - 7500 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(6500 * unit, + 7500 * unit)) specviz_helper.app.state.drawer = True # Values have not yet been validated @@ -488,8 +488,8 @@ def test_invalid_subset(specviz_helper, spectrum1d): # NOTE: using a subset that overlaps the right_spectrum (reference) results in errors when # retrieving the subset (https://github.com/spacetelescope/jdaviz/issues/1868) unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) - specviz_helper.plugins['Subsets'].import_region(SpectralRegion(5000 * unit, - 6000 * unit)) + specviz_helper.plugins['Subset Tools'].import_region(SpectralRegion(5000 * unit, + 6000 * unit)) plugin = specviz_helper.plugins['Line Analysis'] plugin.dataset = 'right_spectrum' diff --git a/jdaviz/configs/specviz/specviz.yaml b/jdaviz/configs/specviz/specviz.yaml index 4eff5edf94..cd077562b8 100644 --- a/jdaviz/configs/specviz/specviz.yaml +++ b/jdaviz/configs/specviz/specviz.yaml @@ -19,7 +19,7 @@ toolbar: tray: - g-metadata-viewer - g-plot-options - - g-subset-plugin + - g-subset-tools - g-markers - g-gaussian-smooth - g-model-fitting diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py index 1c85ac532b..a3d943e430 100644 --- a/jdaviz/configs/specviz/tests/test_helper.py +++ b/jdaviz/configs/specviz/tests/test_helper.py @@ -122,7 +122,7 @@ def test_get_spectral_regions_none(self): assert spec_region == {} def test_get_spectral_regions_one(self): - self.spec_app.plugins['Subsets'].import_region( + self.spec_app.plugins['Subset Tools'].import_region( SpectralRegion(6000*self.spec.spectral_axis.unit, 6500*self.spec.spectral_axis.unit)) spec_region = self.spec_app.get_spectral_regions() assert len(spec_region['Subset 1'].subregions) == 1 @@ -132,7 +132,7 @@ def test_get_spectral_regions_two(self): 6500*self.spec.spectral_axis.unit) + SpectralRegion(7300*self.spec.spectral_axis.unit, 7800*self.spec.spectral_axis.unit)) - self.spec_app.plugins['Subsets'].import_region(subset, combination_mode='or') + self.spec_app.plugins['Subset Tools'].import_region(subset, combination_mode='or') spec_region = self.spec_app.get_spectral_regions() @@ -145,7 +145,7 @@ def test_get_spectral_regions_three(self): 7000*self.spec.spectral_axis.unit) + SpectralRegion(7300*self.spec.spectral_axis.unit, 7800*self.spec.spectral_axis.unit)) - self.spec_app.plugins['Subsets'].import_region(subset, combination_mode='or') + self.spec_app.plugins['Subset Tools'].import_region(subset, combination_mode='or') spec_region = self.spec_app.get_spectral_regions() @@ -171,7 +171,7 @@ def test_get_spectral_regions_does_not_raise_value_error(self): 3*self.spec.spectral_axis.unit) + SpectralRegion(4*self.spec.spectral_axis.unit, 6*self.spec.spectral_axis.unit)) - self.spec_app.plugins['Subsets'].import_region(subset, combination_mode='or') + self.spec_app.plugins['Subset Tools'].import_region(subset, combination_mode='or') spec_region = self.spec_app.get_spectral_regions() assert_quantity_allclose(spec_region['Subset 1'].subregions[0][0].value, @@ -191,7 +191,7 @@ def test_get_spectral_regions_composite_region(self): 7000*self.spec.spectral_axis.unit) + SpectralRegion(7300*self.spec.spectral_axis.unit, 7800*self.spec.spectral_axis.unit)) - self.spec_app.plugins['Subsets'].import_region( + self.spec_app.plugins['Subset Tools'].import_region( subset, combination_mode=['new', 'andnot', 'and']) spec_region = self.spec_app.get_spectral_regions() @@ -210,7 +210,7 @@ def test_get_spectral_regions_composite_region_multiple_and_nots(self): 6600*self.spec.spectral_axis.unit) + SpectralRegion(7300*self.spec.spectral_axis.unit, 7700*self.spec.spectral_axis.unit)) - self.spec_app.plugins['Subsets'].import_region( + self.spec_app.plugins['Subset Tools'].import_region( subset, combination_mode=['new', 'andnot', 'andnot']) spec_region = self.spec_app.get_spectral_regions() @@ -261,7 +261,7 @@ def test_add_spectrum_after_subset(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, data_label="test") subset = SpectralRegion(6200 * spectrum1d.spectral_axis.unit, 7000 * spectrum1d.spectral_axis.unit) - specviz_helper.plugins['Subsets'].import_region(subset) + specviz_helper.plugins['Subset Tools'].import_region(subset) new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9 specviz_helper.load_data(new_spec, data_label="test2") @@ -272,7 +272,7 @@ def test_get_spectral_regions_unit(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) subset = SpectralRegion(6200 * spectrum1d.spectral_axis.unit, 7000 * spectrum1d.spectral_axis.unit) - specviz_helper.plugins['Subsets'].import_region(subset) + specviz_helper.plugins['Subset Tools'].import_region(subset) subsets = specviz_helper.get_spectral_regions() reg = subsets.get('Subset 1') @@ -314,7 +314,7 @@ def test_get_spectral_regions_unit_conversion(specviz_helper, spectrum1d): specviz_helper.plugins['Unit Conversion'].spectral_unit = new_spectral_axis spectral_axis_unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) subset = SpectralRegion(0.6 * spectral_axis_unit, 0.7 * spectral_axis_unit) - specviz_helper.plugins['Subsets'].import_region(subset) + specviz_helper.plugins['Subset Tools'].import_region(subset) # Retrieve the Subset subsets = specviz_helper.get_spectral_regions(use_display_units=False) @@ -349,7 +349,7 @@ def test_subset_default_thickness(specviz_helper, spectrum1d): spectral_axis_unit = u.Unit(specviz_helper.plugins['Unit Conversion'].spectral_unit.selected) subset = SpectralRegion(2.5 * spectral_axis_unit, 3.5 * spectral_axis_unit) - specviz_helper.plugins['Subsets'].import_region(subset) + specviz_helper.plugins['Subset Tools'].import_region(subset) # _on_layers_update is not triggered within CI sv._on_layers_update() assert sv.state.layers[-1].linewidth == 3 @@ -513,7 +513,7 @@ def test_delete_data_with_subsets(specviz_helper, spectrum1d, spectrum1d_nm): subset = SpectralRegion(6200 * spectral_axis_unit, 7000 * spectral_axis_unit) - specviz_helper.plugins['Subsets'].import_region(subset) + specviz_helper.plugins['Subset Tools'].import_region(subset) assert len(specviz_helper.app.data_collection.subset_groups) == 1 subset1 = specviz_helper.app.data_collection.subset_groups[0] diff --git a/jdaviz/configs/specviz2d/specviz2d.yaml b/jdaviz/configs/specviz2d/specviz2d.yaml index ec7b36c212..056a2d3c51 100644 --- a/jdaviz/configs/specviz2d/specviz2d.yaml +++ b/jdaviz/configs/specviz2d/specviz2d.yaml @@ -17,7 +17,7 @@ toolbar: tray: - g-metadata-viewer - g-plot-options - - g-subset-plugin + - g-subset-tools - g-markers - spectral-extraction - g-gaussian-smooth diff --git a/jdaviz/core/tests/test_data_menu.py b/jdaviz/core/tests/test_data_menu.py index 5ce1bb1a02..136cb25168 100644 --- a/jdaviz/core/tests/test_data_menu.py +++ b/jdaviz/core/tests/test_data_menu.py @@ -54,7 +54,7 @@ def test_data_menu_toggles(specviz_helper, spectrum1d): assert sv.layers[1].visible is False # add a subset and make sure it appears for the first data entry but not the second - specviz_helper.plugins['Subsets'].import_region( + specviz_helper.plugins['Subset Tools'].import_region( SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 6500 * spectrum1d.spectral_axis.unit)) assert len(sv.layers) == 4 diff --git a/jdaviz/core/tests/test_helpers.py b/jdaviz/core/tests/test_helpers.py index 8e33b7411e..e49e277a6b 100644 --- a/jdaviz/core/tests/test_helpers.py +++ b/jdaviz/core/tests/test_helpers.py @@ -42,12 +42,12 @@ def setup_class(self, specviz_helper, spectrum1d, multi_order_spectrum_list): self.spec_app.load_data(self.spec2, data_label=self.label2) # Add 3 subsets to cover different parts of spec and spec2 - self.spec_app.plugins['Subsets'].import_region( + self.spec_app.plugins['Subset Tools'].import_region( SpectralRegion(6000*spectral_axis_unit, 6500*spectral_axis_unit)) - self.spec_app.plugins['Subsets'].import_region( + self.spec_app.plugins['Subset Tools'].import_region( SpectralRegion(6700*spectral_axis_unit, 7200*spectral_axis_unit), combination_mode='new') - self.spec_app.plugins['Subsets'].import_region( + self.spec_app.plugins['Subset Tools'].import_region( SpectralRegion(8200*spectral_axis_unit, 8800*spectral_axis_unit), combination_mode='new') diff --git a/jdaviz/core/tests/test_template_mixin.py b/jdaviz/core/tests/test_template_mixin.py index d92b723648..95aa15eed8 100644 --- a/jdaviz/core/tests/test_template_mixin.py +++ b/jdaviz/core/tests/test_template_mixin.py @@ -12,7 +12,7 @@ def test_spectralsubsetselect(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) sv = specviz_helper.app.get_viewer('spectrum-viewer') # create a "Subset 1" entry - subset_plugin = specviz_helper.plugins['Subsets'] + subset_plugin = specviz_helper.plugins['Subset Tools'] subset_plugin.import_region(SpectralRegion(6500 * spectrum1d.spectral_axis.unit, 7400 * spectrum1d.spectral_axis.unit)) diff --git a/jdaviz/core/tools.py b/jdaviz/core/tools.py index e6d57c5c00..71e0b9f53e 100644 --- a/jdaviz/core/tools.py +++ b/jdaviz/core/tools.py @@ -555,10 +555,10 @@ def on_mouse_event(self, data): if data['altKey'] is True: reg = self.get_subset(x, y, as_roi=False) - self.viewer.jdaviz_app.get_tray_item_from_name('g-subset-plugin').combination_mode.selected = 'new' # noqa - self.viewer.jdaviz_app.get_tray_item_from_name('g-subset-plugin').import_region( + self.viewer.jdaviz_app.get_tray_item_from_name('g-subset-tools').combination_mode.selected = 'new' # noqa + self.viewer.jdaviz_app.get_tray_item_from_name('g-subset-tools').import_region( reg, return_bad_regions=True) - self.viewer.jdaviz_app.get_tray_item_from_name('g-subset-plugin').combination_mode.selected = 'replace' # noqa + self.viewer.jdaviz_app.get_tray_item_from_name('g-subset-tools').combination_mode.selected = 'replace' # noqa else: roi = self.get_subset(x, y, as_roi=True) diff --git a/jdaviz/tests/test_app.py b/jdaviz/tests/test_app.py index 1c8cc5be03..adb3e4c2cc 100644 --- a/jdaviz/tests/test_app.py +++ b/jdaviz/tests/test_app.py @@ -35,7 +35,7 @@ def test_nonstandard_specviz_viewer_name(spectrum1d): 'toolbar': ['g-data-tools', 'g-subset-tools'], 'tray': ['g-metadata-viewer', 'g-plot-options', - 'g-subset-plugin', + 'g-subset-tools', 'g-gaussian-smooth', 'g-model-fitting', 'g-unit-conversion', diff --git a/jdaviz/tests/test_subsets.py b/jdaviz/tests/test_subsets.py index 135878ade3..d0afb98f46 100644 --- a/jdaviz/tests/test_subsets.py +++ b/jdaviz/tests/test_subsets.py @@ -17,7 +17,7 @@ def test_region_from_subset_2d(cubeviz_helper): cubeviz_helper.load_data(np.ones((128, 128, 1)), data_label='Test 2D Flux') - subset_plugin = cubeviz_helper.plugins['Subsets'] + subset_plugin = cubeviz_helper.plugins['Subset Tools'] cubeviz_helper.app.add_data_to_viewer('flux-viewer', 'Test 2D Flux') @@ -58,7 +58,7 @@ def test_region_from_subset_2d(cubeviz_helper): def test_region_from_subset_3d(cubeviz_helper): cubeviz_helper.load_data(np.ones((128, 128, 256)), data_label='Test 3D Flux') - subset_plugin = cubeviz_helper.plugins['Subsets'] + subset_plugin = cubeviz_helper.plugins['Subset Tools'] assert subset_plugin._obj.subset_selected == "Create New" cubeviz_helper.app.add_data_to_viewer('flux-viewer', 'Test 3D Flux') @@ -157,7 +157,7 @@ def test_region_from_subset_3d(cubeviz_helper): def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) - subset_plugin = cubeviz_helper.plugins['Subsets']._obj + subset_plugin = cubeviz_helper.plugins['Subset Tools']._obj cubeviz_helper.load_data(data, data_label='Test 1D Flux') @@ -204,7 +204,7 @@ def test_region_from_subset_profile(cubeviz_helper, spectral_cube_wcs): def test_disjoint_spectral_subset(cubeviz_helper, spectral_cube_wcs): - subset_plugin = cubeviz_helper.plugins['Subsets']._obj + subset_plugin = cubeviz_helper.plugins['Subset Tools']._obj data = Spectrum1D(flux=np.ones((128, 128, 256)) * u.nJy, wcs=spectral_cube_wcs) cubeviz_helper.load_data(data, data_label="Test Flux") @@ -252,7 +252,7 @@ def test_composite_region_from_subset_3d(cubeviz_helper): cubeviz_helper.app.add_data_to_viewer('flux-viewer', 'Test 3D Flux') - subset_plugin = cubeviz_helper.plugins['Subsets'] + subset_plugin = cubeviz_helper.plugins['Subset Tools'] subset_plugin.import_region(CircularROI(xc=25, yc=25, radius=5)) reg = cubeviz_helper.app.get_subsets("Subset 1") @@ -295,7 +295,7 @@ def test_composite_region_from_subset_3d(cubeviz_helper): assert reg[-1] == {'name': 'CircularROI', 'glue_state': 'AndNotState', 'region': circle2, 'sky_region': None, 'subset_state': reg[-1]['subset_state']} - subset_plugin = cubeviz_helper.app.get_tray_item_from_name('g-subset-plugin') + subset_plugin = cubeviz_helper.app.get_tray_item_from_name('g-subset-tools') assert subset_plugin.subset_selected == "Subset 1" assert subset_plugin.subset_types == ['CircularROI', 'RectangularROI', 'EllipticalROI', 'RectangularROI', 'CircularROI'] @@ -305,7 +305,7 @@ def test_composite_region_from_subset_3d(cubeviz_helper): def test_composite_region_with_consecutive_and_not_states(cubeviz_helper): cubeviz_helper.load_data(np.ones((128, 128, 10)), data_label='Test 3D Flux') - subset_plugin = cubeviz_helper.plugins['Subsets']._obj + subset_plugin = cubeviz_helper.plugins['Subset Tools']._obj cubeviz_helper.app.add_data_to_viewer('flux-viewer', 'Test 3D Flux') viewer = cubeviz_helper.app.get_viewer('flux-viewer') @@ -371,7 +371,7 @@ def test_composite_region_with_consecutive_and_not_states(cubeviz_helper): def test_composite_region_with_imviz(imviz_helper, image_2d_wcs): arr = NDData(np.ones((10, 10)), wcs=image_2d_wcs) - subset_plugin = imviz_helper.plugins['Subsets']._obj + subset_plugin = imviz_helper.plugins['Subset Tools']._obj data_label = 'image-data' imviz_helper.load_data(arr, data_label=data_label, show_in_viewer=True) subset_plugin.import_region(CircularROI(xc=5, yc=5, radius=2)) @@ -426,10 +426,10 @@ def test_recenter_linked_by_wcs(imviz_helper): # This rectangle is over a real object in reference image but # only the last row in the second image if linked by pixel. - imviz_helper.plugins['Subsets'].import_region( + imviz_helper.plugins['Subset Tools'].import_region( RectanglePixelRegion(center=PixCoord(x=229, y=152), width=17, height=7).to_sky(w)) - subset_plugin = imviz_helper.plugins["Subsets"] + subset_plugin = imviz_helper.plugins['Subset Tools'] subset_plugin._obj.subset_selected = "Subset 1" subset_plugin._obj.dataset_selected = "gauss100_fits_wcs_block_reduced[PRIMARY,1]" @@ -474,7 +474,7 @@ def test_with_invalid_subset_name(cubeviz_helper): def test_composite_region_from_subset_2d(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) viewer = specviz_helper.app.get_viewer(specviz_helper._default_spectrum_viewer_reference_name) - subset_plugin = specviz_helper.plugins['Subsets']._obj + subset_plugin = specviz_helper.plugins['Subset Tools']._obj subset1 = SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 7000 * spectrum1d.spectral_axis.unit) @@ -527,7 +527,7 @@ def test_composite_region_from_subset_2d(specviz_helper, spectrum1d): def test_edit_composite_spectral_subset(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - subset_plugin = specviz_helper.plugins['Subsets']._obj + subset_plugin = specviz_helper.plugins['Subset Tools']._obj unit = spectrum1d.spectral_axis.unit subset = [SpectralRegion(6200 * unit, 6800 * unit), @@ -576,7 +576,7 @@ def test_edit_composite_spectral_subset(specviz_helper, spectrum1d): def test_composite_spectral_with_xor(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - subset_plugin = specviz_helper.plugins['Subsets'] + subset_plugin = specviz_helper.plugins['Subset Tools'] unit = spectrum1d.spectral_axis.unit subset = [SpectralRegion(6200 * unit, 6800 * unit), @@ -635,7 +635,7 @@ def test_composite_spectral_with_xor(specviz_helper, spectrum1d): def test_composite_spectral_with_xor_complicated(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - subset_plugin = specviz_helper.plugins['Subsets'] + subset_plugin = specviz_helper.plugins['Subset Tools'] unit = spectrum1d.spectral_axis.unit subset = [SpectralRegion(6100 * unit, 6700 * unit), @@ -665,7 +665,7 @@ def test_composite_spectral_with_xor_complicated(specviz_helper, spectrum1d): def test_overlapping_spectral_regions(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - subset_plugin = specviz_helper.plugins['Subsets']._obj + subset_plugin = specviz_helper.plugins['Subset Tools']._obj unit = spectrum1d.spectral_axis.unit subset = [SpectralRegion(6400 * unit, 7400 * unit), SpectralRegion(6600 * unit, 7200 * unit), @@ -682,7 +682,7 @@ def test_overlapping_spectral_regions(specviz_helper, spectrum1d): def test_only_overlapping_spectral_regions(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - subset_plugin = specviz_helper.plugins['Subsets']._obj + subset_plugin = specviz_helper.plugins['Subset Tools']._obj unit = spectrum1d.spectral_axis.unit subset_plugin.import_region(SpectralRegion(6400 * unit, 6600 * unit)) @@ -704,7 +704,7 @@ def test_only_overlapping_spectral_regions(specviz_helper, spectrum1d): def test_overlapping_in_specviz2d(specviz2d_helper, mos_spectrum2d): specviz2d_helper.load_data(spectrum_2d=mos_spectrum2d) - subset_plugin = specviz2d_helper.plugins['Subsets']._obj + subset_plugin = specviz2d_helper.plugins['Subset Tools']._obj unit = mos_spectrum2d.spectral_axis.unit subset_plugin.import_region(SpectralRegion(6400 * unit, 7400 * unit)) @@ -723,7 +723,7 @@ def test_overlapping_in_specviz2d(specviz2d_helper, mos_spectrum2d): def test_only_overlapping_in_specviz2d(specviz2d_helper, mos_spectrum2d): specviz2d_helper.load_data(spectrum_2d=mos_spectrum2d) - subset_plugin = specviz2d_helper.plugins['Subsets']._obj + subset_plugin = specviz2d_helper.plugins['Subset Tools']._obj unit = mos_spectrum2d.spectral_axis.unit subset_plugin.import_region(SpectralRegion(6400 * unit, 6600 * unit)) @@ -742,7 +742,7 @@ def test_only_overlapping_in_specviz2d(specviz2d_helper, mos_spectrum2d): def test_multi_mask_subset(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d) - subset_plugin = specviz_helper.plugins['Subsets']._obj + subset_plugin = specviz_helper.plugins['Subset Tools']._obj unit = spectrum1d.spectral_axis.unit subset_plugin.import_region(SpectralRegion(6200 * unit, 6800 * unit)) @@ -797,7 +797,7 @@ def test_delete_subsets(cubeviz_helper, spectral_cube_wcs): spectrum_viewer = cubeviz_helper.app.get_viewer("spectrum-viewer") - subset_plugin = cubeviz_helper.plugins['Subsets'] + subset_plugin = cubeviz_helper.plugins['Subset Tools'] unit = u.Unit(cubeviz_helper.plugins['Unit Conversion'].spectral_unit.selected) subset_plugin.import_region(SpectralRegion(6200 * unit, 6800 * unit)) @@ -825,7 +825,7 @@ def test_get_regions_from_subsets_cubeviz(self, cubeviz_helper, spectral_cube_wc cubeviz_helper.load_data(data) # basic test, a single circular region - subset_plugin = cubeviz_helper.plugins['Subsets'] + subset_plugin = cubeviz_helper.plugins['Subset Tools'] subset_plugin.import_region(CircularROI(25, 25, 10)) subsets = cubeviz_helper.app.get_subsets(include_sky_region=True) sky_region = subsets['Subset 1'][0]['sky_region'] @@ -870,7 +870,7 @@ def test_get_regions_from_subsets_imviz(self, imviz_helper, spectral_cube_wcs): imviz_helper.load_data(data) # basic test, a single circular region - subset_plugin = imviz_helper.plugins['Subsets'] + subset_plugin = imviz_helper.plugins['Subset Tools'] subset_plugin.import_region(CircularROI(25, 25, 10)) subsets = imviz_helper.app.get_subsets(include_sky_region=True) sky_region = subsets['Subset 1'][0]['sky_region'] @@ -902,7 +902,7 @@ def test_no_wcs_sky_regions(self, imviz_helper): data = NDData(np.ones((40, 40)) * u.nJy) imviz_helper.load_data(data) - subset_plugin = imviz_helper.plugins['Subsets'] + subset_plugin = imviz_helper.plugins['Subset Tools'] subset_plugin.import_region(CircularROI(25, 25, 10)) subsets = imviz_helper.app.get_subsets(include_sky_region=True) assert subsets['Subset 1'][0]['sky_region'] is None @@ -910,7 +910,7 @@ def test_no_wcs_sky_regions(self, imviz_helper): def test_subset_renaming(self, specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, 'myfile') - subset_plugin = specviz_helper.plugins['Subsets'] + subset_plugin = specviz_helper.plugins['Subset Tools'] subset_plugin.import_region(SpectralRegion(6200 * spectrum1d.spectral_axis.unit, 7200 * spectrum1d.spectral_axis.unit)) get_data_no_sub = specviz_helper.get_data('myfile') diff --git a/notebooks/CubevizExample.ipynb b/notebooks/CubevizExample.ipynb index 2bf6cd4a38..17a773d237 100644 --- a/notebooks/CubevizExample.ipynb +++ b/notebooks/CubevizExample.ipynb @@ -164,7 +164,7 @@ "my_reg = CirclePixelRegion(center=PixCoord(x=35, y=35), radius=10)\n", "\n", "my_regions = [my_aper, my_reg]\n", - "cubeviz.plugins['Subsets'].import_region(my_regions, combination_mode='new')" + "cubeviz.plugins['Subset Tools'].import_region(my_regions, combination_mode='new')" ] }, { @@ -211,7 +211,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "jdaviz1", "language": "python", "name": "python3" }, @@ -225,7 +225,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.11.6" } }, "nbformat": 4, diff --git a/notebooks/ImvizDitheredExample.ipynb b/notebooks/ImvizDitheredExample.ipynb index c89d59d3cd..2093167554 100644 --- a/notebooks/ImvizDitheredExample.ipynb +++ b/notebooks/ImvizDitheredExample.ipynb @@ -339,7 +339,7 @@ "my_reg_sky = CircleSkyRegion(c, Angle(2, u.arcsec))\n", "\n", "my_regions = [my_aper, my_aper_sky, my_reg, my_reg_sky]\n", - "imviz.plugins['Subsets'].import_region(my_regions, combination_mode='new')" + "imviz.plugins['Subset Tools'].import_region(my_regions, combination_mode='new')" ] }, { diff --git a/notebooks/ImvizExample.ipynb b/notebooks/ImvizExample.ipynb index 79df68f31e..40653569ea 100644 --- a/notebooks/ImvizExample.ipynb +++ b/notebooks/ImvizExample.ipynb @@ -375,7 +375,7 @@ "my_reg_sky = CircleSkyRegion(c, Angle(2, u.arcsec))\n", "\n", "my_regions = [my_aper, my_aper_sky, my_reg, my_reg_sky]\n", - "imviz.plugins['Subsets'].import_region(my_regions, combination_mode='new')" + "imviz.plugins['Subset Tools'].import_region(my_regions, combination_mode='new')" ] }, { diff --git a/notebooks/concepts/RampvizExample.ipynb b/notebooks/concepts/RampvizExample.ipynb index 62297fc195..642c7e4473 100644 --- a/notebooks/concepts/RampvizExample.ipynb +++ b/notebooks/concepts/RampvizExample.ipynb @@ -101,7 +101,7 @@ "from regions import CirclePixelRegion, PixCoord\n", "\n", "region = CirclePixelRegion(center=PixCoord(1797.2, 2051.2), radius=2)\n", - "rampviz.plugins['Subsets'].import_region(region)" + "rampviz.plugins['Subset Tools'].import_region(region)" ] }, { diff --git a/notebooks/concepts/cubeviz_aperture_photometry.ipynb b/notebooks/concepts/cubeviz_aperture_photometry.ipynb index 8f188b2dd6..50b08d0bf0 100644 --- a/notebooks/concepts/cubeviz_aperture_photometry.ipynb +++ b/notebooks/concepts/cubeviz_aperture_photometry.ipynb @@ -141,7 +141,7 @@ "metadata": {}, "outputs": [], "source": [ - "cubeviz.plugins['Subsets'].import_region(aper)" + "cubeviz.plugins['Subset Tools'].import_region(aper)" ] }, { @@ -525,7 +525,7 @@ "source": [ "aper2 = RectanglePixelRegion(center=PixCoord(x=1, y=3), width=1, height=1)\n", "bg2 = RectanglePixelRegion(center=PixCoord(x=0, y=2), width=1, height=1)\n", - "cubeviz2.plugins['Subsets'].import_region([aper2, bg2])" + "cubeviz2.plugins['Subset Tools'].import_region([aper2, bg2])" ] }, { diff --git a/notebooks/concepts/imviz_advanced_aper_phot.ipynb b/notebooks/concepts/imviz_advanced_aper_phot.ipynb index 789f7869a8..7f9bfb1496 100644 --- a/notebooks/concepts/imviz_advanced_aper_phot.ipynb +++ b/notebooks/concepts/imviz_advanced_aper_phot.ipynb @@ -108,7 +108,7 @@ "metadata": {}, "outputs": [], "source": [ - "imviz.plugins['Subsets'].import_region(regions, combination_mode='new')" + "imviz.plugins['Subset Tools'].import_region(regions, combination_mode='new')" ] }, { diff --git a/notebooks/concepts/imviz_colorbar_mpl.ipynb b/notebooks/concepts/imviz_colorbar_mpl.ipynb index 08c283ba29..69ba22d558 100644 --- a/notebooks/concepts/imviz_colorbar_mpl.ipynb +++ b/notebooks/concepts/imviz_colorbar_mpl.ipynb @@ -284,7 +284,7 @@ "metadata": {}, "outputs": [], "source": [ - "imviz.plugins['Subsets'].import_region(reg)" + "imviz.plugins['Subset Tools'].import_region(reg)" ] }, { diff --git a/notebooks/concepts/imviz_edit_subset_programmatic.ipynb b/notebooks/concepts/imviz_edit_subset_programmatic.ipynb index d6fa3198b9..fc44f7ce41 100644 --- a/notebooks/concepts/imviz_edit_subset_programmatic.ipynb +++ b/notebooks/concepts/imviz_edit_subset_programmatic.ipynb @@ -72,7 +72,7 @@ "metadata": {}, "outputs": [], "source": [ - "subset_tool = imviz.plugins['Subsets']" + "subset_tool = imviz.plugins['Subset Tools']" ] }, { diff --git a/notebooks/concepts/imviz_simple_aper_phot.ipynb b/notebooks/concepts/imviz_simple_aper_phot.ipynb index e5e4c52289..254186301e 100644 --- a/notebooks/concepts/imviz_simple_aper_phot.ipynb +++ b/notebooks/concepts/imviz_simple_aper_phot.ipynb @@ -157,7 +157,7 @@ "viewer.zoom_level = my_zoom\n", "\n", "# Click on image to finalize selection.\n", - "imviz.plugins['Subsets'].import_region([my_aper, my_bg], combination_mode='new')" + "imviz.plugins['Subset Tools'].import_region([my_aper, my_bg], combination_mode='new')" ] }, {