Skip to content

Commit

Permalink
working on initial DQ plugin UI
Browse files Browse the repository at this point in the history
  • Loading branch information
bmorris3 committed Mar 22, 2024
1 parent 2828292 commit e19977a
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 11 deletions.
2 changes: 1 addition & 1 deletion jdaviz/configs/default/default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ toolbar:
tray:
- g-subset-plugin
- g-gaussian-smooth
- export
- export
1 change: 1 addition & 0 deletions jdaviz/configs/default/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from .export.export import * # noqa
from .plot_options.plot_options import * # noqa
from .markers.markers import * # noqa
from .data_quality.data_quality import * # noqa
2 changes: 1 addition & 1 deletion jdaviz/configs/default/plugins/data_quality/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .dq_utils import * # noqa
from .data_quality import * # noqa
197 changes: 197 additions & 0 deletions jdaviz/configs/default/plugins/data_quality/data_quality.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import os
from traitlets import Any, Dict, Bool, List, Unicode, observe

import numpy as np
from glue_jupyter.common.toolbar_vuetify import read_icon
from echo import delay_callback
from matplotlib.colors import hex2color

from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import (
PluginTemplateMixin, ViewerSelect, LayerSelect
)
from jdaviz.core.tools import ICON_DIR
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 = {
"jwst": "JWST",
"roman": "Roman"
}


@tray_registry('g-data-quality', label="Data Quality", viewer_requirements="image")
class DataQuality(PluginTemplateMixin):
template_file = __file__, "data_quality.vue"

viewer_multiselect = Bool(False).tag(sync=True)
viewer_items = List().tag(sync=True)
viewer_selected = Any().tag(sync=True) # Any needed for multiselect
viewer_limits = Dict().tag(sync=True)

# `layer` is the science data layer
science_layer_multiselect = Bool(False).tag(sync=True)
science_layer_items = List().tag(sync=True)
science_layer_selected = Any().tag(sync=True) # Any needed for multiselect

# `dq_layer` is teh data quality layer corresponding to the
# science data in `layer`
dq_layer_multiselect = Bool(False).tag(sync=True)
dq_layer_items = List().tag(sync=True)
dq_layer_selected = Any().tag(sync=True) # Any needed for multiselect

flag_map_definitions = Dict().tag(sync=True)
flag_map_selected = Any().tag(sync=True)
flag_map_items = List().tag(sync=True)
viewer_selected = Any().tag(sync=True) # Any needed for multiselect
decoded_flags = List().tag(sync=True)

icons = Dict().tag(sync=True)
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

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

self.icons = {k: v for k, v in self.app.state.icons.items()}

self.viewer = ViewerSelect(
self, 'viewer_items', 'viewer_selected', 'viewer_multiselect'
)
self.science_layer = LayerSelect(
self, 'science_layer_items', 'science_layer_selected',
'viewer_selected', 'science_layer_multiselect', is_root=True
)

self.dq_layer = LayerSelect(
self, 'dq_layer_items', 'dq_layer_selected',
'viewer_selected', 'dq_layer_multiselect', is_root=False,
is_child_of=self.science_layer.selected
)

self.load_default_flag_maps()
self.init_decoding()

@observe('science_layer_selected')
def update_dq_layer(self, *args):
if not hasattr(self, 'dq_layer'):
return

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

def load_default_flag_maps(self):
for name in dq_flag_map_paths:
self.flag_map_definitions[name] = load_flag_map(name)
self.flag_map_items = self.flag_map_items + [telescope_names[name]]

@property
def multiselect(self):
logging.warning(f"DeprecationWarning: multiselect has been replaced by separate viewer_multiselect and layer_multiselect and will be removed in the future. This currently evaluates viewer_multiselect or layer_multiselect") # noqa
return self.viewer_multiselect or self.layer_multiselect

Check warning on line 94 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#L93-L94

Added lines #L93 - L94 were not covered by tests

@multiselect.setter
def multiselect(self, value):
logging.warning(f"DeprecationWarning: multiselect has been replaced by separate viewer_multiselect and layer_multiselect and will be removed in the future. This currently sets viewer_multiselect and layer_multiselect") # noqa
self.viewer_multiselect = value
self.layer_multiselect = value

Check warning on line 100 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#L98-L100

Added lines #L98 - L100 were not covered by tests

def vue_set_value(self, data):
attr_name = data.get('name')
value = data.get('value')
setattr(self, attr_name, value)

Check warning on line 105 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#L103-L105

Added lines #L103 - L105 were not covered by tests

@property
def unique_flags(self):
selected_dq = self.dq_layer.selected_obj
if not len(selected_dq):
return []

Check warning on line 111 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#L109-L111

Added lines #L109 - L111 were not covered by tests

dq = selected_dq[0].get_image_data()
return np.unique(dq[~np.isnan(dq)])

Check warning on line 114 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#L113-L114

Added lines #L113 - L114 were not covered by tests

@property
def flag_map_definitions_selected(self):
return self.flag_map_definitions[self.flag_map_selected.lower()]

Check warning on line 118 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#L118

Added line #L118 was not covered by tests

@property
def validate_flag_decode_possible(self):
return (
self.flag_map_selected is not None and
len(self.dq_layer.selected_obj) > 0
)

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

unique_flags = self.unique_flags
cmap, rgba_colors = generate_listed_colormap(n_flags=len(unique_flags))
self.decoded_flags = decode_flags(

Check warning on line 134 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#L132-L134

Added lines #L132 - L134 were not covered by tests
flag_map=self.flag_map_definitions_selected,
unique_flags=unique_flags,
rgba_colors=rgba_colors
)

viewer = self.viewer.selected_obj
[dq_layer] = [

Check warning on line 141 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#L140-L141

Added lines #L140 - L141 were not covered by tests
layer for layer in viewer.layers if
layer.layer.label == self.dq_layer_selected
]
dq_layer.composite._allow_bad_alpha = True

Check warning on line 145 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#L145

Added line #L145 was not covered by tests

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

Check warning on line 147 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#L147

Added line #L147 was not covered by tests

with delay_callback(dq_layer.state, 'alpha', 'cmap', 'v_min', 'v_max', 'stretch'):
dq_layer.state.cmap = cmap

Check warning on line 150 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#L149-L150

Added lines #L149 - L150 were not covered by tests

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

Check warning on line 154 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#L152-L154

Added lines #L152 - L154 were not covered by tests

dq_layer.state.v_min = min(flag_bits)
dq_layer.state.v_max = max(flag_bits)
dq_layer.state.alpha = 0.9

Check warning on line 158 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#L156-L158

Added lines #L156 - L158 were not covered by tests

@observe('decoded_flags')
def update_cmap(self, event={}):
viewer = self.viewer.selected_obj
[dq_layer] = [

Check warning on line 163 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#L162-L163

Added lines #L162 - L163 were not covered by tests
layer for layer in viewer.layers if
layer.layer.label == self.dq_layer_selected
]
flag_bits = np.float32([flag['flag'] for flag in self.decoded_flags])
rgb_colors = [hex2color(flag['color']) for flag in self.decoded_flags]

Check warning on line 168 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#L167-L168

Added lines #L167 - L168 were not covered by tests

# 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()

Check warning on line 174 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#L172-L174

Added lines #L172 - L174 were not covered by tests

with delay_callback(dq_layer.state, 'v_min', 'v_max', 'alpha'):

Check warning on line 176 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#L176

Added line #L176 was not covered by tests
# trigger updates to cmap in viewer:
dq_layer.update()

Check warning on line 178 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#L178

Added line #L178 was not covered by tests

# set correct stretch and limits:
dq_layer.state.stretch = 'lookup'
dq_layer.state.v_min = min(flag_bits)
dq_layer.state.v_max = max(flag_bits)
dq_layer.state.alpha = 0.9

Check warning on line 184 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#L181-L184

Added lines #L181 - L184 were not covered by tests

@observe('science_layer_selected')
def mission_or_instrument_from_meta(self, event):
if not hasattr(self, 'science_layer'):
return

layer = self.science_layer.selected_obj
if len(layer):
# this is defined for JWST and ROMAN, should be upper case:
telescope = layer[0].layer.meta.get('telescope', None)

if telescope is not None:
self.flag_map_selected = telescope_names[telescope.lower()]
124 changes: 124 additions & 0 deletions jdaviz/configs/default/plugins/data_quality/data_quality.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<template>
<j-tray-plugin
:description="docs_description || 'Viewer and data/layer options.'"
:link="docs_link || 'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#plot-options'"
@plugin-ping="plugin_ping($event)"
:popout_button="popout_button">

<!-- VIEWER OPTIONS -->
<plugin-viewer-select
:items="viewer_items"
:selected.sync="viewer_selected"
:multiselect.sync="viewer_multiselect"
:show_multiselect_toggle="viewer_multiselect || viewer_items.length > 1"
:icon_checktoradial="icon_checktoradial"
:icon_radialtocheck="icon_radialtocheck"
:label="viewer_multiselect ? 'Viewers' : 'Viewer'"
:show_if_single_entry="viewer_multiselect"
:hint="viewer_multiselect ? 'Select viewers to set options simultaneously' : 'Select the viewer to set options.'"
/>

<plugin-layer-select
:items="science_layer_items"
:selected.sync="science_layer_selected"
:multiselect="science_layer_multiselect"
:icons="icons"
:show_if_single_entry="true"
:label="'Science data'"
:hint="'Select the science data'"
/>

<plugin-layer-select
:items="dq_layer_items"
:selected.sync="dq_layer_selected"
:multiselect="dq_layer_multiselect"
:label="'Data quality'"
:show_if_single_entry="true"
:hint="'Select the data quality'"
:icons="icons"
/>

<v-row>
<v-select
attach
:menu-props="{ left: true }"
:items="flag_map_items"
v-model="flag_map_selected"
label="Flag definitions"
></v-select>
</v-row>

<j-plugin-section-header>Quality Flags</j-plugin-section-header>
<v-row style="max-width: calc(100% - 80px)">
<v-col>
Color
</v-col>
<v-col>
<strong>Flag</strong>
</v-col>
<v-col>
(Decomposed)
</v-col>
</v-row>
<v-row>
<v-expansion-panels accordion>
<v-expansion-panel v-for="(item, index) in decoded_flags" key=":item">
<v-expansion-panel-header v-slot="{ open }">
<v-row no-gutters align="center" style="...">
<v-col cols=1>
</v-col>
<v-col cols=2>
<j-tooltip tipid='plugin-line-lists-color-picker'>
<v-menu>
<template v-slot:activator="{ on }">
<span class="color-menu"
:style="`background:${item.color}; cursor: pointer`"
@click.stop="on.click"
> </span>
</template>
<div @click.stop="" style="text-align: end; background-color: white">
<v-color-picker v-model="decoded_flags[index].color"
@update:color="throttledSetColor($event.hexa)">
></v-color-picker>
</div>
</v-menu>
</j-tooltip>
</v-col>
<v-col>
<div> <strong>{{item.flag}}</strong> ({{Object.keys(item.decomposed).join(', ')}})</div>
</v-col>
</v-row>
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-col v-for="(item, key, index) in item.decomposed">
<span>{{item.name}} ({{key}}): {{item.description}}</span>
</v-col>
</v-expansion-panel-content>
<v-expansion-panel>
</v-expansion-panels>
</v-row>
</j-tray-plugin>
</template>


<script>
module.exports = {
created() {
this.throttledSetColor = _.throttle(
(v) => { this.color = v },
100);
}
}
</script>

<style>
.v-slider {
margin: 0px !important;
}
.color-menu {
font-size: 16px;
padding-left: 16px;
border: 2px solid rgba(0,0,0,0.54);
}
</style>
8 changes: 4 additions & 4 deletions jdaviz/configs/default/plugins/data_quality/dq_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def load_flag_map(mission_or_instrument=None, path=None):

flag_mapping = {}
for flag, name, desc in flag_table.iterrows():
flag_mapping[flag] = dict(name=name, description=desc)
flag_mapping[int(flag)] = dict(name=name, description=desc)

return flag_mapping

Expand Down Expand Up @@ -77,7 +77,7 @@ def write_flag_map(flag_mapping, csv_path, **kwargs):
table.write(csv_path, format='ascii.csv', **kwargs)


def generate_listed_colormap(n_flags, seed=42):
def generate_listed_colormap(n_flags=None, seed=3):
"""
Generate a list of random "light" colors of length ``n_flags``.
Expand All @@ -103,14 +103,14 @@ def generate_listed_colormap(n_flags, seed=42):
# Generate random colors that are generally "light", i.e. with
# RGB values in the upper half of the interval (0, 1):
rgba_colors = [

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

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/data_quality/dq_utils.py#L105

Added line #L105 was not covered by tests
tuple(rng.uniform(low=0.5, high=1, size=3).tolist() + [default_alpha])
tuple(np.insert(rng.uniform(size=2), rng.integers(0, 3), 1).tolist() + [default_alpha])
for _ in range(n_flags)
]

cmap = ListedColormap(rgba_colors)

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

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/data_quality/dq_utils.py#L110

Added line #L110 was not covered by tests

# setting `bad` alpha=0 will make NaNs transparent:
cmap.set_bad(alpha=0)
cmap.set_bad(color='k', alpha=0)
return cmap, rgba_colors

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

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/default/plugins/data_quality/dq_utils.py#L113-L114

Added lines #L113 - L114 were not covered by tests


Expand Down
Loading

0 comments on commit e19977a

Please sign in to comment.