Skip to content
New issue

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

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

Already on GitHub? # to your account

Cubeviz spectral extraction through plugin #2827

Merged
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 14 additions & 0 deletions CHANGES.rst
Copy link
Contributor

Choose a reason for hiding this comment

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

LGTM.

Is there any follow-up needed for all the JDAT notebooks out there?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure, I did go through and update the example notebooks. I'll create a general ticket for updating JDAT for any/all breaking changes in 4.0 - that's a good idea.

Copy link
Contributor

Choose a reason for hiding this comment

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

:shipit:

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ New Features
Cubeviz
^^^^^^^

- Automatic spectral extraction now goes through the logic of the spectral extraction plugin for
self-consistency. This results in several breaking changes to data-labels and ``get_data``
(the extracted spectra are now given dedicated data-labels instead of referring to them by
the label of the flux cube) as well as to several plugins: model fitting, gaussian smooth,
line analysis, and moment maps. [#2827]

Imviz
^^^^^

Expand All @@ -28,6 +34,14 @@ API Changes
Cubeviz
^^^^^^^

- ``get_data`` no longer supports ``function`` or ``spatial_subset`` as arguments. To access
an extracted 1D spectrum, use the Spectral Extraction plugin or the automatic extraction of
spatial subsets, and refer to the data-label assigned to the resulting 1D spectrum. [#2827]

- Several plugins that take 1D spectra replace ``spatial_subset`` with referring to the 1D
spectrum in ``dataset``. This affects: model fitting, gaussian smooth, line analysis,
and moment maps. [#2827]

Imviz
^^^^^

Expand Down
33 changes: 7 additions & 26 deletions docs/cubeviz/export_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,15 @@ Spatial Regions
:ref:`Export Spatial Regions <imviz_export>`
Documentation on how to export spatial regions.

Since Specviz can be accessed from Cubeviz, the following line of code
can be used to extract the *spectrum* of a spatial subset named "Subset 1":
Any cube (or extracted spectrum) can be extracted from cubeviz:

.. code-block:: python

subset1_spec1d = cubeviz.specviz.get_spectra(spectral_subset="Subset 1")
subset1_spec1d = cubeviz.get_data("Spectrum (Subset 1, sum)")

An example without accessing Specviz:

.. code-block:: python

subset1_spec1d = cubeviz.get_data(data_label=flux_data_label,
spatial_subset="Subset 1",
function="mean")

Note that in the above example, the ``function`` keyword is used to tell Cubeviz
how to collapse the flux cube down to a one dimensional spectrum - this is not
necessarily equivalent to the collapsed spectrum in the spectrum viewer, which
may have used a different collapse function.
To use a ``function`` other than sum, use the :ref:`Spectral Extraction <spectral-extraction>` plugin
first to create a 1D spectrum and then refer to it by label in ``get_data``.

To get all subsets from the spectrum viewer:

Expand All @@ -58,11 +48,12 @@ To access the spatial regions themselves:
:ref:`Export Spectra <specviz-export-data>`
Documentation on how to export data from the ``spectrum-viewer``.

The following line of code can be used to extract a spectral subset named "Subset 2":
The following line of code can be used to extract 1D spectrum either automatically extracted
or extracted manually through the :ref:`Spectral Extraction <spectral-extraction>` plugin:

.. code-block:: python

subset2_spec1d = cubeviz.specviz.get_spectra("Subset 2")
subset2_spec1d = cubeviz.get_data("Spectrum (Subset 2, sum)")

3D Data Cubes
=============
Expand All @@ -85,16 +76,6 @@ where the mask (if available) is as defined in

mydata.write("mydata.fits", format="jdaviz-cube")

Data can also be accessed directly from ``data_collection`` using the following code:

.. code-block:: python

cubeviz.app.data_collection[0]

Which is returned as a `~glue.core.data.Data` object. The
`~glue.core.data_collection.DataCollection` object
can be indexed to return all available data (i.e., not just using 0 like in the
previous example).

.. _cubeviz-export-model:

Expand Down
3 changes: 0 additions & 3 deletions docs/cubeviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,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
49 changes: 35 additions & 14 deletions jdaviz/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def to_unit(self, data, cid, values, original_units, target_units):
eqv = u.spectral_density(spec.spectral_axis)

else: # spectral axis
eqv = u.spectral()
eqv = u.spectral() + u.pixel_scale(1*u.pix)

return (values * u.Unit(original_units)).to_value(u.Unit(target_units), equivalencies=eqv)

Expand Down Expand Up @@ -412,6 +412,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 @@ -423,8 +425,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 @@ -433,7 +434,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 @@ -455,18 +456,34 @@ 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()
if not plg.supports_auto_update:
raise NotImplementedError(f"{data.meta.get('Plugin')} does not support live-updates") # noqa
plg.user_api.from_dict(plugin_inputs)
# keep auto-updating, even if the option is hidden from the user API
# (can remove this line if auto_update is exposed to the user API in the future)
plg.add_results.auto_update_result = True
try:
plg()
except Exception as e:
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 @@ -478,6 +495,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 @@ -2123,7 +2144,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 @@ -2315,13 +2343,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 @@ -2479,7 +2500,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 @@ -2686,7 +2707,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 @@ -178,8 +178,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 @@ -25,13 +25,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
49 changes: 27 additions & 22 deletions jdaviz/configs/cubeviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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 +80,27 @@

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

if 'Spectral Extraction' not in self.plugins: # pragma: no cover
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._extract_in_new_instance(auto_update=False, add_data=True) # noqa
except Exception:
msg = SnackbarMessage(

Check warning on line 92 in jdaviz/configs/cubeviz/helper.py

View check run for this annotation

Codecov / codecov/patch

jdaviz/configs/cubeviz/helper.py#L91-L92

Added lines #L91 - L92 were not covered by tests
"Automatic spectrum extraction for the entire cube failed."
" See the spectral extraction plugin to perform a custom extraction",
color='error', sender=self, timeout=10000)
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 @@ -120,26 +142,21 @@
self._specviz = Specviz(app=self.app)
return self._specviz

def get_data(self, data_label=None, spatial_subset=None, spectral_subset=None, function=None,
Copy link
Contributor

Choose a reason for hiding this comment

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

We don't have to deprecate function here? Can use deprecated_renamed_argument.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a large reason why we're bumping to 4.0 once we merge this PR (this also makes a few breaking changes to plugins). If there is a way to decorate and note the removal without having to temporarily support the old kwargs, I have no objections, but keeping the old code around with a warning proved to be messy and inconsistent with the "new way".

Copy link
Contributor

Choose a reason for hiding this comment

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

If you want to remove it without deprecation, that is fine but should at least mention in change log under API?

Copy link
Member Author

Choose a reason for hiding this comment

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

yes, for sure, this PR will require a major change log entry (probably many of the entries in the PR description will need to be included somehow).

def get_data(self, data_label=None, spatial_subset=None, spectral_subset=None,
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
Spatial subset applied to data.
Spatial subset applied to data. Only applicable if ``data_label`` points to a cube or
image. To extract a spectrum from a cube, use the spectral extraction plugin instead.
spectral_subset : str, optional
Spectral subset applied to data.
function : {True, False, 'minimum', 'maximum', 'mean', 'median', 'sum'}, optional
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
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
The type that data will be returned as.

Expand All @@ -149,20 +166,8 @@
Data is returned as type cls with subsets applied.

"""
# 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.
# If function is any value other than False, we use specviz
if (function is not False and spectral_subset and spatial_subset) or function:
return self.specviz.get_data(data_label=data_label, spectral_subset=spectral_subset,
cls=cls, spatial_subset=spatial_subset, function=function)
elif function is False and spectral_subset:
raise ValueError("function cannot be False if spectral_subset"
" is set")
elif function is False:
function = None
return self._get_data(data_label=data_label, spatial_subset=spatial_subset,
spectral_subset=spectral_subset, function=function,
spectral_subset=spectral_subset,
cls=cls, use_display_units=use_display_units)

# Need this method for Imviz Aperture Photometry plugin.
Expand Down
Loading