Skip to content

Commit

Permalink
footprints: filter by observatory (#3345)
Browse files Browse the repository at this point in the history
* allow manual_options to be filter
* manual options can be either strings or dictionaries
* filters passed to SelectPluginComponent will act on manual items, classes that subclass SelectPluginComponent may choose to ALWAYS include all manual items, ignoring filters
* FileImportSelect icon support
* use JWST and Roman footprint icons
* fix test failures
* handle exception in export plugin

---------

Co-authored-by: Jennifer Kotler <[email protected]>
  • Loading branch information
kecnry and Jennifer Kotler authored Dec 20, 2024
1 parent 18a740d commit 9663cf7
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 104 deletions.
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Imviz

- Orientation plugin API now exposes create_north_up_east_left and create_north_up_east_right methods. [#3308]

- Add Roman WFI and CGI footprints to the Footprints plugin. [#3322]
- Add Roman WFI and CGI footprints to the Footprints plugin. [#3322, #3345]

- Catalog Search plugin now exposes a maximum sources limit for all catalogs and resolves an edge case
when loading a catalog from a file that only contains one source. [#3337]
Expand Down
29 changes: 21 additions & 8 deletions jdaviz/components/plugin_file_import_select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,36 @@
<v-select
:menu-props="{ left: true }"
attach
:items="items.map(i => i.label)"
:items="items"
item-text="label"
item-value="label"
v-model="selected"
@change="$emit('update:selected', $event)"
:label="api_hints_enabled && api_hint ? api_hint : label"
:class="api_hints_enabled && api_hint ? 'api-hint' : null"
:hint="hint"
persistent-hint
>
<template v-slot:selection="{ item }">
<span :class="api_hints_enabled ? 'api-hint' : null">
{{ api_hints_enabled ?
'\'' + item + '\''
:
item
}}
<template v-slot:selection="{ item, index }">
<div class="single-line" style="width: 100%">
<span v-if="api_hints_enabled" class="api-hint" :style="index > 0 ? 'display: none' : null">
{{'\'' + selected + '\''}}
</span>
<span v-else>
<v-icon v-if="item.icon && item.icon.length < 50" small>{{ item.icon }}</v-icon>
<img v-else-if="item.icon" :src="item.icon" width="16" class="invert-if-dark" style="opacity: 1.0; margin-bottom: -2px"/>
{{ selected }}
</span>
</div>
</template>
<template v-slot:item="{ item }">
<span style="margin-top: 8px; margin-bottom: 0px">
<v-icon v-if="item.icon && item.icon.length < 50" small>{{ item.icon }}</v-icon>
<img v-else-if="item.icon" :src="item.icon" width="16" class="invert-if-dark" style="opacity: 1.0; margin-bottom: -2px"/>
{{ item.label }}
</span>
</template>

</v-select>
<v-chip v-if="selected === 'From File...'"
close
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/configs/default/plugins/data_menu/data_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def _layers_changed(self, event={}):
if event.get('name') == 'layer_items':
# changing the layers in the viewer needs to trigger an update to dataset_items
# through the set filters
self.dataset._on_data_changed()
self.dataset._update_items()
self.loaded_n_data = len([lyr for lyr in self.layer.choices
if lyr not in subset_labels])
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def update_dq_layer(self, *args):
return

self.dq_layer.filter_is_child_of = self.science_layer_selected
self.dq_layer._update_layer_items()
self.dq_layer._update_items()

# listen for changes on the image opacity, and update the
# data quality layer opacity on changes to the science layer opacity
Expand Down
29 changes: 19 additions & 10 deletions jdaviz/configs/default/plugins/export/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,12 @@ def _is_filename_changed(self, event):
def _update_subset_format_disabled(self):
new_items = []
if self.subset.selected is not None:
subset = self.app.get_subsets(self.subset.selected)
try:
subset = self.app.get_subsets(self.subset.selected)
except Exception:
# subset invalid message will already be set,
# no need to set valid/invalid formats.
return
if self.app._is_subset_spectral(subset[0]):
good_formats = ["ecsv"]
else:
Expand Down Expand Up @@ -333,16 +338,20 @@ def _set_subset_not_supported_msg(self, msg=None):
disable Export button until these are supported.
"""

if self.subset.selected is not None:
subset = self.app.get_subsets(self.subset.selected)
if self.subset.selected == '':
self.subset_invalid_msg = ''
elif self.app._is_subset_spectral(subset[0]):
self.subset_invalid_msg = ''
elif len(subset) > 1:
self.subset_invalid_msg = 'Export for composite subsets not yet supported.'
if self.subset.selected not in [None, '']:
try:
subset = self.app.get_subsets(self.subset.selected)
except Exception as e:
self.subset_invalid_msg = f"Export for subset not supported: {e}"
else:
self.subset_invalid_msg = ''
if self.subset.selected == '':
self.subset_invalid_msg = ''
elif self.app._is_subset_spectral(subset[0]):
self.subset_invalid_msg = ''
elif len(subset) > 1:
self.subset_invalid_msg = 'Export for composite subsets not yet supported.'
else:
self.subset_invalid_msg = ''
else: # no subset selected (can be '' instead of None if previous selection made)
self.subset_invalid_msg = ''

Expand Down
14 changes: 7 additions & 7 deletions jdaviz/configs/default/plugins/subset_tools/subset_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ class SubsetTools(PluginTemplateMixin):
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)
combination_mode_items = List([]).tag(sync=True)
combination_mode_selected = Any().tag(sync=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -160,8 +160,8 @@ def __init__(self, *args, **kwargs):
multiselect=None)

self.combination_mode = SelectPluginComponent(self,
items='combination_items',
selected='combination_selected',
items='combination_mode_items',
selected='combination_mode_selected',
manual_options=COMBO_OPTIONS)

@property
Expand Down Expand Up @@ -1125,11 +1125,11 @@ def _load_regions(self, regions, combination_mode=None, max_num_regions=None,
if return_bad_regions:
return bad_regions

@observe('combination_selected')
def _combination_selected_updated(self, change):
@observe('combination_mode_selected')
def _combination_mode_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.combination_mode_selected = SUBSET_TO_PRETTY[
self.app.session.edit_subset_mode.mode]
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@

<v-row v-if="api_hints_enabled" style="margin-top: -32px">
<span class="api-hint">
plg.combination_mode = '{{ combination_selected }}'
plg.combination_mode = '{{ combination_mode_selected }}'
</span>
</v-row>

Expand Down
63 changes: 48 additions & 15 deletions jdaviz/configs/imviz/plugins/footprints/footprints.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
from traitlets import Bool, List, Unicode, observe
import numpy as np
import os
import regions

from glue.core.message import DataCollectionAddMessage, DataCollectionDeleteMessage
from glue_jupyter.common.toolbar_vuetify import read_icon

from jdaviz.core.custom_traitlets import FloatHandleEmpty
from jdaviz.core.events import LinkUpdatedMessage, ChangeRefDataMessage
from jdaviz.core.marks import FootprintOverlay
from jdaviz.core.region_translators import regions2roi
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import (PluginTemplateMixin, ViewerSelectMixin,
EditableSelectPluginComponent,
EditableSelectPluginComponent, SelectPluginComponent,
FileImportSelectPluginComponent, HasFileImportSelect)
from jdaviz.core.tools import ICON_DIR
from jdaviz.core.user_api import PluginUserApi

from jdaviz.configs.imviz.plugins.footprints import preset_regions
Expand All @@ -20,13 +23,6 @@
__all__ = ['Footprints']


_available_instruments = {
display_name: {'label': display_name, 'siaf_name': siaf_name, 'observatory': observatory}
for observatory, instruments in preset_regions._instruments.items()
for display_name, siaf_name in instruments.items()
}


@tray_registry('imviz-footprints', label="Footprints")
class Footprints(PluginTemplateMixin, ViewerSelectMixin, HasFileImportSelect):
"""
Expand Down Expand Up @@ -54,6 +50,8 @@ class Footprints(PluginTemplateMixin, ViewerSelectMixin, HasFileImportSelect):
color of the currently selected overlay
* ``fill_opacity``
opacity of the filled region of the currently selected overlay
* ``preset_obs`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
selected observatories to filter ``preset`` choices.
* ``preset`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
selected overlay preset
* :meth:`import_region`
Expand Down Expand Up @@ -92,6 +90,8 @@ class Footprints(PluginTemplateMixin, ViewerSelectMixin, HasFileImportSelect):

# PRESET OVERLAYS AND OPTIONS
has_pysiaf = Bool(preset_regions._has_pysiaf).tag(sync=True)
preset_obs_items = List().tag(sync=True)
preset_obs_selected = Unicode().tag(sync=True)
preset_items = List().tag(sync=True)
preset_selected = Unicode().tag(sync=True)

Expand Down Expand Up @@ -126,17 +126,31 @@ def __init__(self, *args, **kwargs):
on_remove=self._on_overlay_remove)

if self.has_pysiaf:
preset_options = list(_available_instruments.keys())
obs_icons = {'JWST': read_icon(os.path.join(ICON_DIR, 'jwst_solid.svg'), 'svg+xml'),
'Roman': read_icon(os.path.join(ICON_DIR, 'roman_solid.svg'), 'svg+xml')}
preset_options = [{'label': display_name,
'siaf_name': siaf_name,
'observatory': observatory,
'icon': obs_icons.get(observatory, None)}
for observatory, instruments in preset_regions._instruments.items()
for display_name, siaf_name in instruments.items()]
preset_obs_options = ['Any'] + [{'label': obs, 'icon': obs_icons.get(obs)}
for obs in preset_regions._instruments.keys()]
else:
preset_options = ['None']
preset_obs_options = []

if not self.app.state.settings.get('server_is_remote', False):
preset_options.append('From File...')
self.preset_obs = SelectPluginComponent(self,
items='preset_obs_items',
selected='preset_obs_selected',
manual_options=preset_obs_options)

self.preset = FileImportSelectPluginComponent(self,
items='preset_items',
selected='preset_selected',
manual_options=preset_options)
manual_options=preset_options,
apply_filters_to_manual_options=True,
server_is_remote=self.app.state.settings.get('server_is_remote', False)) # noqa

# set the custom file parser for importing catalogs
self.preset._file_parser = self._file_parser
Expand All @@ -153,7 +167,7 @@ def user_api(self):
return PluginUserApi(self, expose=('overlay',
'rename_overlay', 'add_overlay', 'remove_overlay',
'viewer', 'visible', 'color', 'fill_opacity',
'preset', 'import_region',
'preset_obs', 'preset', 'import_region',
'center_on_viewer', 'ra', 'dec', 'pa',
'v2_offset', 'v3_offset',
'overlay_regions'))
Expand Down Expand Up @@ -489,15 +503,34 @@ def overlay_regions(self):
regs = [regs]
overlay['regions'] = regs
regs = overlay.get('regions', [])
elif self.has_pysiaf and self.preset_selected in _available_instruments.keys():
elif self.has_pysiaf:
regs = preset_regions.instrument_footprint(
_available_instruments[self.preset_selected]['observatory'],
self.preset.selected_item['observatory'],
self.preset_selected, **callable_kwargs
)
else: # pragma: no cover
regs = []
return regs

@observe('preset_obs_selected')
def _update_preset_filters(self, event={}):
if not hasattr(self, 'preset'):
# during plugin init
return

def only_jwst(item):
return item['label'] == 'From File...' or item.get('observatory') == 'JWST'

def only_roman(item):
return item['label'] == 'From File...' or item.get('observatory') == 'Roman'

if self.preset_obs_selected == 'JWST':
self.preset.filters = [only_jwst]
elif self.preset_obs_selected == 'Roman':
self.preset.filters = [only_roman]
else:
self.preset.filters = []

@observe('preset_selected', 'from_file', 'ra', 'dec', 'pa', 'v2_offset', 'v3_offset')
def _preset_args_changed(self, msg={}, overlay_selected=None):
if self._ignore_traitlet_change:
Expand Down
20 changes: 20 additions & 0 deletions jdaviz/configs/imviz/plugins/footprints/footprints.vue
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,26 @@
and running `pip install pysiaf` and then launching Jdaviz.
</v-alert>

<v-row justify="end" class="row-no-outside-padding" style="margin-bottom: -6px !important">
<div v-for="preset_obs_item in preset_obs_items">
<j-tooltip v-if="preset_obs_item.label !== 'Any'" :tooltipcontent="'Show only '+preset_obs_item.label+' footprints in preset list'">
<v-btn
tile
:elevation=0
x-small
dense
:color="preset_obs_selected === preset_obs_item.label ? 'turquoise' : 'transparent'"
:dark="preset_obs_selected === preset_obs_item.label"
style="padding-left: 8px; padding-right: 6px;"
@click="() => {if (preset_obs_selected === preset_obs_item.label) {preset_obs_selected = 'Any'} else {preset_obs_selected = preset_obs_item.label}}"
>
<img :src="preset_obs_item.icon" width="16" class="invert-if-dark" style="margin-right: 2px"/>
{{ preset_obs_item.label }}
</v-btn>
</j-tooltip>
</div>
</v-row>

<plugin-file-import-select
:items="preset_items"
:selected.sync="preset_selected"
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/configs/mosviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ def _row_click_message_handler(self, msg):
# update data filters in each viewer's data_menu
for viewer in self.viewers.values():
if data_menu := getattr(viewer._obj, '_data_menu', None):
data_menu.dataset._on_data_changed()
data_menu.dataset._update_items()

def _handle_image_zoom(self, msg):
mos_data = self.app.data_collection['MOS Table']
Expand Down
Loading

0 comments on commit 9663cf7

Please sign in to comment.