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

Use get_data internally instead of get_data_from_viewer #2072

Merged
merged 16 commits into from
Mar 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ API Changes

- Export plot plugin now exposes the ``viewer`` dropdown in the user API. [#2037]

- Replaced internal ``get_data_from_viewer()`` calls, ``specviz.get_spectra`` now returns
spectra for all data+subset combinations. [#2072]

Cubeviz
^^^^^^^

Expand Down
48 changes: 15 additions & 33 deletions docs/cubeviz/export_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,20 @@ can be used to extract the *spectrum* of a spatial subset named "Subset 1":

.. code-block:: python

subset1_spec1d = cubeviz.specviz.get_spectra("Subset 1")
subset1_spec1d = cubeviz.specviz.get_spectra(subset_to_apply="Subset 1")

An example without accessing Specviz:

.. code-block:: python

subset1_spec1d = cubeviz.app.get_data_from_viewer("flux-viewer", data_label="Subset 1")
subset1_spec1d = cubeviz.get_data(data_label=flux_data_label,
subset_to_apply="Subset 1",
statistic="mean")

Note that in the above example, the ``statistic`` 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 statistic.

To get all subsets from the spectrum viewer:

Expand Down Expand Up @@ -57,42 +64,17 @@ The following line of code can be used to extract a spectral subset named "Subse

subset2_spec1d = cubeviz.specviz.get_spectra("Subset 2")

2D Images and 3D Data Cubes
===========================

2D images and 3D data cubes can be extracted from their respective
:ref:`viewers <cubeviz-viewers>`. The viewer options in the Cubeviz configuration are
``flux-viewer``, ``uncert-viewer``, and ``mask-viewer``.
For example, to list the data available in a particular viewer:

.. code-block:: python

mydata = cubeviz.app.get_data_from_viewer("flux-viewer")

To extract the data you want (replace "data_name" with the name of your data):

.. code-block:: python

mydata = cubeviz.app.get_data_from_viewer("uncert-viewer", "data_name")

The data is returned as a ``glue-jupyter`` object. To convert to a numpy array:

.. code-block:: python

mydata_flux = mydata["flux"]
3D Data Cubes
=============

To retrieve the data cube as a `specutils.Spectrum1D` object, you can do the following:
To extract the entire cube, you can run the following code (replace "data_name"
with the name of the data you want to extract):

.. code-block:: python

from specutils import Spectrum1D
mydata.get_object(cls=Spectrum1D, statistic=None)

Alternatively, you can wrap this all into a single command:

.. code-block:: python
mydata = cubeviz.get_data(data_label="data_name")

mydata = cubeviz.app.get_data_from_viewer("uncert-viewer", "data_name")
The data is returned as a 3D `specutils.Spectrum1D` object.

To write out a `specutils.Spectrum1D` cube from Cubeviz
(e.g., a fitted cube from :ref:`model-fitting`),
Expand Down
34 changes: 22 additions & 12 deletions docs/specviz/export_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,38 @@ those data currently back into your Jupyter notebook:

specviz.get_spectra()

which yields a `specutils.Spectrum1D` object that you can manipulate however
you wish. You can then load the modified spectrum back into the notebook via
the API described in :ref:`specviz-import-api`.
which yields a either a single `specutils.Spectrum1D` object or a dictionary of
`specutils.Spectrum1D` (if there are multiple displayed spectra) that you can
manipulate however you wish. You can then load the modified spectrum back into
the notebook via the API described in :ref:`specviz-import-api`.

Alternatively, if you want more control over Specviz, you can access it the
via the lower-level application interface that connects to the ``glue-jupyter``
application level. This is accessed via the ``.app`` attribute of the
:py:class:`~jdaviz.configs.specviz.helper.Specviz` helper class. For example:
via the ``get_data`` method of the
:py:class:`~jdaviz.configs.specviz.helper.Specviz` helper class. This method always
returns a single spectrum; if there are multiple spectra loaded you must supply a
label to the ``data_label`` argument. For example:

.. code-block:: python

specviz.app.get_data_from_viewer('spectrum-viewer')
specviz.get_data(data_label='Spectrum 1')

To extract a specific spectral subset:
To extract a spectrum with a spectral subset applied:

.. code-block:: python

specviz.app.get_data_from_viewer('spectrum-viewer', 'Subset 1')
specviz.get_data(subset_to_apply='Subset 1')

For more on what you can do with this lower-level object, see the API sections
and the
`glue-jupyter documentation <https://glue-jupyter.readthedocs.io/en/latest/>`_.
In this case, the returned `specutils.Spectrum1D` object will have a ``mask``
attribute, where ``True`` corresponds to the region outside the selected subset
(i.e., the region that has been masked out). You could load back in a copy of the
spectrum containing only your subset by running:

.. code-block:: python

spec = specviz.get_data(subset_to_apply='Subset 1')
subset_spec = Spectrum1D(flux=spec.flux[~spec.mask],
spectral_axis=spec.spectral_axis[~spec.mask])
specviz.load_spectrum(subset_spec)
Comment on lines +43 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this makes sense for now. But since Spectrum1D already has a mask attribute, we could(/should?) make specviz.load_spectrum load the mask so that you don't have to also index by the not-mask. Does the parser do this already/is this step necessary?

Copy link
Collaborator Author

@rosteen rosteen Mar 13, 2023

Choose a reason for hiding this comment

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

The parser does load the mask (I believe you can show the mask and uncertainties, should be a toggle somewhere), but what Cami wanted to do was load in the data such that the data outside the subset is gone completely, which is what this does.


.. seealso::

Expand Down
5 changes: 2 additions & 3 deletions docs/specviz/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,8 @@ The redshift itself can be set from the notebook using the ``set_redshift`` meth

Any set redshift values are applied to spectra output using the
:py:meth:`~jdaviz.configs.specviz.helper.Specviz.get_spectra` helper method.
Note that using the lower-level app data retrieval (e.g.,
``specviz.app.get_data_from_viewer()``) will return the data as
originally loaded, with the redshift unchanged.
Note that using the lower-level app data retrieval (e.g., ``specviz.get_data()``)
will return the data as originally loaded, with the redshift unchanged.

.. _line-analysis:

Expand Down
21 changes: 3 additions & 18 deletions docs/specviz2d/export_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,11 @@ Exporting Data From Specviz2D
==========

Images in the 2D spectrum viewer can be exported as `specutils.Spectrum1D` objects into
the notebook:
the notebook (replace "2D data" with the label of the desired data):

.. code-block:: python

specviz2d.app.get_data_from_viewer('spectrum-2d-viewer')

which returns a dictionary, with the loaded data labels as keywords and `specutils.Spectrum1D`
objects as values. To return only a single `specutils.Spectrum1D` object, pass the data label:

.. code-block:: python

specviz2d.app.get_data_from_viewer('spectrum-2d-viewer', 'my-data-label')

specviz2d.get_data(data_label="2D data")

.. _specviz2d-export-data-1d:

Expand All @@ -33,14 +25,7 @@ Similarly, the 1D spectrum data objects can be exported into the notebook:

.. code-block:: python

specviz2d.app.get_data_from_viewer('spectrum-viewer')

or:

.. code-block:: python

specviz2d.app.get_data_from_viewer('spectrum-viewer', 'my-data-label')

specviz2d.get_data(data_label='1D data')

An instance of Specviz can also be accessed, which exposes some helper methods from Specviz:

Expand Down
4 changes: 3 additions & 1 deletion jdaviz/configs/cubeviz/plugins/tests/test_regions.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ def test_spatial_spectral_mix(self):
# https://github.com/spacetelescope/jdaviz/issues/1584
with pytest.warns(UserWarning, match='Applying the value from the redshift slider'):
spectral_subsets = self.cubeviz.specviz.get_spectra()
assert list(spectral_subsets.keys()) == ['has_microns[FLUX]', 'Subset 1', 'Subset 2'], spectral_subsets # noqa
assert list(spectral_subsets.keys()) == ['has_microns[FLUX]',
'has_microns[FLUX] (Subset 1)',
'has_microns[FLUX] (Subset 2)'], spectral_subsets # noqa
Comment on lines +72 to +74
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't remember if this paradigm for how data from get_spectra() is returned has been discussed yet but if <filename> (<subset name>) is the way we want to go, we need to update the SpecvizExample notebook cell specviz.get_spectra('Subset 1'). I am not opposed to it but I think we should run it by the team before I use the same paradigm in the get_subset work.

for sp in spectral_subsets.values():
assert isinstance(sp, Spectrum1D)
4 changes: 1 addition & 3 deletions jdaviz/configs/default/plugins/line_lists/line_lists.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,7 @@ def _on_viewer_data_changed(self, msg=None):

label = msg.data.label
try:
viewer_data = self.app.get_data_from_viewer(
self._default_spectrum_viewer_reference_name
).get(label)
viewer_data = self.app._jdaviz_helper.get_data(data_label=label)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Ahh, I understand your desire to make get_data available via the app. Personally I think how it is here, while clunky, is probably a more robust way to implement it, but I could be convinced otherwise

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So is that an approval or a change request? 😅

Copy link
Collaborator

Choose a reason for hiding this comment

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

Haha nothing to hold out on. Just a note

except TypeError:
warn_message = SnackbarMessage("Line list plugin could not retrieve data from viewer",
sender=self, color="error")
Expand Down
25 changes: 15 additions & 10 deletions jdaviz/configs/default/plugins/model_fitting/model_fitting.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,15 +294,20 @@ def _warn_if_no_equation(self):

def _get_1d_spectrum(self):
# retrieves the 1d spectrum (accounting for spatial subset for cubeviz, if necessary)
if self.config == 'cubeviz' and self.spatial_subset_selected != 'Entire Cube':
# then we're acting on the auto-collapsed data in the spectrum-viewer
# of a spatial subset. In the future, we may want to expose on-the-fly
# collapse options... but right now these will follow the settings of the
# spectrum-viewer itself
return self.app.get_data_from_viewer(
self._default_spectrum_viewer_reference_name,
self.spatial_subset_selected
)
if self.config == 'cubeviz':
stat = self.app.get_viewer(self._default_spectrum_viewer_reference_name).state.function
if self.spatial_subset_selected == 'Entire Cube':
return self.app._jdaviz_helper.get_data(data_label=self.dataset_selected,
statistic=stat)
else:
# then we're acting on the auto-collapsed data in the spectrum-viewer
# of a spatial subset. In the future, we may want to expose on-the-fly
# collapse options... but right now these will follow the settings of the
# spectrum-viewer itself
kwargs = {"data_label": self.dataset_selected,
"subset_to_apply": self.spatial_subset_selected,
"statistic": stat}
return self.app._jdaviz_helper.get_data(**kwargs)
else:
return self.dataset.selected_obj

Expand All @@ -321,7 +326,7 @@ def _dataset_selected_changed(self, event=None):
IPyWidget callback event object. In this case, represents the data
label of the data collection object selected by the user.
"""
if not hasattr(self, 'dataset'):
if not hasattr(self, 'dataset') or self.app._jdaviz_helper is None:
# during initial init, this can trigger before the component is initialized
return

Expand Down
13 changes: 10 additions & 3 deletions jdaviz/configs/imviz/plugins/coords_info/coords_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,14 @@ def _spectrum_viewer_update(self, viewer, x, y):
if not isinstance(lyr.layer.subset_state, RoiSubsetState):
# then this is a SPECTRAL subset
continue
elif ((not isinstance(lyr.layer, BaseData)) or (lyr.layer.ndim not in (1, 3))
or (not lyr.visible)):
# For use later in data retrieval
subset_label = lyr.layer.label
data_label = lyr.layer.data.label
elif ((not isinstance(lyr.layer, BaseData)) or (lyr.layer.ndim not in (1, 3))):
continue
else:
subset_label = None
data_label = lyr.layer.label

try:
# Cache should have been populated when spectrum was first plotted.
Expand All @@ -339,7 +344,9 @@ def _spectrum_viewer_update(self, viewer, x, y):
if cache_key in self.app._get_object_cache:
sp = self.app._get_object_cache[cache_key]
else:
sp = self.app.get_data_from_viewer('spectrum-viewer', lyr.layer.label)
sp = self.app._jdaviz_helper.get_data(data_label=data_label,
statistic=statistic,
subset_to_apply=subset_label)
self.app._get_object_cache[cache_key] = sp

# Out of range in spectral axis.
Expand Down
43 changes: 37 additions & 6 deletions jdaviz/configs/specviz/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from astropy import units as u
from astropy.utils.decorators import deprecated
from glue.core.subset_group import GroupedSubset
from specutils import SpectralRegion, Spectrum1D

from jdaviz.core.helpers import ConfigHelper
Expand Down Expand Up @@ -67,20 +68,50 @@ def load_spectrum(self, data, data_label=None, format=None, show_in_viewer=True,
show_in_viewer=show_in_viewer,
concat_by_file=concat_by_file)

def get_spectra(self, data_label=None, apply_slider_redshift="Warn"):
def get_spectra(self, data_label=None, subset_to_apply=None, apply_slider_redshift="Warn"):
"""Returns the current data loaded into the main viewer

"""
spectra = self.app.get_data_from_viewer(
self._default_spectrum_viewer_reference_name, data_label=data_label
)
spectra = {}
# Just to save line length
get_data_method = self.app._jdaviz_helper.get_data
viewer = self.app.get_viewer(self._default_spectrum_viewer_reference_name)
statistic = getattr(viewer, "function", None)

if data_label is not None:
spectrum = get_data_method(data_label=data_label,
subset_to_apply=subset_to_apply,
statistic=statistic)
spectra[data_label] = spectrum
else:
for layer_state in viewer.state.layers:
lyr = layer_state.layer
if subset_to_apply is not None:
if lyr.label == subset_to_apply:
spectrum = get_data_method(data_label=lyr.data.label,
subset_to_apply=subset_to_apply,
statistic=statistic)
spectra[lyr.data.label] = spectrum
else:
continue
else:
if isinstance(lyr, GroupedSubset):
spectrum = get_data_method(data_label=lyr.data.label,
subset_to_apply=lyr.label,
statistic=statistic)
spectra[f'{lyr.data.label} ({lyr.label})'] = spectrum
else:
spectrum = get_data_method(data_label=lyr.label,
statistic=statistic)
spectra[lyr.label] = spectrum

if not apply_slider_redshift:
if data_label is not None:
return spectra[data_label]
return spectra
else:
output_spectra = {}
# We need to create new Spectrum1D outputs with the redshifts set
if data_label is not None:
spectra = {data_label: spectra}
for key in spectra.keys():
output_spectra[key] = _apply_redshift_to_spectra(spectra[key], self._redshift)

Expand Down
Loading