diff --git a/CHANGES.rst b/CHANGES.rst index f12d30111d..dcd5e31261 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,6 +6,8 @@ New Features - Added API for renaming subsets to Subset Tools plugin. [#3356] +- Viewer data-menus are now found in the legend on the right of the viewer. [#3281] + Cubeviz ^^^^^^^ diff --git a/docs/imviz/displayimages.rst b/docs/imviz/displayimages.rst index 801cac130a..90b8eec6b4 100644 --- a/docs/imviz/displayimages.rst +++ b/docs/imviz/displayimages.rst @@ -18,24 +18,18 @@ Right-click will open a dropdown with access to different options for each butto Selecting a Data Set ==================== -Data can be selected and de-selected in each viewer's data menu, opened by clicking the -|icon-viewer-data-select| button in the top left of the viewer. Here, you can click a -checkbox to the left of the listed data to make the data visible (checked) or invisible -(unchecked). The datasets available in each viewer are filtered +Data can be selected and de-selected in each viewer's data menu, opened by clicking on +the legend in the top right of the viewer. Here, all the data and subset layers are listed +and their visibility can be toggled with the "eye" icon on the right. + +To add an additional data layer or interactively create a new subset, click the "+" icon in +the top right of the data menu. The datasets available in each viewer are filtered to include only compatible data, so you may not see all loaded data in the menu for every viewer. For example, 1D spectra will not be available in the image viewers. -In addition to selecting and de-selecting data to toggle its visibility in the viewer, you -can also unload the data from the viewer completely by clicking the ``X`` to the right of the -data label. Any data that still exists in Imviz but has been unloaded from the viewer -is listed in a separate section that is hidden by default but can can be expanded by clicking -on the section header: - -.. image:: img/imviz_removed_data.png - -This section can be hidden by clicking the section header again. Unloaded data will be available -to re-load into the viewer (by clicking the ``+`` icon) or remove permanently from the app (by -clicking the trashcan icon). +Clicking on the rows in the data menu selects entries for further actions available in the +bottom bar of the data menu. Here you can modify selected subsets, view metadata or subset +details, or remove layers from either the viewer or the entire application instance. .. warning:: Deleting the first image that was loaded into Imviz may be slow, as deleting this image diff --git a/docs/imviz/img/imviz_removed_data.png b/docs/imviz/img/imviz_removed_data.png deleted file mode 100644 index c799ae631e..0000000000 Binary files a/docs/imviz/img/imviz_removed_data.png and /dev/null differ diff --git a/docs/specviz/displaying.rst b/docs/specviz/displaying.rst index 8bbf0bdaca..6e1f586256 100644 --- a/docs/specviz/displaying.rst +++ b/docs/specviz/displaying.rst @@ -29,19 +29,16 @@ for each button. Selecting/Showing Data Sets =========================== -Data can be selected and de-selected in each viewer's data menu, opened by clicking the -|icon-viewer-data-select| button in the top left of the viewer. Here, you can click a -checkbox next to the listed data to make the data visible (checked) or invisible (unchecked). +Data layers can be toggled in each viewer's data menu, opened by clicking the +legend in the top right of the viewer. Here, you can click the "eye" icon +next to the listed data to toggle that layer's visibility. -.. image:: img/data_tab.png - -In addition to toggling the visibility of a data layer, the data can be unloaded from a viewer -by clicking the ``X`` button on the right. Data unloaded from the viewer will also be excluded +Data can be unloaded from a viewer by clicking on the data row +and selecting "Remove from viewer" in the delete submenu on the +bottom bar of the data menu. Data unloaded from the viewer will also be excluded as options from dataset dropdown menus in the various plugins. Unloaded data will be available -to re-load into the viewer (by clicking the ``+`` icon) or remove permanently from the app (by -clicking the trashcan icon) from an expandable section in the data menu: +to re-load into the viewer (by clicking the ``+`` icon in the top right of the data menu). -.. image:: img/specviz_remove_data.png .. _specviz_cursor_info: diff --git a/docs/specviz/img/data_tab.png b/docs/specviz/img/data_tab.png deleted file mode 100644 index 7c6b09a0e5..0000000000 Binary files a/docs/specviz/img/data_tab.png and /dev/null differ diff --git a/docs/specviz/img/specviz_remove_data.png b/docs/specviz/img/specviz_remove_data.png deleted file mode 100644 index e1bc57d3b6..0000000000 Binary files a/docs/specviz/img/specviz_remove_data.png and /dev/null differ diff --git a/docs/specviz2d/displaying.rst b/docs/specviz2d/displaying.rst index 82d7cb080f..e802c1a289 100644 --- a/docs/specviz2d/displaying.rst +++ b/docs/specviz2d/displaying.rst @@ -5,7 +5,7 @@ Displaying Spectra ****************** Specviz2D consists of a 2D spectrum viewer and a 1D spectrum viewer, with linked x-axes. Each -viewer window contains a toolbar, including a data menu. +viewer window contains a toolbar, legend, and data menu. .. seealso:: diff --git a/jdaviz/app.py b/jdaviz/app.py index ffbd64a19d..2b92c1747a 100644 --- a/jdaviz/app.py +++ b/jdaviz/app.py @@ -124,8 +124,6 @@ def to_unit(self, data, cid, values, original_units, target_units): custom_components = {'j-tooltip': 'components/tooltip.vue', 'j-external-link': 'components/external_link.vue', 'j-docs-link': 'components/docs_link.vue', - 'j-viewer-data-select': 'components/viewer_data_select.vue', - 'j-viewer-data-select-item': 'components/viewer_data_select_item.vue', 'j-layer-viewer-icon': 'components/layer_viewer_icon.vue', 'j-layer-viewer-icon-stylized': 'components/layer_viewer_icon_stylized.vue', 'j-tray-plugin': 'components/tray_plugin.vue', @@ -446,15 +444,13 @@ def _update_live_plugin_results(self, trigger_data_lbl=None, trigger_subset=None self.hub.broadcast(SnackbarMessage( f"Auto-update for {plugin_inputs['add_results']['label']} failed: {e}", sender=self, color="error")) - # TODO: should we delete the entry (but then any plot options, etc, are lost) - # self.vue_data_item_remove({'item_name': data.label}) def _remove_live_plugin_results(self, trigger_data_lbl=None, trigger_subset=None): for data, plugin_inputs in self._iter_live_plugin_results(trigger_data_lbl, trigger_subset): self.hub.broadcast(SnackbarMessage( f"Removing {data.label} due to deletion of {trigger_subset.label if trigger_subset is not None else trigger_data_lbl}", # noqa sender=self, color="warning")) - self.vue_data_item_remove({'item_name': data.label}) + self.data_item_remove(data.label) def _on_add_data_message(self, msg): self._on_layers_changed(msg) @@ -1702,12 +1698,6 @@ def remove_data_from_viewer(self, viewer_reference, data_label): sender=self) self.hub.broadcast(remove_data_message) - # update data menu entry - selected_items = viewer_item['selected_data_items'] - data_id = self._data_id_from_label(data_label) - if data_id in selected_items: - _ = selected_items.pop(data_id) - def _data_id_from_label(self, label): """ Retrieve the data item given the Glue ``DataCollection`` data label. @@ -2267,19 +2257,6 @@ def remove(stack_items): self.hub.broadcast(ViewerRemovedMessage(cid, sender=self)) - def vue_data_item_unload(self, event): - """ - Callback for selection events in the front-end data list when clicking to unload an entry - from the viewer. - """ - data_label = self._get_data_item_by_id(event['item_id'])['name'] - self.remove_data_from_viewer(event['id'], data_label) - - def vue_data_item_visibility(self, event): - self.set_data_visibility(event['id'], - self._get_data_item_by_id(event['item_id'])['name'], - visible=event['visible'], replace=event.get('replace', False)) - def vue_change_reference_data(self, event): self._change_reference_data( self._get_data_item_by_id(event['item_id'])['name'], @@ -2367,7 +2344,7 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac # if Data has children, update their visibilities to match Data: available_plugins = [tray_item['name'] for tray_item in self.state.tray_items] for child in assoc_children: - if child not in viewer.data_labels_loaded: + if child not in viewer.data_menu.data_labels_loaded: self.add_data_to_viewer(viewer.reference, child, visible=visible) if 'g-data-quality' in available_plugins and visible: @@ -2389,29 +2366,6 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac else: layer.visible = visible - # update data menu - selected_data_items should be READ ONLY, not modified by the user/UI. - # must update the visibility of `data_label` and its children: - selected_items = viewer_item['selected_data_items'] - update_data_labels = [data_label] + assoc_children - for update_data_label in update_data_labels: - data_id = self._data_id_from_label(update_data_label) - - if replace and update_data_label == data_label: - for id in selected_items: - if id != data_id: - selected_items[id] = 'hidden' - - selected_items[data_id] = 'visible' if visible else 'hidden' - - # remove WCS-only data from selected items, add to wcs_only_layers: - for layer in viewer.layers: - layer_is_wcs_only = getattr(layer.layer, 'meta', {}).get(_wcs_only_label, False) - if layer.layer.data.label == data_label and layer_is_wcs_only: - layer.visible = False - if data_label not in viewer.state.wcs_only_layers: - viewer.state.wcs_only_layers.append(data_label) - selected_items.pop(data_id) - # Sets the plot axes labels to be the units of the most recently # active data. viewer_data_labels = [layer.layer.label for layer in viewer.layers] @@ -2421,9 +2375,7 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac if self.config == 'imviz': viewer.on_limits_change() # Trigger compass redraw - def vue_data_item_remove(self, event): - - data_label = event['item_name'] + def data_item_remove(self, data_label): data = self.data_collection[data_label] orientation_plugin = self._jdaviz_helper.plugins.get("Orientation") if orientation_plugin is not None and orientation_plugin.align_by == "WCS": @@ -2672,8 +2624,6 @@ def _create_viewer_item(self, viewer, vid=None, name=None, reference=None, sender=self) ) - wcs_only_layers = getattr(viewer.state, 'wcs_only_layers', []) - reference_data = getattr(viewer.state, 'reference_data', None) reference_data_label = getattr(reference_data, 'label', None) linked_by_wcs = getattr(viewer.state, 'linked_by_wcs', False) @@ -2684,9 +2634,6 @@ def _create_viewer_item(self, viewer, vid=None, name=None, reference=None, 'widget': "IPY_MODEL_" + viewer.figure_widget.model_id, 'toolbar': "IPY_MODEL_" + viewer.toolbar.model_id if viewer.toolbar else '', # noqa 'data_menu': 'IPY_MODEL_' + viewer._data_menu.model_id if hasattr(viewer, '_data_menu') else '', # noqa - # TODO: remove unused entries after old data menu deprecation period - 'selected_data_items': {}, # noqa data_id: visibility state (visible, hidden, mixed), READ-ONLY - 'wcs_only_layers': wcs_only_layers, 'reference_data_label': reference_data_label, 'canvas_angle': 0, # canvas rotation clockwise rotation angle in deg 'canvas_flip_horizontal': False, # canvas rotation horizontal flip @@ -2694,7 +2641,6 @@ def _create_viewer_item(self, viewer, vid=None, name=None, reference=None, 'collapse': True, 'reference': reference or name or vid, 'linked_by_wcs': linked_by_wcs, - 'open_data_menu_if_empty': open_data_menu_if_empty # noqa open menu on init if viewer is empty } def _on_new_viewer(self, msg, vid=None, name=None, add_layers_to_viewer=False, @@ -2748,10 +2694,6 @@ def _on_new_viewer(self, msg, vid=None, name=None, add_layers_to_viewer=False, linked_by_wcs = False viewer.state.linked_by_wcs = linked_by_wcs - if linked_by_wcs: - from jdaviz.configs.imviz.helper import get_wcs_only_layer_labels - viewer.state.wcs_only_layers = get_wcs_only_layer_labels(self) - if msg.x_attr is not None: x = msg.data.id[msg.x_attr] viewer.state.x_att = x diff --git a/jdaviz/components/plugin_dataset_select.vue b/jdaviz/components/plugin_dataset_select.vue index 40c8cc53ae..0adcba83f8 100644 --- a/jdaviz/components/plugin_dataset_select.vue +++ b/jdaviz/components/plugin_dataset_select.vue @@ -67,12 +67,7 @@ diff --git a/jdaviz/components/tooltip.vue b/jdaviz/components/tooltip.vue index 9c5ff0ee93..c1de555358 100644 --- a/jdaviz/components/tooltip.vue +++ b/jdaviz/components/tooltip.vue @@ -64,15 +64,6 @@ const tooltips = { 'viewer-toolbar-figure-save': 'Save figure', 'viewer-toolbar-menu': 'Adjust display: contrast, bias, stretch', 'viewer-toolbar-more': 'More options...', - 'viewer-data-select-enabled': 'Allow multiple entries (click to enable replace)', - 'viewer-data-radio-enabled': 'Replace current entry (click to enable multi-select)', - 'viewer-data-select': 'Toggle visibility of all layers associated with this data entry', - 'viewer-data-radio': 'Switch visibility to layers associated with this data entry', - 'viewer-data-enable': 'Load data entry into this viewer', - 'viewer-data-disable': 'Disable data within this viewer (will be hidden and unavailable from plugins until re-enabled)', - 'viewer-wcs-delete': 'Remove orientation option across entire app', - 'viewer-data-delete': 'Remove data entry across entire app (might affect existing subsets)', - 'viewer-data-nowcs': 'Data does not have WCS, cannot add unless link type changed to pixel', 'table-prev': 'Select previous row in table', 'table-next': 'Select next row in table', diff --git a/jdaviz/components/viewer_data_select.vue b/jdaviz/components/viewer_data_select.vue deleted file mode 100644 index 8ea1f17474..0000000000 --- a/jdaviz/components/viewer_data_select.vue +++ /dev/null @@ -1,316 +0,0 @@ - - diff --git a/jdaviz/components/viewer_data_select_item.vue b/jdaviz/components/viewer_data_select_item.vue deleted file mode 100644 index f9064f0f14..0000000000 --- a/jdaviz/components/viewer_data_select_item.vue +++ /dev/null @@ -1,235 +0,0 @@ - - - - - diff --git a/jdaviz/configs/cubeviz/plugins/tests/test_data_selection.py b/jdaviz/configs/cubeviz/plugins/tests/test_data_selection.py index c49f970ddb..e0bbc11413 100644 --- a/jdaviz/configs/cubeviz/plugins/tests/test_data_selection.py +++ b/jdaviz/configs/cubeviz/plugins/tests/test_data_selection.py @@ -31,7 +31,6 @@ def test_data_selection(cubeviz_helper, spectrum1d_cube, tmpdir): assert len(fv.layers) == 2 assert len([layer for layer in fv.layers if layer.visible]) == 2 - app.vue_data_item_remove({'item_name': app.state.data_items[1]['name'], - 'viewer_id': 'cubeviz-0'}) + app.data_item_remove(app.state.data_items[1]['name']) assert len(fv.layers) == 1 diff --git a/jdaviz/configs/default/plugins/data_menu/data_menu.py b/jdaviz/configs/default/plugins/data_menu/data_menu.py index d7cab1f748..ef417f0f40 100644 --- a/jdaviz/configs/default/plugins/data_menu/data_menu.py +++ b/jdaviz/configs/default/plugins/data_menu/data_menu.py @@ -43,6 +43,9 @@ class DataMenu(TemplateMixin, LayerSelectMixin, DatasetSelectMixin): :ref:`public API `: * :meth:`open_menu` + * :meth:`data_labels_loaded` + * :meth:`data_labels_visible` + * :meth:`data_labels_unloaded` * ``layer`` (:class:`~jdaviz.core.template_mixin.LayerSelect`): actively selected layer(s) * ``orientation`` (:class:`~jdaviz.core.template_mixin.LayerSelect`): @@ -98,8 +101,6 @@ class DataMenu(TemplateMixin, LayerSelectMixin, DatasetSelectMixin): subset_edit_modes = List().tag(sync=True) - dev_data_menu = Bool(False).tag(sync=True) - def __init__(self, viewer, *args, **kwargs): super().__init__(*args, **kwargs) self._viewer = viewer @@ -153,9 +154,10 @@ def user_api(self): expose = ['open_menu', 'layer', 'set_layer_visibility', 'toggle_layer_visibility', 'create_subset', 'modify_subset', 'add_data', 'view_info', 'remove_from_viewer', 'remove_from_app'] + readonly = ['data_labels_loaded', 'data_labels_visible', 'data_labels_unloaded'] if self.app.config == 'imviz': expose += ['orientation'] - return UserApiWrapper(self, expose=expose) + return UserApiWrapper(self, expose=expose, readonly=readonly) def open_menu(self): """ @@ -167,6 +169,20 @@ def open_menu(self): def existing_subset_labels(self): return [sg.label for sg in self.app.data_collection.subset_groups] + @property + def data_labels_loaded(self): + return [layer['label'] for layer in self.layer_items + if layer['label'] not in self.existing_subset_labels] + + @property + def data_labels_visible(self): + return [layer['label'] for layer in self.layer_items + if layer['label'] not in self.existing_subset_labels and layer['visible']] + + @property + def data_labels_unloaded(self): + return self.dataset.choices + def _set_viewer_id(self): # viewer_ids are not populated on the viewer at init, so we'll keep checking and set # these the first time that they are available @@ -213,13 +229,21 @@ def during_select_sync(self): raise self._during_select_sync = False - @observe('dm_layer_selected') + @observe('dm_layer_selected', 'layer_multiselect') def _dm_layer_selected_changed(self, event={}): - if not hasattr(self, 'layer') or not self.layer.multiselect: # pragma: no cover + if not hasattr(self, 'layer'): # pragma: no cover return if self._during_select_sync: return - if len(event.get('new')) == len(event.get('old')): + if event.get('name') == 'layer_multiselect' and event.get('new'): + return + if not self.layer.multiselect and len(self.dm_layer_selected) > 1: + # vue will still treat the element as a list, so we will include the + # logic here to enforce single-select toggling + self.dm_layer_selected = [self.dm_layer_selected[-1]] + return + if (event.get('name') == 'dm_layer_selected' + and len(event.get('new')) == len(event.get('old'))): # not possible from UI interaction, but instead caused by a selected # layer being removed (deleting a selected subset, etc). We want # to update dm_layer_selected in order to preserve layer.selected @@ -228,8 +252,12 @@ def _dm_layer_selected_changed(self, event={}): with self.during_select_sync(): # map index in dm_layer_selected (inverse order of layer_items) # to set self.layer.selected - self.layer.selected = [self.layer_items[i]['label'] - for i in self.dm_layer_selected] + selected = [self.layer_items[i]['label'] + for i in self.dm_layer_selected] + if self.layer.multiselect: + self.layer.selected = selected + else: + self.layer.selected = selected[0] if len(selected) else '' @observe('layer_selected', 'layer_items') def _layers_changed(self, event={}): @@ -239,7 +267,8 @@ def _layers_changed(self, event={}): with self.during_select_sync(): # map list of strings in self.layer.selected to indices in dm_layer_selected layer_labels = [layer['label'] for layer in self.layer_items] - self.dm_layer_selected = [layer_labels.index(label) for label in self.layer.selected + layer_selected = self.layer_selected if self.layer.multiselect else [self.layer_selected] # noqa + self.dm_layer_selected = [layer_labels.index(label) for label in layer_selected if label in layer_labels] subset_labels = self.existing_subset_labels @@ -515,7 +544,7 @@ def remove_from_app(self): self.app.data_collection.remove_subset_group(sg) break else: - self.app.vue_data_item_remove({'item_name': layer}) + self.app.data_item_remove(layer) def vue_remove_from_app(self, *args): self.remove_from_app() # pragma: no cover diff --git a/jdaviz/configs/default/plugins/data_menu/data_menu.vue b/jdaviz/configs/default/plugins/data_menu/data_menu.vue index 83e386a6c2..792edc4dcf 100644 --- a/jdaviz/configs/default/plugins/data_menu/data_menu.vue +++ b/jdaviz/configs/default/plugins/data_menu/data_menu.vue @@ -26,8 +26,7 @@ :linewidth="0" :cmap_samples="cmap_samples" btn_style="margin-bottom: 0px" - @click="() => {if (dev_data_menu) {data_menu_open = !data_menu_open}}" - :disabled="!dev_data_menu" + @click="() => {data_menu_open = !data_menu_open}" /> {{viewer_reference || viewer_id}} @@ -46,13 +45,12 @@ :linewidth="item.linewidth" :cmap_samples="cmap_samples" btn_style="margin-bottom: 0px" - @click="() => {if (dev_data_menu) {data_menu_open = !data_menu_open}}" - :disabled="!dev_data_menu" + @click="() => {data_menu_open = !data_menu_open}" /> - + {{ item.label }} @@ -71,6 +69,7 @@ - + {{ item.label }} diff --git a/jdaviz/configs/default/plugins/viewers.py b/jdaviz/configs/default/plugins/viewers.py index 36ee3ca952..41050d5be0 100644 --- a/jdaviz/configs/default/plugins/viewers.py +++ b/jdaviz/configs/default/plugins/viewers.py @@ -15,6 +15,7 @@ from glue_jupyter.bqplot.image import BqplotImageView from glue_jupyter.table import TableViewer +from astropy.utils import deprecated from astropy import units as u from astropy.nddata import ( NDDataArray, StdDevUncertainty, VarianceUncertainty, InverseVariance @@ -70,10 +71,9 @@ def __init__(self, *args, **kwargs): def user_api(self): # default exposed user APIs. Can override this method in any particular viewer. if not isinstance(self, TableViewer): - # TODO: eventually remove data_labels, data_labels_loaded, + # TODO: eventually remove data_labels_loaded # and data_labels_visible once deprecation period passes - # TODO: add data_menu once API is finalized and ready to be made public - expose = ['data_labels', 'data_labels_loaded', 'data_labels_visible'] + expose = ['data_labels_loaded', 'data_labels_visible', 'data_menu'] else: expose = [] if isinstance(self, BqplotImageView): @@ -99,8 +99,13 @@ def user_api(self): def data_menu(self): return self._data_menu.user_api + def _deprecated_data_menu(self): + # temporary method to allow for opening new data-menu from old button. This should + # be removed anytime after the old button is removed (likely in 4.3) + self.data_menu.open_menu() + @property - # TODO: deprecate in favor of viewer.data_menu.layers_loaded + @deprecated(since="4.1", alternative="viewer.data_menu.data_labels_loaded") def data_labels_loaded(self): """ List of data labels loaded in this viewer. @@ -110,13 +115,10 @@ def data_labels_loaded(self): data_labels : list list of strings """ - viewer_item = self.jdaviz_app._get_viewer_item(self.reference_id) - return [self.jdaviz_app._get_data_item_by_id(data_id)['name'] - for data_id in viewer_item.get('selected_data_items', {}).keys() - if self.jdaviz_app._get_data_item_by_id(data_id) is not None] + return self.data_menu.data_labels_loaded @property - # TODO: deprecate in favor of viewer.data_menu.layers_visible + @deprecated(since="4.1", alternative="viewer.data_menu.data_labels_visible") def data_labels_visible(self): """ List of data labels visible in this viewer. @@ -126,10 +128,7 @@ def data_labels_visible(self): data_labels : list list of strings """ - viewer_item = self.jdaviz_app._get_viewer_item(self.reference_id) - return [self.jdaviz_app._get_data_item_by_id(data_id)['name'] - for data_id, visibility in viewer_item.get('selected_data_items', {}).items() - if visibility != 'hidden'] + return self.data_menu.data_labels_visible def reset_limits(self): """ diff --git a/jdaviz/configs/default/tests/test_data_menu.py b/jdaviz/configs/default/tests/test_data_menu.py index ad952aed70..df4a68c066 100644 --- a/jdaviz/configs/default/tests/test_data_menu.py +++ b/jdaviz/configs/default/tests/test_data_menu.py @@ -64,7 +64,7 @@ def test_data_menu_selection(specviz_helper, spectrum1d): dm._obj.dm_layer_selected = [1] assert dm.layer.selected == ['test'] specviz_helper.app.remove_data_from_viewer("spectrum-viewer", "test2") - specviz_helper.app.vue_data_item_remove({"item_name": "test2"}) + specviz_helper.app.data_item_remove("test2") assert len(dm._obj.layer_items) == 1 assert dm._obj.dm_layer_selected == [0] assert dm.layer.selected == ['test'] diff --git a/jdaviz/configs/imviz/plugins/orientation/orientation.py b/jdaviz/configs/imviz/plugins/orientation/orientation.py index de86ca981c..61547b24dc 100644 --- a/jdaviz/configs/imviz/plugins/orientation/orientation.py +++ b/jdaviz/configs/imviz/plugins/orientation/orientation.py @@ -25,7 +25,8 @@ PluginTemplateMixin, SelectPluginComponent, LayerSelect, ViewerSelectMixin, AutoTextField ) from jdaviz.core.user_api import PluginUserApi -from jdaviz.utils import get_reference_image_data, layer_is_2d, _wcs_only_label +from jdaviz.utils import (get_wcs_only_layer_labels, get_reference_image_data, + layer_is_2d, _wcs_only_label) __all__ = ['Orientation'] @@ -421,8 +422,7 @@ def _add_orientation(self, rotation_angle=None, east_left=None, label=None, def _add_data_to_viewer(self, data_label, viewer_id): viewer = self.app.get_viewer_by_id(viewer_id) - wcs_only_layers = viewer.state.wcs_only_layers - if data_label not in wcs_only_layers: + if data_label not in viewer.data_menu.orientation.choices: self.app.add_data_to_viewer(viewer_id, data_label) def _on_viewer_added(self, msg): @@ -433,7 +433,7 @@ def _send_wcs_layers_to_all_viewers(self, *args, **kwargs): if not hasattr(self, 'viewer'): return - wcs_only_layers = self.app._jdaviz_helper.default_viewer._obj.state.wcs_only_layers + wcs_only_layers = get_wcs_only_layer_labels(self.app) viewers_to_update = kwargs.get( 'viewers_to_update', self.app._viewer_store.keys() @@ -446,7 +446,7 @@ def _send_wcs_layers_to_all_viewers(self, *args, **kwargs): self.app.add_data_to_viewer(viewer_ref, wcs_layer) if ( self.orientation.selected not in - self.viewer.selected_obj.state.wcs_only_layers and + wcs_only_layers and self.align_by_selected == 'WCS' ): self.orientation.selected = base_wcs_layer_label diff --git a/jdaviz/configs/imviz/plugins/viewers.py b/jdaviz/configs/imviz/plugins/viewers.py index 3726ee75cb..d250ae9016 100644 --- a/jdaviz/configs/imviz/plugins/viewers.py +++ b/jdaviz/configs/imviz/plugins/viewers.py @@ -12,7 +12,8 @@ from jdaviz.core.registries import viewer_registry from jdaviz.core.freezable_state import FreezableBqplotImageViewerState from jdaviz.configs.default.plugins.viewers import JdavizViewerMixin -from jdaviz.utils import data_has_valid_wcs, layer_is_image_data, get_top_layer_index +from jdaviz.utils import (get_wcs_only_layer_labels, data_has_valid_wcs, + layer_is_image_data, get_top_layer_index) __all__ = ['ImvizImageView'] @@ -318,7 +319,7 @@ def get_alignment_method(self, data_label): if data_label == ref_label: return 'self' - if ref_label in self.state.wcs_only_layers: + if ref_label in get_wcs_only_layer_labels(self.jdaviz_app): return 'wcs' align_by = None diff --git a/jdaviz/configs/imviz/tests/test_delete_data.py b/jdaviz/configs/imviz/tests/test_delete_data.py index 62b8ca08e4..9978d6b028 100644 --- a/jdaviz/configs/imviz/tests/test_delete_data.py +++ b/jdaviz/configs/imviz/tests/test_delete_data.py @@ -60,7 +60,7 @@ def test_delete_with_subset_wcs(self): # We have to remove the data from the viewer before deleting the data from the app. self.imviz.app.remove_data_from_viewer("imviz-0", "has_wcs_1[SCI,1]") - self.imviz.app.vue_data_item_remove({"item_name": "has_wcs_1[SCI,1]"}) + self.imviz.app.data_item_remove("has_wcs_1[SCI,1]") # Make sure we re-linked images 2 and 3 (plus WCS-only reference data layer) assert len(self.imviz.app.data_collection.external_links) == 2 @@ -98,7 +98,7 @@ def test_delete_wcs_layer_with_subset(self): self.imviz.app._change_reference_data("Default orientation") # Delete N-up E-left reference data from GUI. - self.imviz.app.vue_data_item_remove({"item_name": "North-up, East-left"}) + self.imviz.app.data_item_remove("North-up, East-left") # Make sure rotated ellipse is still the same as before. out_reg_d = self.imviz.app.get_subsets(include_sky_region=True)['Subset 1'][0]['sky_region'] diff --git a/jdaviz/configs/imviz/tests/test_orientation.py b/jdaviz/configs/imviz/tests/test_orientation.py index 96d4a4737c..024a98cff5 100644 --- a/jdaviz/configs/imviz/tests/test_orientation.py +++ b/jdaviz/configs/imviz/tests/test_orientation.py @@ -177,7 +177,7 @@ def test_delete_orientation_multi_viewer(self): lc_plugin.viewer = "imviz-1" lc_plugin.orientation = "North-up, East-left" - self.imviz.app.vue_data_item_remove({"item_name": "North-up, East-left"}) + self.imviz.app.data_item_remove("North-up, East-left") assert self.viewer.state.reference_data.label == "Default orientation" assert viewer_2.state.reference_data.label == "Default orientation" @@ -202,7 +202,7 @@ def test_delete_orientation_with_subset(self, klass, angle, sbst_theta): # Switch to N-up E-right lc_plugin.set_north_up_east_right() - self.imviz.app.vue_data_item_remove({"item_name": "North-up, East-left"}) + self.imviz.app.data_item_remove("North-up, East-left") # Check that E-right still linked to default assert len(self.imviz.app.data_collection.external_links) == 3 diff --git a/jdaviz/configs/imviz/tests/test_wcs_utils.py b/jdaviz/configs/imviz/tests/test_wcs_utils.py index 1cfd1a236d..f5b1b9dd6a 100644 --- a/jdaviz/configs/imviz/tests/test_wcs_utils.py +++ b/jdaviz/configs/imviz/tests/test_wcs_utils.py @@ -114,11 +114,13 @@ class TestWCSOnly(BaseImviz_WCS_GWCS): # TODO: Replace private API calls with more public ones when available. def test_non_wcs_layer_labels(self): + from jdaviz.utils import get_wcs_only_layer_labels + self.imviz.link_data(align_by="wcs") assert len(self.imviz.app.data_collection) == 3 # Confirm the WCS-only layer is created by WCS-linking . - assert len(self.viewer.state.wcs_only_layers) == 1 + assert len(get_wcs_only_layer_labels(self.imviz.app)) == 1 # Load a WCS-only layer, bypassing normal labeling scheme. ndd = wcs_utils._get_rotated_nddata_from_label( @@ -133,7 +135,7 @@ def test_non_wcs_layer_labels(self): assert len(self.imviz.app.state.layer_icons) == 4 # 3 + 1 # Confirm the new WCS-only layer is logged. - assert len(self.viewer.state.wcs_only_layers) == 2 + assert len(get_wcs_only_layer_labels(self.imviz.app)) == 2 # Load a second WCS-only layer. ndd2 = wcs_utils._get_rotated_nddata_from_label( @@ -149,7 +151,7 @@ def test_non_wcs_layer_labels(self): assert len(self.imviz.app.state.layer_icons) == 5 # Confirm the second WCS-only layer is logged - assert len(self.viewer.state.wcs_only_layers) == 3 + assert len(get_wcs_only_layer_labels(self.imviz.app)) == 3 # First entry is image data and the default reference data. assert self.imviz.app.state.layer_icons["fits_wcs[DATA]"] == "a" diff --git a/jdaviz/configs/specviz/tests/test_helper.py b/jdaviz/configs/specviz/tests/test_helper.py index 927046d8a5..3c0f7f5223 100644 --- a/jdaviz/configs/specviz/tests/test_helper.py +++ b/jdaviz/configs/specviz/tests/test_helper.py @@ -523,7 +523,7 @@ def test_delete_data_with_subsets(specviz_helper, spectrum1d, spectrum1d_nm): np.testing.assert_allclose((subset1.subset_state.lo, subset1.subset_state.hi), (6200, 7000)) specviz_helper.app.remove_data_from_viewer("spectrum-viewer", "my_spec_AA") - specviz_helper.app.vue_data_item_remove({"item_name": "my_spec_AA"}) + specviz_helper.app.data_item_remove("my_spec_AA") # Check that the reparenting and coordinate recalculations happened assert subset1.subset_state.att.parent.label == "my_spec_nm" diff --git a/jdaviz/container.vue b/jdaviz/container.vue index 0eb2f6b2d6..db3b933a0e 100644 --- a/jdaviz/container.vue +++ b/jdaviz/container.vue @@ -11,9 +11,6 @@ :layer_icons="layer_icons" @resize="(e) => $emit('resize', e)" :closefn="closefn" - @data-item-visibility="$emit('data-item-visibility', $event)" - @data-item-unload="$emit('data-item-unload', $event)" - @data-item-remove="$emit('data-item-remove', $event)" @call-viewer-method="$emit('call-viewer-method', $event)" @change-reference-data="$emit('change-reference-data', $event)" > @@ -28,18 +25,20 @@ >
- + + + mdi-format-list-bulleted-square + + diff --git a/jdaviz/core/tests/test_config_detection.py b/jdaviz/core/tests/test_config_detection.py new file mode 100644 index 0000000000..fbe0d57814 --- /dev/null +++ b/jdaviz/core/tests/test_config_detection.py @@ -0,0 +1,37 @@ +import pytest +from astropy.utils.data import download_file +from jdaviz.core.data_formats import identify_helper + +# URIs to example JWST/HST files on MAST, and their +# corresponding jdaviz helpers: +example_uri_helper = [ + ['mast:HST/product/id4301ouq_drz.fits', 'imviz'], + ['mast:HST/product/ldq601030_x1dsum.fits', 'specviz'], + ['mast:HST/product/o4xw01dkq_flt.fits', 'specviz2d'], + ['mast:JWST/product/jw02072-c1003_s00002_nirspec_clear-prism-s200a1_x1d.fits', + 'specviz'], + ['mast:JWST/product/jw01324-o006_s00005_nirspec_f100lp-g140h_s2d.fits', + 'specviz2d'], + ['mast:JWST/product/jw01345-o001_t021_nircam_clear-f200w_i2d.fits', 'imviz'], + ['mast:JWST/product/jw01373-o028_t001_nirspec_g395h-f290lp_s3d.fits', + 'cubeviz'], + ['mast:JWST/product/jw01373-o031_t007_miri_ch1-shortmediumlong_s3d.fits', + 'cubeviz'], + ['mast:JWST/product/jw01783-o004_t008_nircam_clear-f444w_i2d.fits', 'imviz'], + ['mast:JWST/product/jw02732-o004_t004_miri_ch1-shortmediumlong_x1d.fits', + 'specviz'] +] + + +@pytest.mark.skip(reason="filenames changed") +@pytest.mark.remote_data +@pytest.mark.filterwarnings(r"ignore::astropy.wcs.wcs.FITSFixedWarning") +@pytest.mark.parametrize( + "uri, expected_helper", example_uri_helper +) +def test_auto_config_detection(uri, expected_helper): + url = f'https://mast.stsci.edu/api/v0.1/Download/file/?uri={uri}' + fn = download_file(url, timeout=100) + helper_name, hdul = identify_helper(fn) + hdul.close() + assert helper_name == expected_helper diff --git a/jdaviz/core/tests/test_data_menu.py b/jdaviz/core/tests/test_data_menu.py deleted file mode 100644 index 136cb25168..0000000000 --- a/jdaviz/core/tests/test_data_menu.py +++ /dev/null @@ -1,116 +0,0 @@ -import pytest -import numpy as np - -from astropy.utils.data import download_file -from specutils import SpectralRegion - -from jdaviz.core.data_formats import identify_helper - -# URIs to example JWST/HST files on MAST, and their -# corresponding jdaviz helpers: -example_uri_helper = [ - ['mast:HST/product/id4301ouq_drz.fits', 'imviz'], - ['mast:HST/product/ldq601030_x1dsum.fits', 'specviz'], - ['mast:HST/product/o4xw01dkq_flt.fits', 'specviz2d'], - ['mast:JWST/product/jw02072-c1003_s00002_nirspec_clear-prism-s200a1_x1d.fits', - 'specviz'], - ['mast:JWST/product/jw01324-o006_s00005_nirspec_f100lp-g140h_s2d.fits', - 'specviz2d'], - ['mast:JWST/product/jw01345-o001_t021_nircam_clear-f200w_i2d.fits', 'imviz'], - ['mast:JWST/product/jw01373-o028_t001_nirspec_g395h-f290lp_s3d.fits', - 'cubeviz'], - ['mast:JWST/product/jw01373-o031_t007_miri_ch1-shortmediumlong_s3d.fits', - 'cubeviz'], - ['mast:JWST/product/jw01783-o004_t008_nircam_clear-f444w_i2d.fits', 'imviz'], - ['mast:JWST/product/jw02732-o004_t004_miri_ch1-shortmediumlong_x1d.fits', - 'specviz'] -] - - -def test_data_menu_toggles(specviz_helper, spectrum1d): - # load 2 data entries - specviz_helper.load_data(spectrum1d, data_label="test") - app = specviz_helper.app - sv = app.get_viewer('spectrum-viewer') - new_spec = specviz_helper.get_spectra(apply_slider_redshift=True)["test"]*0.9 - specviz_helper.load_data(new_spec, data_label="test2") - - # check that both are enabled in the data menu - selected_data_items = app._viewer_item_by_id('specviz-0')['selected_data_items'] - data_ids = list(selected_data_items.keys()) - assert len(data_ids) == 2 - assert np.all([visible == 'visible' for visible in selected_data_items.values()]) - assert len(sv.layers) == 2 - assert np.all([layer.visible for layer in sv.layers]) - - # disable (hide layer) for second entry - app.set_data_visibility('specviz-0', app._get_data_item_by_id(data_ids[-1])['name'], - visible=False) - - selected_data_items = app._viewer_item_by_id('specviz-0')['selected_data_items'] - assert selected_data_items[data_ids[0]] == 'visible' - assert sv.layers[0].visible is True - assert selected_data_items[data_ids[1]] == 'hidden' - 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['Subset Tools'].import_region( - SpectralRegion(6000 * spectrum1d.spectral_axis.unit, 6500 * spectrum1d.spectral_axis.unit)) - - assert len(sv.layers) == 4 - assert sv.layers[2].visible is True # subset corresponding to first (visible) data entry - assert sv.layers[3].visible is False # subset corresponding to second (hidden) data entry - - # enable data layer from menu and subset should also become visible - app.set_data_visibility('specviz-0', app._get_data_item_by_id(data_ids[-1])['name'], - visible=True) - assert np.all([layer.visible for layer in sv.layers]) - - # manually hide just the data layer, and the visiblity state in the menu should show as mixed - sv.layers[3].visible = False - - selected_data_items = app._viewer_item_by_id('specviz-0')['selected_data_items'] - assert selected_data_items[data_ids[0]] == 'visible' - assert selected_data_items[data_ids[1]] == 'mixed' - - -def test_visibility_toggle(imviz_helper): - arr = np.arange(4).reshape((2, 2)) - imviz_helper.load_data(arr, data_label='no_results_data') - app = imviz_helper.app - iv = app.get_viewer('imviz-0') - - # regression test for https://github.com/spacetelescope/jdaviz/issues/1723 - # test to make sure plot options (including percentile) stick when toggling visibility - # via the data menu checkboxes - selected_data_items = app._viewer_item_by_id('imviz-0')['selected_data_items'] - data_ids = list(selected_data_items.keys()) - assert selected_data_items[data_ids[0]] == 'visible' - assert iv.layers[0].visible is True - po = imviz_helper.plugins['Plot Options'] - po.stretch_preset = 90 - assert po.stretch_preset.value == 90 - - app.set_data_visibility('imviz-0', app._get_data_item_by_id(data_ids[0])['name'], - visible=False) - - assert iv.layers[0].visible is False - - app.set_data_visibility('imviz-0', app._get_data_item_by_id(data_ids[0])['name'], - visible=True) - assert iv.layers[0].visible is True - assert po.stretch_preset.value == 90 - - -@pytest.mark.skip(reason="filenames changed") -@pytest.mark.remote_data -@pytest.mark.filterwarnings(r"ignore::astropy.wcs.wcs.FITSFixedWarning") -@pytest.mark.parametrize( - "uri, expected_helper", example_uri_helper -) -def test_auto_config_detection(uri, expected_helper): - url = f'https://mast.stsci.edu/api/v0.1/Download/file/?uri={uri}' - fn = download_file(url, timeout=100) - helper_name, hdul = identify_helper(fn) - hdul.close() - assert helper_name == expected_helper diff --git a/jdaviz/tests/test_user_api.py b/jdaviz/tests/test_user_api.py index dcea529654..ad4f7c6d87 100644 --- a/jdaviz/tests/test_user_api.py +++ b/jdaviz/tests/test_user_api.py @@ -35,8 +35,8 @@ def test_specviz_data_labels(specviz_helper, spectrum1d): specviz_helper.load_data(spectrum1d, data_label=label) assert specviz_helper.data_labels == [label] - assert specviz_helper.viewers['spectrum-viewer'].data_labels_loaded == [label] - assert specviz_helper.viewers['spectrum-viewer'].data_labels_visible == [label] + assert specviz_helper.viewers['spectrum-viewer'].data_menu.data_labels_loaded == [label] + assert specviz_helper.viewers['spectrum-viewer'].data_menu.data_labels_visible == [label] def test_toggle_api_hints(specviz_helper):