Skip to content

Commit

Permalink
supporting one DQ layer in multiple viewers
Browse files Browse the repository at this point in the history
  • Loading branch information
bmorris3 committed Apr 23, 2024
1 parent 5b5869e commit 8118066
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 79 deletions.
16 changes: 16 additions & 0 deletions jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2113,6 +2113,22 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac

# if Data has children, update their visibilities to match Data:
assoc_children = self._get_assoc_data_children(data_label)
available_plugins = [tray_item['name'] for tray_item in self.state.tray_items]
if assoc_children not in viewer.data_labels_loaded:
for child in assoc_children:
self.add_data_to_viewer(viewer.reference, child, visible=visible)

if 'g-data-quality' in available_plugins and visible:
# if we're adding a DQ layer to a viewer, make sure that
# the layer is appropriately colormapped as DQ:
data_quality_plugin = self.get_tray_item_from_name('g-data-quality')
old_viewer = data_quality_plugin.viewer_selected
data_quality_plugin.viewer_selected = viewer.reference
data_quality_plugin.science_layer_selected = data_label
data_quality_plugin.dq_layer_selected = child
data_quality_plugin.init_decoding(viewers=[viewer])
data_quality_plugin.viewer_selected = old_viewer

for layer in viewer.layers:
if layer.layer.data.label in assoc_children:
if visible and not layer.visible:
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/components/viewer_data_select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ module.exports = {
return this.$props.data_items.filter((item) => this.itemIsVisible(item, false))
},
extraDataItems() {
return this.$props.data_items.filter((item) => this.itemIsVisible(item, true))
return this.$props.data_items.filter((item) => this.itemIsVisible(item, true) && !this.isChild(item))
},
nDataEntries() {
// return number of data entries in the entire plugin that were NOT created by a plugin
Expand Down
6 changes: 5 additions & 1 deletion jdaviz/components/viewer_data_select_item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,11 @@ module.exports = {
} else {
return 'gray'
}
}
},
isChild(item) {
// only override multi_select choice when data entry is a child:
return item.parent !== null
},
}
};
</script>
Expand Down
180 changes: 110 additions & 70 deletions jdaviz/configs/default/plugins/data_quality/data_quality.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@
from jdaviz.core.template_mixin import (
PluginTemplateMixin, LayerSelect, ViewerSelectMixin
)
from jdaviz.core.user_api import PluginUserApi
from jdaviz.core.tools import ICON_DIR
from jdaviz.core.user_api import PluginUserApi
from jdaviz.configs.default.plugins.data_quality.dq_utils import (
decode_flags, generate_listed_colormap, dq_flag_map_paths, load_flag_map
)


__all__ = ['DataQuality']

telescope_names = {
Expand Down Expand Up @@ -71,21 +72,37 @@ def __init__(self, *args, **kwargs):

self.load_default_flag_maps()
self.init_decoding()
self._set_irrelevant()
self._update_available_viewers()

if self.app.config == 'cubeviz':
# cubeviz will show the DQ layer in the flux-viewer only:
def is_flux_viewer(viewer):
return viewer.reference == 'flux-viewer'
def _update_available_viewers(self):
if not hasattr(self, 'viewer'):
return

def is_image_viewer(viewer):
return hasattr(viewer, 'active_image_layer')

self.viewer.add_filter(is_flux_viewer)
viewer_filter_names = [filt.__name__ for filt in self.viewer.filters]
if 'is_image_viewer' not in viewer_filter_names:
self.viewer.add_filter(is_image_viewer)
self.viewer._on_viewers_changed()

self._set_irrelevant()
@observe('viewer_selected')
def _on_viewer_change(self, *args):
self._update_available_viewers()
self.update_dq_layer()
self.send_state('decoded_flags')

@observe('dq_layer_items')
def _set_irrelevant(self, *args):
self._update_available_viewers()

children_available = any([
len(assoc['children']) > 0
for label, assoc in getattr(self.app, '_data_associations', {}).items()
])
self.irrelevant_msg = (
'' if len(self.dq_layer_items) else
'' if children_available else
"No Data Quality layers available."
)

Expand Down Expand Up @@ -129,7 +146,7 @@ def update_flag_map_definitions_selected(self, event):
self.flag_map_definitions_selected = selected

@observe('dq_layer_selected')
def init_decoding(self, event={}):
def init_decoding(self, event={}, viewers=None):
if not self.validate_flag_decode_possible:
return

Expand All @@ -140,64 +157,84 @@ def init_decoding(self, event={}):
unique_flags=unique_flags,
rgba_colors=rgba_colors
)
dq_layer = self.get_dq_layer()
dq_layer.composite._allow_bad_alpha = True

# for cubeviz, also change uncert-viewer defaults to
# map the out-of-bounds regions to the cmap's `bad` color:
if self.app.config == 'cubeviz':
uncert_viewer = self.app.get_viewer(
self.app._jdaviz_helper._default_uncert_viewer_reference_name
)
for layer in uncert_viewer.layers:
layer.composite._allow_bad_alpha = True
layer.force_update()
dq_layers = self.get_dq_layers(viewers=viewers)

flag_bits = np.array([flag['flag'] for flag in self.decoded_flags])
for dq_layer in dq_layers:
dq_layer.composite._allow_bad_alpha = True

dq_layer.state.stretch = 'lookup'
stretch_object = dq_layer.state.stretch_object
stretch_object.flags = flag_bits
# for cubeviz, also change uncert-viewer defaults to
# map the out-of-bounds regions to the cmap's `bad` color:
if self.app.config == 'cubeviz':
uncert_viewer = self.app.get_viewer(
self.app._jdaviz_helper._default_uncert_viewer_reference_name
)
for layer in uncert_viewer.layers:
layer.composite._allow_bad_alpha = True
layer.force_update()

flag_bits = np.array([flag['flag'] for flag in self.decoded_flags])

dq_layer.state.stretch = 'lookup'
stretch_object = dq_layer.state.stretch_object
stretch_object.flags = flag_bits

with delay_callback(dq_layer.state, 'alpha', 'cmap', 'v_min', 'v_max'):
if len(flag_bits):
dq_layer.state.v_min = min(flag_bits)
dq_layer.state.v_max = max(flag_bits)
with delay_callback(dq_layer.state, 'alpha', 'cmap', 'v_min', 'v_max'):
if len(flag_bits):
dq_layer.state.v_min = min(flag_bits)
dq_layer.state.v_max = max(flag_bits)

dq_layer.state.alpha = self.dq_layer_opacity
dq_layer.state.cmap = cmap
dq_layer.state.alpha = self.dq_layer_opacity
dq_layer.state.cmap = cmap

def get_dq_layer(self):
def get_dq_layers(self, viewers=None):
if self.dq_layer_selected == '':
return

viewer = self.viewer.selected_obj
[dq_layer] = [
layer for layer in viewer.layers if
if viewers is None:
viewers = self.viewer.selected_obj

if not hasattr(viewers, '__len__'):
viewers = [viewers]

dq_layers = [
layer for viewer in viewers
for layer in viewer.layers if
layer.layer.label == self.dq_layer_selected
]
return dq_layer
return dq_layers

def get_science_layers(self, viewers=None):

def get_science_layer(self):
viewer = self.viewer.selected_obj
[science_layer] = [
layer for layer in viewer.layers if
if viewers is None:
viewers = self.viewer.selected_obj

if not hasattr(viewers, '__len__'):
viewers = [viewers]

science_layers = [
layer for viewer in viewers
for layer in viewer.layers if
layer.layer.label == self.science_layer_selected
]
return science_layer
return science_layers

@observe('dq_layer_opacity')
def update_opacity(self, event={}):
science_layer = self.get_science_layer()
dq_layer = self.get_dq_layer()
science_layers = self.get_science_layers()
dq_layers = self.get_dq_layers()

if dq_layer is not None:
# DQ opacity is a fraction of the science layer's opacity:
dq_layer.state.alpha = self.dq_layer_opacity * science_layer.state.alpha
if dq_layers is not None:
for sci_layer, dq_layer in zip(science_layers, dq_layers):
# DQ opacity is a fraction of the science layer's opacity:
dq_layer.state.alpha = self.dq_layer_opacity * sci_layer.state.alpha

@observe('decoded_flags', 'flags_filter')
def _update_cmap(self, event={}):
dq_layer = self.get_dq_layer()
def _update_cmap(self, event={}, viewers=None):
dq_layers = self.get_dq_layers(viewers=viewers)

if dq_layers is None:
return

Check warning on line 236 in jdaviz/configs/default/plugins/data_quality/data_quality.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/data_quality/data_quality.py#L236

Added line #L236 was not covered by tests

flag_bits = np.array([flag['flag'] for flag in self.decoded_flags])
rgb_colors = [hex2color(flag['color']) for flag in self.decoded_flags]

Expand All @@ -217,28 +254,31 @@ def _update_cmap(self, event={}):
)
])

with delay_callback(dq_layer.state, 'v_min', 'v_max', 'alpha', 'stretch', 'cmap'):
# set correct stretch and limits:
# dq_layer.state.stretch = 'lookup'
stretch_object = dq_layer.state.stretch_object
stretch_object.flags = flag_bits
stretch_object.dq_array = dq_layer.get_image_data()
stretch_object.hidden_flags = hidden_flags

# update the colors of the listed colormap without
# reassigning the layer.state.cmap object
cmap = dq_layer.state.cmap
cmap.colors = rgb_colors
cmap._init()

# trigger updates to cmap in viewer:
dq_layer.update()

if len(flag_bits):
dq_layer.state.v_min = min(flag_bits)
dq_layer.state.v_max = max(flag_bits)

dq_layer.state.alpha = self.dq_layer_opacity
for dq_layer in dq_layers:
with delay_callback(
dq_layer.state, 'v_min', 'v_max', 'alpha', 'stretch', 'cmap'
):
# set correct stretch and limits:
# dq_layer.state.stretch = 'lookup'
stretch_object = dq_layer.state.stretch_object
stretch_object.flags = flag_bits
stretch_object.dq_array = dq_layer.get_image_data()
stretch_object.hidden_flags = hidden_flags

# update the colors of the listed colormap without
# reassigning the layer.state.cmap object
cmap = dq_layer.state.cmap
cmap.colors = rgb_colors
cmap._init()

# trigger updates to cmap in viewer:
dq_layer.update()

if len(flag_bits):
dq_layer.state.v_min = min(flag_bits)
dq_layer.state.v_max = max(flag_bits)

dq_layer.state.alpha = self.dq_layer_opacity

def update_visibility(self, index):
self.decoded_flags[index]['show'] = not self.decoded_flags[index]['show']
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<j-tray-plugin
:description="docs_description || 'Viewer and data/layer options.'"
:description="docs_description || 'Data Quality layer visualization options.'"
:link="docs_link || 'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#data-quality'"
@plugin-ping="plugin_ping($event)"
:popout_button="popout_button">
Expand Down
3 changes: 2 additions & 1 deletion jdaviz/configs/default/plugins/viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def data_labels_loaded(self):
"""
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()]
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]

@property
def data_labels_visible(self):
Expand Down
13 changes: 8 additions & 5 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,14 @@ def _image_viewer_update(self, viewer, x, y):
image = None

# If there is one, get the associated DQ layer for the active layer:
associated_dq_layer = None
associated_dq_layers = None
available_plugins = [tray_item['name'] for tray_item in self.app.state.tray_items]
if 'g-data-quality' in available_plugins:
assoc_children = self.app._get_assoc_data_children(active_layer.layer.label)
if assoc_children:
data_quality_plugin = self.app.get_tray_item_from_name('g-data-quality')
associated_dq_layer = data_quality_plugin.get_dq_layer()
viewer_obj = self.app.get_viewer(viewer)
associated_dq_layers = data_quality_plugin.get_dq_layers(viewer_obj)

unreliable_pixel, unreliable_world = False, False

Expand Down Expand Up @@ -444,7 +445,8 @@ def _image_viewer_update(self, viewer, x, y):

if isinstance(viewer, (ImvizImageView, MosvizImageView, MosvizProfile2DView)):
value = image.get_data(attribute)[int(round(y)), int(round(x))]
if associated_dq_layer is not None:
if associated_dq_layers is not None:
associated_dq_layer = associated_dq_layers[0]
dq_attribute = associated_dq_layer.state.attribute
dq_data = associated_dq_layer.layer.get_data(dq_attribute)
dq_value = dq_data[int(round(y)), int(round(x))]
Expand All @@ -454,14 +456,15 @@ def _image_viewer_update(self, viewer, x, y):
unit = image.get_component(attribute).units
value = self._get_cube_value(image, arr, x, y, viewer)

if associated_dq_layer is not None:
if associated_dq_layers is not None:
associated_dq_layer = associated_dq_layers[0]
dq_attribute = associated_dq_layer.state.attribute
dq_data = associated_dq_layer.layer.get_data(dq_attribute)
dq_value = self._get_cube_value(image, dq_data, x, y, viewer)

Check warning on line 463 in jdaviz/configs/imviz/plugins/coords_info/coords_info.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/imviz/plugins/coords_info/coords_info.py#L460-L463

Added lines #L460 - L463 were not covered by tests

self.row1b_title = 'Value'

if associated_dq_layer is not None:
if associated_dq_layers is not None:
if np.isnan(dq_value):
dq_text = ''
else:
Expand Down

0 comments on commit 8118066

Please sign in to comment.