Skip to content

Commit

Permalink
integrate into cubeviz spectral extraction for future cone support
Browse files Browse the repository at this point in the history
  • Loading branch information
kecnry committed Jan 18, 2024
1 parent 7ff0327 commit becb20c
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 28 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ API Changes
Cubeviz
^^^^^^^

- ``spatial_subset`` in the spectral extraction plugin is now renamed to ``aperture`` and will
be removed in a future release. [#2664]

Imviz
^^^^^

Expand Down
2 changes: 1 addition & 1 deletion docs/dev/ui_style_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ try to adhere to the following principles:
components are necessary in a single row. Always emphasize readability at the default/minimum
width of the plugin tray, rather than using columns that result in a ton of text overflow.
* Use ``<v-row justify="end">`` to align content to the right (such as action buttons).
* Action buttons should use ``<plugin-action-button :results_isolated_to_plugin="true/false">text</v-btn>``
* Action buttons should use ``<plugin-action-button :results_isolated_to_plugin="true/false">text</plugin-action-button>``
to control the color depending on whether the button affects things outside the plugin itself
(adding/modifying data collection or subset entries, etc) or are isolated to within the plugin
(adding model components in model fitting, etc). These buttons can be wrapped in tooltip components
Expand Down
7 changes: 6 additions & 1 deletion jdaviz/configs/cubeviz/plugins/slice/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
from specutils.spectra.spectrum1d import Spectrum1D

from jdaviz.core.events import (AddDataMessage, SliceToolStateMessage,
SliceSelectSliceMessage, GlobalDisplayUnitChanged)
SliceSelectSliceMessage, SliceWavelengthUpdatedMessage,
GlobalDisplayUnitChanged)
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import PluginTemplateMixin
from jdaviz.core.user_api import PluginUserApi
Expand Down Expand Up @@ -217,6 +218,10 @@ def _on_slider_updated(self, event):
for viewer in self._indicator_viewers:
viewer._update_slice_indicator(value)

self.hub.broadcast(SliceWavelengthUpdatedMessage(slice=value,
wavelength=self.wavelength,
sender=self))

def vue_goto_first(self, *args):
if self.is_playing:
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@
import numpy as np
import astropy
import astropy.units as u
from astropy.utils.decorators import deprecated
from astropy.nddata import (
NDDataArray, StdDevUncertainty, NDUncertainty
)
from traitlets import Bool, List, Unicode, observe
from traitlets import Bool, Float, List, Unicode, observe

from jdaviz.core.events import SnackbarMessage
from jdaviz.core.custom_traitlets import FloatHandleEmpty
from jdaviz.core.events import SnackbarMessage, SliceWavelengthUpdatedMessage
from jdaviz.core.registries import tray_registry
from jdaviz.core.template_mixin import (PluginTemplateMixin,
DatasetSelectMixin,
SelectPluginComponent,
SpatialSubsetSelectMixin,
ApertureSubsetSelectMixin,
AddResultsMixin,
with_spinner)
from jdaviz.core.user_api import PluginUserApi
Expand All @@ -31,7 +33,7 @@
'cubeviz-spectral-extraction', label="Spectral Extraction", viewer_requirements='spectrum'
)
class SpectralExtraction(PluginTemplateMixin, DatasetSelectMixin,
SpatialSubsetSelectMixin, AddResultsMixin):
ApertureSubsetSelectMixin, AddResultsMixin):
"""
See the :ref:`Spectral Extraction Plugin Documentation <spex>` for more details.
Expand All @@ -41,12 +43,20 @@ class SpectralExtraction(PluginTemplateMixin, DatasetSelectMixin,
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.show`
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.open_in_tray`
* :meth:`~jdaviz.core.template_mixin.PluginTemplateMixin.close_in_tray`
* ``spatial_subset`` (:class:`~jdaviz.core.template_mixin.SubsetSelect`):
Subset to use for the spectral extraction, or ``No Subset``.
* ``aperture`` (:class:`~jdaviz.core.template_mixin.SubsetSelect`):
Subset to use for the spectral extraction, or ``Entire Cube``.
* ``add_results`` (:class:`~jdaviz.core.template_mixin.AddResults`)
* :meth:`collapse`
"""
template_file = __file__, "spectral_extraction.vue"
uses_active_status = Bool(True).tag(sync=True)

# feature flag for cone support
dev_cone_support = Bool(False).tag(sync=True)
wavelength_dependent = Bool(False).tag(sync=True)
reference_wavelength = FloatHandleEmpty().tag(sync=True)
slice_wavelength = Float().tag(sync=True)

function_items = List().tag(sync=True)
function_selected = Unicode('Sum').tag(sync=True)
filename = Unicode().tag(sync=True)
Expand All @@ -68,6 +78,12 @@ def __init__(self, *args, **kwargs):

self.extracted_spec = None

# TODO: in the future this could be generalized with support in SelectPluginComponent
self.aperture._default_text = 'Entire Cube'
self.aperture._manual_options = ['Entire Cube']
self.aperture.items = [{"label": "Entire Cube"}]
self.aperture.select_default()

self.function = SelectPluginComponent(
self,
items='function_items',
Expand All @@ -77,6 +93,9 @@ def __init__(self, *args, **kwargs):
self._set_default_results_label()
self.add_results.viewer.filters = ['is_spectrum_viewer']

self.session.hub.subscribe(self, SliceWavelengthUpdatedMessage,
handler=self._on_slice_changed)

if ASTROPY_LT_5_3_2:
self.disabled_msg = "Spectral Extraction in Cubeviz requires astropy>=5.3.2"

Expand All @@ -95,11 +114,46 @@ def user_api(self):
return PluginUserApi(
self,
expose=(
'function', 'spatial_subset',
'function', 'spatial_subset', 'aperture',
'add_results', 'collapse_to_spectrum'
)
)

@property
@deprecated(since="3.9", alternative="aperture")
def spatial_subset(self):
return self.user_api.aperture

@property
def slice_plugin(self):
return self.app._jdaviz_helper.plugins['Slice']

@observe('wavelength_dependent')
def _wavelength_dependent_changed(self, *args):
if self.wavelength_dependent:
self.reference_wavelength = self.slice_plugin.wavelength
# NOTE: this can be redundant in the case where reference_wavelength changed and triggers
# the observe, but we need to ensure it is updated if reference_wavelength is unchanged
self._update_mark_scale()

def _on_slice_changed(self, msg):
self.slice_wavelength = msg.wavelength

def vue_goto_reference_wavelength(self, *args):
self.slice_plugin.wavelength = self.reference_wavelength
# TODO: can we get rid of this extra call after properly observing wavelength?
# self._update_mark_scale()

def vue_adopt_slice_as_reference(self, *args):
self.reference_wavelength = self.slice_plugin.wavelength

@observe('reference_wavelength', 'slice_wavelength')
def _update_mark_scale(self, *args):
if not self.wavelength_dependent:
self.aperture.scale_factor = 1.0
return
self.aperture.scale_factor = self.slice_wavelength/self.reference_wavelength

@with_spinner()
def collapse_to_spectrum(self, add_data=True, **kwargs):
"""
Expand All @@ -121,12 +175,12 @@ def collapse_to_spectrum(self, add_data=True, **kwargs):
# defaults to ``No Subset``). Since the Cubeviz parser puts the fluxes
# and uncertainties in different glue Data objects, we translate the spectral
# cube and its uncertainties into separate NDDataArrays, then combine them:
if self.spatial_subset_selected != self.spatial_subset.default_text:
if self.aperture.selected != self.spatial_subset.default_text:
nddata = spectral_cube.get_subset_object(
subset_id=self.spatial_subset_selected, cls=NDDataArray
subset_id=self.aperture.selected, cls=NDDataArray
)
uncertainties = uncert_cube.get_subset_object(
subset_id=self.spatial_subset_selected, cls=StdDevUncertainty
subset_id=self.aperture.selected, cls=StdDevUncertainty
)
else:
nddata = spectral_cube.get_object(cls=NDDataArray)
Expand Down Expand Up @@ -242,15 +296,15 @@ def _save_extracted_spec_to_fits(self, overwrite=False, *args):
f"Extracted spectrum saved to {os.path.abspath(filename)}",
sender=self, color="success"))

@observe('spatial_subset_selected')
@observe('aperture_selected')
def _set_default_results_label(self, event={}):
label = "Spectral extraction"

if (
hasattr(self, 'spatial_subset') and
self.spatial_subset.selected != self.spatial_subset.default_text
hasattr(self, 'aperture') and
self.aperture.selected != self.aperture.default_text
):
label += f' ({self.spatial_subset_selected})'
label += f' ({self.aperture_selected})'
self.results_label_default = label


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
<j-tray-plugin
description='Extract a spectrum from a spectral cube.'
:link="docs_link || 'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#spectral-extraction'"
:uses_active_status="uses_active_status"
@plugin-ping="plugin_ping($event)"
:keep_active.sync="keep_active"
:popout_button="popout_button"
:disabled_msg="disabled_msg">

Expand All @@ -18,13 +21,53 @@
</v-row>

<plugin-subset-select
:items="spatial_subset_items"
:selected.sync="spatial_subset_selected"
:items="aperture_items"
:selected.sync="aperture_selected"
:show_if_single_entry="true"
label="Spatial region"
label="Spatial aperture"
hint="Select a spatial region to extract its spectrum."
/>

<div v-if="aperture_selected !== 'Entire Cube' && dev_cone_support">
<v-alert type='warning'>cone support is under active development and hidden from users</v-alert>
<v-row>
<v-switch
v-model="wavelength_dependent"
label="Wavelength dependent"
hint="Vary aperture linearly with wavelength"
persistent-hint>
</v-switch>
</v-row>
<div v-if="wavelength_dependent">
<v-row justify="end">
<j-tooltip tooltipcontent="Adopt the current slice as the reference wavelength">
<plugin-action-button :results_isolated_to_plugin="true" @click="adopt_slice_as_reference">
Adopt Current Slice
</plugin-action-button>
</j-tooltip>
</v-row>
<v-row>
<v-text-field
v-model.number="reference_wavelength"
type="number"
:step="0.1"
class="mt-0 pt-0"
label="Wavelength"
hint="Wavelength at which the aperture matches the selected subset."
persistent-hint
></v-text-field>
</v-row>
<v-row justify="end">
<j-tooltip tooltipcontent="Select the slice nearest the reference wavelength">
<plugin-action-button :results_isolated_to_plugin="true" @click="goto_reference_wavelength">
Slice to Reference Wavelength
</plugin-action-button>
</j-tooltip>
</v-row>
</div>

</div>

<plugin-add-results
:label.sync="results_label"
:label_default="results_label_default"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def test_gauss_smooth_before_spec_extract(cubeviz_helper, spectrum1d_cube_with_u
extract_plugin.function = "Sum"
expected_uncert = 2

extract_plugin.spatial_subset = 'Subset 1'
extract_plugin.aperture = 'Subset 1'
collapsed_spec = extract_plugin.collapse_to_spectrum()

# this single pixel has two wavelengths, and all uncertainties are unity
Expand All @@ -94,7 +94,7 @@ def test_gauss_smooth_before_spec_extract(cubeviz_helper, spectrum1d_cube_with_u
assert np.all(np.equal(collapsed_spec.uncertainty.array, 1))

# this two-pixel region has four unmasked data points per wavelength:
extract_plugin.spatial_subset = 'Subset 2'
extract_plugin.aperture = 'Subset 2'
collapsed_spec_2 = extract_plugin.collapse_to_spectrum()
assert np.all(np.equal(collapsed_spec_2.uncertainty.array, expected_uncert))

Expand Down Expand Up @@ -129,7 +129,7 @@ def test_subset(
plg.function = function

# single pixel region:
plg.spatial_subset = 'Subset 1'
plg.aperture = 'Subset 1'
collapsed_spec_1 = plg.collapse_to_spectrum()

# this single pixel has two wavelengths, and all uncertainties are unity
Expand All @@ -138,7 +138,7 @@ def test_subset(
assert np.all(np.equal(collapsed_spec_1.uncertainty.array, 1))

# this two-pixel region has four unmasked data points per wavelength:
plg.spatial_subset = 'Subset 2'
plg.aperture = 'Subset 2'
collapsed_spec_2 = plg.collapse_to_spectrum()

assert np.all(np.equal(collapsed_spec_2.uncertainty.array, expected_uncert))
Expand Down
10 changes: 9 additions & 1 deletion jdaviz/core/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
__all__ = ['NewViewerMessage', 'ViewerAddedMessage', 'ViewerRemovedMessage', 'LoadDataMessage',
'AddDataMessage', 'SnackbarMessage', 'RemoveDataMessage',
'AddLineListMessage', 'RowLockMessage',
'SliceSelectSliceMessage',
'SliceSelectSliceMessage', 'SliceWavelengthUpdatedMessage',
'SliceToolStateMessage',
'TableClickMessage', 'LinkUpdatedMessage', 'ExitBatchLoadMessage',
'MarkersChangedMessage', 'CanvasRotationChangedMessage',
Expand Down Expand Up @@ -288,6 +288,14 @@ def slice(self):
return self._slice


class SliceWavelengthUpdatedMessage(Message):
'''Message generated by the slice plugin when the selected slice and wavelength are updated'''
def __init__(self, slice, wavelength, *args, **kwargs):
super().__init__(*args, **kwargs)
self.slice = slice
self.wavelength = wavelength


class SliceToolStateMessage(Message):
'''Message generated by the select slice plot plugin when activated/deactivated'''
def __init__(self, change, *args, **kwargs):
Expand Down
8 changes: 4 additions & 4 deletions jdaviz/core/template_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1938,7 +1938,6 @@ def __init__(self, plugin, items, selected, scale_factor, multiselect=None,
selected=selected,
multiselect=multiselect,
filters=['is_spatial', 'is_not_composite', 'is_not_annulus'],
dataset=dataset,
viewers=viewers,
default_text=None)

Expand Down Expand Up @@ -1988,10 +1987,12 @@ def marks(self):
return all_aperture_marks

def _get_mark_coords(self, viewer):
if not len(self.selected) or not len(self.dataset.selected):
if not len(self.selected):
return [], []
if self.selected in self.manual_options:
return [], []

subset_dicts = self.selected_obj if self.multiselect else [self.selected_obj]
subset_dicts = self.selected_obj if getattr(self, 'multiselect', False) else [self.selected_obj] # noqa

x_coords, y_coords = np.array([]), np.array([])
for subset_dict in subset_dicts:
Expand Down Expand Up @@ -2063,7 +2064,6 @@ def __init__(self, *args, **kwargs):
'aperture_items',
'aperture_selected',
'aperture_scale_factor',
dataset='dataset' if hasattr(self, 'dataset') else None, # noqa
multiselect='multiselect' if hasattr(self, 'multiselect') else None) # noqa


Expand Down

0 comments on commit becb20c

Please # to comment.