Skip to content

Commit

Permalink
implement associations between Data layers
Browse files Browse the repository at this point in the history
  • Loading branch information
bmorris3 committed Mar 19, 2024
1 parent deda388 commit 9d378d2
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 11 deletions.
63 changes: 60 additions & 3 deletions jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,9 @@ def __init__(self, configuration=None, *args, **kwargs):
self.hub.subscribe(self, SubsetUpdateMessage,
handler=lambda msg: self._clear_object_cache(msg.subset.label))

# Store for associations between Data entries:
self._data_associations = self._init_data_associations()

# Subscribe to messages that result in changes to the layers
self.hub.subscribe(self, AddDataMessage,
handler=self._on_layers_changed)
Expand Down Expand Up @@ -473,9 +476,11 @@ def _on_layers_changed(self, msg):
if hasattr(msg, 'data'):
layer_name = msg.data.label
is_wcs_only = msg.data.meta.get(_wcs_only_label, False)
is_not_child = self._get_assoc_data_parent(layer_name) is None
elif hasattr(msg, 'subset'):
layer_name = msg.subset.label
is_wcs_only = False
is_not_child = True
else:
raise NotImplementedError(f"cannot recognize new layer from {msg}")

Expand All @@ -490,13 +495,25 @@ def _on_layers_changed(self, msg):
self.state.layer_icons = {**self.state.layer_icons,
layer_name: orientation_icons.get(layer_name,
wcs_only_refdata_icon)}
else:
elif is_not_child:
self.state.layer_icons = {
**self.state.layer_icons,
layer_name: alpha_index(len([ln for ln, ic in self.state.layer_icons.items()
if not ic.startswith('mdi-')]))
if not ic.startswith('mdi-') and
self._get_assoc_data_parent(ln) is None]))
}

# all remaining layers at this point have a parent:
for layer_name in self.state.layer_icons:
children_layers = self._get_assoc_data_children(layer_name)
if children_layers is not None:
parent_icon = self.state.layer_icons[layer_name]
for i, child_layer in enumerate(children_layers, start=1):
self.state.layer_icons = {

Check warning on line 512 in jdaviz/app.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/app.py#L512

Added line #L512 was not covered by tests
**self.state.layer_icons,
child_layer: f'{parent_icon}{i}'
}

def _change_reference_data(self, new_refdata_label, viewer_id=None):
"""
Change reference data to Data with ``data_label``.
Expand Down Expand Up @@ -1251,7 +1268,7 @@ def merge_overlapping_spectral_regions(self, subset_name, att):

return new_state

def add_data(self, data, data_label=None, notify_done=True):
def add_data(self, data, data_label=None, notify_done=True, parent=None):
"""
Add data to the Glue ``DataCollection``.
Expand Down Expand Up @@ -1280,6 +1297,11 @@ def add_data(self, data, data_label=None, notify_done=True):

self.data_collection[data_label] = data

# manage associated Data entries:
self._add_assoc_data_as_parent(data_label)
if parent is not None:
self._set_assoc_data_as_child(data_label, new_parent_label=parent)

Check warning on line 1303 in jdaviz/app.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/app.py#L1303

Added line #L1303 was not covered by tests

# Send out a toast message
if notify_done:
snackbar_message = SnackbarMessage(
Expand Down Expand Up @@ -1998,6 +2020,17 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac
if layer.layer.data.label != data_label:
layer.visible = False

# if Data has children, update their visibilities to match Data:
assoc_children = self._get_assoc_data_children(data_label)
for layer in viewer.layers:
for data_label in assoc_children:
if layer.layer.data.label == data_label:
if visible and not layer.visible:
layer.visible = True
layer.update()

Check warning on line 2030 in jdaviz/app.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/app.py#L2027-L2030

Added lines #L2027 - L2030 were not covered by tests
else:
layer.visible = visible

Check warning on line 2032 in jdaviz/app.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/app.py#L2032

Added line #L2032 was not covered by tests

# update data menu - selected_data_items should be READ ONLY, not modified by the user/UI
selected_items = viewer_item['selected_data_items']
data_id = self._data_id_from_label(data_label)
Expand Down Expand Up @@ -2577,3 +2610,27 @@ def get_tray_item_from_name(self, name):
raise KeyError(f'{name} not found in app.state.tray_items')

return tray_item

def _init_data_associations(self):
# assume all Data are parents:
data_associations = {
data.label: {'parent': None, 'children': []}
for data in self.data_collection
}
return data_associations

def _add_assoc_data_as_parent(self, data_label):
self._data_associations[data_label] = {'parent': None, 'children': []}

def _set_assoc_data_as_child(self, data_label, new_parent_label):
# Data has a new parent:
self._data_associations[data_label]['parent'] = new_parent_label

Check warning on line 2627 in jdaviz/app.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/app.py#L2627

Added line #L2627 was not covered by tests
# parent has a new child:
self._data_associations[new_parent_label]['children'].append(data_label)

Check warning on line 2629 in jdaviz/app.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/app.py#L2629

Added line #L2629 was not covered by tests

def _get_assoc_data_children(self, data_label):
# intentionally not recursive for now, just one generation:
return self._data_associations.get(data_label, {}).get('children', [])

def _get_assoc_data_parent(self, data_label):
return self._data_associations.get(data_label, {}).get('parent')
16 changes: 8 additions & 8 deletions jdaviz/configs/imviz/plugins/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@


@data_parser_registry("imviz-data-parser")
def parse_data(app, file_obj, ext=None, data_label=None):
def parse_data(app, file_obj, ext=None, data_label=None, parent=None):
"""Parse a data file into Imviz.
Parameters
Expand Down Expand Up @@ -74,27 +74,27 @@ def parse_data(app, file_obj, ext=None, data_label=None):
else: # Assume RGB
pf = rgb2gray(im)
pf = pf[::-1, :] # Flip it
_parse_image(app, pf, data_label, ext=ext)
_parse_image(app, pf, data_label, ext=ext, parent=parent)

elif file_obj_lower.endswith('.asdf'):
try:
if HAS_ROMAN_DATAMODELS:
with rdd.open(file_obj) as pf:
_parse_image(app, pf, data_label, ext=ext)
_parse_image(app, pf, data_label, ext=ext, parent=parent)

Check warning on line 83 in jdaviz/configs/imviz/plugins/parsers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/imviz/plugins/parsers.py#L83

Added line #L83 was not covered by tests
except TypeError:
# if roman_datamodels cannot parse the file, load it with asdf:
with asdf.open(file_obj) as af:
_parse_image(app, af, data_label, ext=ext)
_parse_image(app, af, data_label, ext=ext, parent=parent)

Check warning on line 87 in jdaviz/configs/imviz/plugins/parsers.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/imviz/plugins/parsers.py#L87

Added line #L87 was not covered by tests

elif file_obj_lower.endswith('.reg'):
# This will load DS9 regions as Subset but only if there is already data.
app._jdaviz_helper.load_regions_from_file(file_obj)

else: # Assume FITS
with fits.open(file_obj) as pf:
_parse_image(app, pf, data_label, ext=ext)
_parse_image(app, pf, data_label, ext=ext, parent=parent)
else:
_parse_image(app, file_obj, data_label, ext=ext)
_parse_image(app, file_obj, data_label, ext=ext, parent=parent)


def get_image_data_iterator(app, file_obj, data_label, ext=None):
Expand Down Expand Up @@ -168,7 +168,7 @@ def get_image_data_iterator(app, file_obj, data_label, ext=None):
return data_iter


def _parse_image(app, file_obj, data_label, ext=None):
def _parse_image(app, file_obj, data_label, ext=None, parent=None):
if app is None:
raise ValueError("app is None, cannot proceed")
if data_label is None:
Expand All @@ -186,7 +186,7 @@ def _parse_image(app, file_obj, data_label, ext=None):
data.coords.bounding_box = None
if not data.meta.get(_wcs_only_label, False):
data_label = app.return_data_label(data_label, alt_name="image_data")
app.add_data(data, data_label)
app.add_data(data, data_label, parent=parent)

# Do not link image data here. We do it at the end in Imviz.load_data()

Expand Down
12 changes: 12 additions & 0 deletions jdaviz/core/template_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1365,6 +1365,12 @@ def __init__(self, plugin, items, selected, viewer,
self._update_layer_items()
self.update_wcs_only_filter(only_wcs_layers)

# ignore layers that are children in associations:
def is_parent(data):
return self.app._get_assoc_data_parent(data.label) is None

self.add_filter(is_parent)

def _get_viewer(self, viewer):
# newer will likely be the viewer name in most cases, but viewer id in the case
# of additional viewers in imviz.
Expand Down Expand Up @@ -2923,6 +2929,12 @@ def __init__(self, plugin, items, selected,
# initialize items from original viewers
self._on_data_changed()

# ignore layers that are children in associations:
def is_parent(data):
return self.app._get_assoc_data_parent(data.label) is None

self.add_filter(is_parent)

def _cubeviz_include_spatial_subsets(self):
"""
Call this method to prepend spatial subsets to the list of datasets (and listen for newly
Expand Down

0 comments on commit 9d378d2

Please sign in to comment.