Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cubeviz spectral extraction through plugin #2827

Merged
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
49d499a
do not load flux cube into profile viewer from parser
kecnry Apr 23, 2024
ec00996
remove cubes from spectrum-viewer data menu
kecnry Apr 23, 2024
a7bcc2c
remove collapse_function from plot options
kecnry Apr 24, 2024
d693cd0
extract entire cube during load
kecnry Apr 24, 2024
dcca4c6
include collapse function in default label
kecnry Apr 24, 2024
f3d8c31
create auto-updating extraction when creating valid subset apertures
kecnry Apr 24, 2024
1d5f470
implement and use skip_if_not_tray_instance decorator
kecnry Apr 24, 2024
eb48a9a
fix initial slice location
kecnry Apr 24, 2024
0aa82cd
remove ShadowSpatialSpectral implementation
kecnry Apr 24, 2024
8d2c6e5
move plugin higher in tray
kecnry Apr 24, 2024
09525b2
pin min-requirements for spectral extraction
kecnry Apr 24, 2024
de94ef2
remove more unneeded (hopefully) code related to spectral-spatial subset
kecnry Apr 24, 2024
1753636
allow unloading extracted cube from spectrum-viewer
kecnry Apr 24, 2024
5ccb995
update plot legend
kecnry Apr 24, 2024
64909ff
spatial subset excluded as layer from plot options for spectrum-viewer
kecnry Apr 24, 2024
69249f9
remove spatial-subset from line analysis plugin
kecnry Apr 24, 2024
1d5b1ed
remove unused imports
kecnry Apr 24, 2024
08861b2
remove spatial-subset from plot options and layer cycler
kecnry Apr 24, 2024
5e1d353
ensure add_filter triggers update to choices
kecnry Apr 25, 2024
e7f4e10
update gaussian smooth
kecnry Apr 25, 2024
e7fb1db
model fitting: remove cube fit from results table and default labels
kecnry Apr 25, 2024
e1c8a32
remove handling for spectrum-viewer x-axis temporarily being in degs
kecnry Apr 25, 2024
8d18167
get_data: deprecate support for spatial_subset and function
kecnry Apr 25, 2024
b55ef9e
remove more unused code
kecnry Apr 25, 2024
3ef4aaf
spectral extraction: adopt default color based on input aperture color
kecnry Apr 25, 2024
410f5ca
delete auto-updating products when input subset/data is deleted
kecnry Apr 25, 2024
78a5615
fix typos in deprecation messages
kecnry Apr 26, 2024
816ce47
WIP: fix/update tests
kecnry Apr 26, 2024
eccd413
allow no wcs in spectral extraction
kecnry Apr 29, 2024
a7f5400
Merge remote-tracking branch 'spacetelescope/main' into cubeviz-spec-…
kecnry Apr 29, 2024
3f39c8a
remove now-irrelevant tests
kecnry Apr 29, 2024
bf6864e
update tests
kecnry Apr 29, 2024
862d74e
simplify/optimize slice caching logic
kecnry Apr 29, 2024
c488a99
update tests
kecnry Apr 29, 2024
7f62fb5
update cubeviz tests to load data through parser
kecnry Apr 29, 2024
fc74ccc
do not attempt auto-extract when no cube loaded
kecnry Apr 30, 2024
9c515f3
update cubeviz.specviz.get_data() and tests
kecnry Apr 30, 2024
04b6779
get_data: REMOVE support for function/spatial_subset
kecnry May 1, 2024
b49f083
update tests
kecnry May 1, 2024
1fb2015
moment maps: update to choose continuum_dataset
kecnry May 1, 2024
a39bb5a
model fitting: allow initializing parameters for any cube
kecnry May 2, 2024
43c0631
aperture select: force updating mark preview when opening plugin
kecnry May 2, 2024
d9fd5a9
fix cache clearing for slice
kecnry May 2, 2024
497810e
treat pixels and dimensionless as an equivalency
kecnry May 2, 2024
abc433e
update tests
kecnry May 2, 2024
1336f16
update hint language for moment maps
kecnry May 2, 2024
13b896a
update cubeviz example notebook
kecnry May 2, 2024
386b6e7
update export data docs
kecnry May 2, 2024
1931f86
Apply suggestions from code review
kecnry May 3, 2024
2d96aeb
fix auto-updating to work more than once
kecnry May 3, 2024
21b7630
Apply suggestions from code review
kecnry May 3, 2024
e6f38ad
remove instead of deprecate plugin user-API entries
kecnry May 3, 2024
027cc13
generalize divide-by-zero avoidance check
kecnry May 3, 2024
e4cdb35
remove already-deprecated spatial subset from spectral extraction
kecnry May 3, 2024
72cc2e9
clear selected_spectrum cache on display unit change
kecnry May 3, 2024
ff48983
Merge remote-tracking branch 'spacetelescope/main' into cubeviz-spec-…
kecnry May 3, 2024
b97a327
fix case of deleting spatial subset
kecnry May 6, 2024
ae2e81e
fix case of changing subset bound through plugin after unit conversion
kecnry May 6, 2024
a2fa931
Merge branch 'main' into cubeviz-spec-extract-through-plugin
kecnry May 7, 2024
ad8dbf3
changelog entries
kecnry May 7, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions docs/cubeviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,6 @@ Spectral Extraction

.. image:: ../img/cubeviz_spectral_extraction.png

.. note::

Spectral Extraction requires at least version 5.3.2 of astropy.

The Spectral Extraction plugin produces a 1D spectrum from a spectral
cube. The 1D spectrum can be computed via the sum, mean, minimum, or
Expand Down
44 changes: 31 additions & 13 deletions jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,8 @@ def __init__(self, configuration=None, *args, **kwargs):
self._get_object_cache = {}
self.hub.subscribe(self, SubsetUpdateMessage,
handler=self._on_subset_update_message)
self.hub.subscribe(self, SubsetDeleteMessage,
handler=self._on_subset_delete_message)

# Store for associations between Data entries:
self._data_associations = self._init_data_associations()
Expand All @@ -378,8 +380,7 @@ def __init__(self, configuration=None, *args, **kwargs):
handler=self._on_layers_changed)
self.hub.subscribe(self, SubsetCreateMessage,
handler=self._on_layers_changed)
self.hub.subscribe(self, SubsetDeleteMessage,
handler=self._on_layers_changed)
# SubsetDeleteMessage will also call _on_layers_changed via _on_subset_delete_message

def _on_plugin_table_added(self, msg):
if msg.plugin._plugin_name is None:
Expand All @@ -388,7 +389,7 @@ def _on_plugin_table_added(self, msg):
key = f"{msg.plugin._plugin_name}: {msg.table._table_name}"
self._plugin_tables.setdefault(key, msg.table.user_api)

def _update_live_plugin_results(self, trigger_data_lbl=None, trigger_subset=None):
def _iter_live_plugin_results(self, trigger_data_lbl=None, trigger_subset=None):
trigger_subset_lbl = trigger_subset.label if trigger_subset is not None else None
for data in self.data_collection:
plugin_inputs = data.meta.get('_update_live_plugin_results', None)
Expand All @@ -410,6 +411,10 @@ def _update_live_plugin_results(self, trigger_data_lbl=None, trigger_subset=None
for attr in data_subs]):
# trigger parent data of subset does not match subscribed data entries
continue
yield (data, plugin_inputs)

def _update_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):
# update and overwrite data
# make a new instance of the plugin to avoid changing any UI settings
plg = self._jdaviz_helper.plugins.get(data.meta.get('Plugin'))._obj.new()
Expand All @@ -422,6 +427,15 @@ 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})

def _on_add_data_message(self, msg):
self._on_layers_changed(msg)
Expand All @@ -433,6 +447,10 @@ def _on_subset_update_message(self, msg):
if msg.attribute == 'subset_state':
self._update_live_plugin_results(trigger_subset=msg.subset)

def _on_subset_delete_message(self, msg):
self._remove_live_plugin_results(trigger_subset=msg.subset)
self._on_layers_changed(msg)

def _on_plugin_plot_added(self, msg):
if msg.plugin._plugin_name is None:
# plugin was instantiated after the app was created, ignore
Expand Down Expand Up @@ -2078,7 +2096,14 @@ def set_data_visibility(self, viewer_reference, data_label, visible=True, replac

data = self.data_collection[data_label]

viewer.add_data(data, percentile=95, color=viewer.color_cycler())
# set the original color based on metadata preferences, if provided, and otherwise
# based on the colorcycler
# NOTE: this is intentionally not a single line to avoid incrementing the color-cycler
# unless it is used
color = data.meta.get('_default_color')
if color is None:
color = viewer.color_cycler()
viewer.add_data(data, percentile=95, color=color)

# Specviz removes the data from collection in viewer.py if flux unit incompatible.
if data_label not in self.data_collection:
Expand Down Expand Up @@ -2248,13 +2273,6 @@ def _on_data_deleted(self, msg):
if data_item['name'] == msg.data.label:
self.state.data_items.remove(data_item)

# TODO: Fix bug with DataCollectionDeleteMessage not working with
# a handler in cubeviz/plugins/viewers.py. This code is a temporary
# workaround for that.
if self.config == 'cubeviz':
viewer = self.get_viewer(self._jdaviz_helper._default_spectrum_viewer_reference_name)
viewer._check_if_data_removed(msg=msg)

self._clear_object_cache(msg.data.label)

def _create_data_item(self, data):
Expand Down Expand Up @@ -2410,7 +2428,7 @@ def _create_viewer_item(self, viewer, vid=None, name=None, reference=None,
'layer_options': "IPY_MODEL_" + viewer.layer_options.model_id,
'viewer_options': "IPY_MODEL_" + viewer.viewer_options.model_id,
'selected_data_items': {}, # noqa data_id: visibility state (visible, hidden, mixed), READ-ONLY
'visible_layers': {}, # label: {color, label_suffix}, READ-ONLY
'visible_layers': {}, # label: {color}, READ-ONLY
'wcs_only_layers': wcs_only_layers,
'reference_data_label': reference_data_label,
'canvas_angle': 0, # canvas rotation clockwise rotation angle in deg
Expand Down Expand Up @@ -2617,7 +2635,7 @@ def compose_viewer_area(viewer_area_items):
for name in config.get('tray', []):
tray = tray_registry.members.get(name)

tray_item_instance = tray.get('cls')(app=self)
tray_item_instance = tray.get('cls')(app=self, tray_instance=True)

# store a copy of the tray name in the instance so it can be accessed by the
# plugin itself
Expand Down
8 changes: 2 additions & 6 deletions jdaviz/components/viewer_data_select.vue
Original file line number Diff line number Diff line change
Expand Up @@ -208,12 +208,8 @@ module.exports = {
} else if (this.$props.viewer.config === 'cubeviz') {
if (this.$props.viewer.reference === 'spectrum-viewer') {
if (item.meta.Plugin === undefined) {
// then the data can be a cube (auto-collapsed) as long as its the flux data
// if this logic moves to python, we could check directly against reference data instead
return (item.name.indexOf('[FLUX]') !== -1 || item.name.indexOf('[SCI]') !== -1) && this.dataItemInViewer(item, returnExtraItems)
} else if (item.meta.Plugin === 'GaussianSmooth') {
// spectrally smoothed would still be a collapsible cube
return item.ndims === 3 && this.dataItemInViewer(item, returnExtraItems)
// then only allow 1d spectra (not cubes or images)
return item.ndims === 1
} else {
// filter plugin results to only those that are spectra
return item.ndims === 1 && this.dataItemInViewer(item, returnExtraItems)
Expand Down
2 changes: 0 additions & 2 deletions jdaviz/components/viewer_data_select_item.vue
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,6 @@ module.exports = {
return ['IVAR', 'ERR'].indexOf(extension) !== -1
} else if (this.$props.viewer.reference === 'mask-viewer') {
return ['MASK', 'DQ'].indexOf(extension) !== -1
} else if (this.$props.viewer.reference === 'spectrum-viewer') {
return ['SCI', 'FLUX'].indexOf(extension) !== -1
}
} else if (this.$props.viewer.config === 'specviz2d') {
if (this.$props.viewer.reference === 'spectrum-2d-viewer') {
Expand Down
2 changes: 1 addition & 1 deletion jdaviz/configs/cubeviz/cubeviz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ tray:
- g-markers
- cubeviz-slice
- g-unit-conversion
- cubeviz-spectral-extraction
- g-gaussian-smooth
- g-collapse
- g-model-fitting
- g-line-list
- specviz-line-analysis
- cubeviz-moment-maps
- cubeviz-spectral-extraction
- imviz-aper-phot-simple
- export
viewer_area:
Expand Down
39 changes: 36 additions & 3 deletions jdaviz/configs/cubeviz/helper.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import logging
import numpy as np
from astropy.io import fits
from astropy.io import registry as io_registry
from astropy.utils.decorators import deprecated
from specutils import Spectrum1D
from specutils.io.registers import _astropy_has_priorities

from jdaviz.core.events import SnackbarMessage
from jdaviz.core.helpers import ImageConfigHelper
from jdaviz.configs.default.plugins.line_lists.line_list_mixin import LineListMixin
from jdaviz.configs.specviz import Specviz
Expand Down Expand Up @@ -79,6 +81,28 @@ def load_data(self, data, data_label=None, override_cube_limit=False, **kwargs):

super().load_data(data, parser_reference="cubeviz-data-parser", **kwargs)

if 'Spectral Extraction' not in self.plugins.keys(): # pragma: nocov
kecnry marked this conversation as resolved.
Show resolved Hide resolved
msg = SnackbarMessage(
"Automatic spectral extraction requires the Spectral Extraction plugin to be enabled", # noqa
color='error', sender=self, timeout=10000)
self.app.hub.broadcast(msg)
else:
try:
self.plugins['Spectral Extraction']._obj._create_auto_extract()
except Exception:
msg = SnackbarMessage(
"Automatic spectrum extraction for the entire cube failed."
" See the spectral extraction plugin to perform a custom extraction",
color='error', sender=self, timeout=10000)
raise # TODO: remove before merge
kecnry marked this conversation as resolved.
Show resolved Hide resolved
else:
msg = SnackbarMessage(
"The extracted 1D spectrum was generated automatically for the entire cube."
" See the spectral extraction plugin for details or to"
" perform a custom extraction.",
color='warning', sender=self, timeout=10000)
self.app.hub.broadcast(msg)

@deprecated(since="3.9", alternative="select_wavelength")
def select_slice(self, slice):
"""
Expand Down Expand Up @@ -124,20 +148,24 @@ def get_data(self, data_label=None, spatial_subset=None, spectral_subset=None, f
cls=None, use_display_units=False):
"""
Returns data with name equal to ``data_label`` of type ``cls`` with subsets applied from
``spatial_subset`` and/or ``spectral_subset`` using ``function`` if applicable.
``spectral_subset``, if applicable.

Parameters
----------
data_label : str, optional
Provide a label to retrieve a specific data set from data_collection.
spatial_subset : str, optional
Deprecated as of 3.11. Use the spectral extraction plugin and extract the extracted
spectrum instead.
Spatial subset applied to data.
spectral_subset : str, optional
Spectral subset applied to data.
function : {True, False, 'minimum', 'maximum', 'mean', 'median', 'sum'}, optional
Deprecated as of 3.11. Use the spectral extraction plugin and extract the extracted
spectrum instead.
Ignored if ``data_label`` does not point to cube-like data.
If True, will collapse according to the current collapse function defined in the
spectrum viewer. If provided as a string, the cube will be collapsed with the provided
If True, will collapse using 'sum'.
If provided as a string, the cube will be collapsed with the provided
function. If False, None, or not passed, the entire cube will be returned (unless there
are values for ``spatial_subset`` and ``spectral_subset``).
cls : `~specutils.Spectrum1D`, `~astropy.nddata.CCDData`, optional
Expand All @@ -149,6 +177,11 @@ def get_data(self, data_label=None, spatial_subset=None, spectral_subset=None, f
Data is returned as type cls with subsets applied.

"""
if function is not None or spatial_subset is not None:
logging.warning("DeprecationWarning: function and spatial_subset are deprecated and"
" will be removed in a future release. Use the spectral extraction"
" plugin and access the extracted spectrum directly.")

# If function is a value ('sum' or 'minimum') or True and spatial and spectral
# are set, then we collapse the cube along the spatial subset using the function, then
# we apply the mask from the spectral subset.
Expand Down
5 changes: 2 additions & 3 deletions jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,6 @@ def _calculate_continuum(self, msg=None):
# NOTE: there is no use in caching this, as the continuum will need to be re-computed
# per-spaxel to use within calculating the moment map
_ = self._get_continuum(self.dataset,
None,
self.spectral_subset,
update_marks=True)

Expand Down Expand Up @@ -236,9 +235,9 @@ def calculate_moment(self, add_data=True):
cube = self.dataset.selected_obj
else:
_, _, cube = self._get_continuum(self.dataset,
'per-pixel',
self.spectral_subset,
update_marks=False)
update_marks=False,
per_pixel=True)

# slice out desired region
# TODO: should we add a warning for a composite spectral subset?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ def test_moment_calculation(cubeviz_helper, spectrum1d_cube, tmp_path):
mm._obj.vue_calculate_moment()

assert mm._obj.moment_available
assert dc[1].label == 'moment 0'
assert dc[-1].label == 'moment 0'
mv_data = cubeviz_helper.app.get_viewer(
cubeviz_helper._default_uncert_viewer_reference_name
).data()
# by default, will overwrite the previous entry (so only one data entry)
assert len(mv_data) == 1
assert mv_data[0].label == 'moment 0'

assert len(dc.links) == 14
assert len(dc.links) == 19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there 5 more links now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is now a new 1d spectrum loaded and linked when first loading the new flux cube (so probably 3 links) and then the moment map is linking (2 new links). The added links are:

Wave <- forwards(Wave), Pixel Axis 0 [x] <- world2pix(Wave)
Pixel Axis 0 [x] <- world2pix(Wave)
mask <-> flux
Wave <- pix2world(Pixel Axis 0 [x])
Wave <- backwards(Wave)

I definitely think this could use some thinking and scrutiny (especially by @javerbukh) to see if we're overlinking anywhere. With this many changes, I'll admit I was mostly just trying to update tests to pass as long as the changes seemed reasonable, but we should be careful and make sure they actually make sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to find out which data are associated with those links?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin request for 4.0: Link viewer. My guess is that the glue-collapsed spectra had fewer links then the jdaviz-collapsed spectra, so 5 more links seems reasonable.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Link viewer would be so nice. We already have a ticket for that a long time ago, I think.


# label should remain unchanged but raise overwrite warnings
assert mm._obj.results_label == 'moment 0'
Expand All @@ -99,9 +99,9 @@ def test_moment_calculation(cubeviz_helper, spectrum1d_cube, tmp_path):
cubeviz_helper._default_flux_viewer_reference_name, 'moment 0'
)

result = dc[1].get_object(cls=CCDData)
result = dc[-1].get_object(cls=CCDData)
assert result.shape == (4, 2) # Cube shape is (2, 2, 4)
assert isinstance(dc[1].coords, WCS)
assert isinstance(dc[-1].coords, WCS)

# Make sure coordinate display now show moment map info (no WCS)
label_mouseover._viewer_mouse_event(flux_viewer, {'event': 'mousemove',
Expand Down
Loading
Loading