Skip to content

Commit

Permalink
Moment map continuum subtraction (#2587)
Browse files Browse the repository at this point in the history
* shared code to visualize continuum in line analysis and moment maps
* re-compute continuum-subtracted per-spaxel
* changelog entry
* update API docs
* basic test coverage of live-preview
* handle width deprecation - astropy decorators aren't ideal for renaming a traitlet/property/setter, so add custom warnings
  • Loading branch information
kecnry authored Dec 12, 2023
1 parent 0673de8 commit c88afa2
Show file tree
Hide file tree
Showing 7 changed files with 392 additions and 184 deletions.
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Cubeviz

- Added functionality to Collapse and Spectral Extraction plugins to save results to FITS file. [#2586]

- Moment map plugin now supports linear per-spaxel continuum subtraction. [#2587]

Imviz
^^^^^
Expand All @@ -26,6 +27,9 @@ Specviz2d
API Changes
-----------

- ``width`` argument in Line Analysis plugin is renamed to ``continuum_width`` and ``width``
will be removed in a future release. [#2587]

Cubeviz
^^^^^^^

Expand Down
61 changes: 53 additions & 8 deletions jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
SpectralSubsetSelectMixin,
AddResultsMixin,
SelectPluginComponent,
SpectralContinuumMixin,
skip_if_no_updates_since_last_active,
with_spinner)
from jdaviz.core.user_api import PluginUserApi

Expand All @@ -30,7 +32,7 @@
@tray_registry('cubeviz-moment-maps', label="Moment Maps",
viewer_requirements=['spectrum', 'image'])
class MomentMap(PluginTemplateMixin, DatasetSelectMixin, SpectralSubsetSelectMixin,
AddResultsMixin):
SpectralContinuumMixin, AddResultsMixin):
"""
See the :ref:`Moment Maps Plugin Documentation <moment-maps>` for more details.
Expand All @@ -44,6 +46,14 @@ class MomentMap(PluginTemplateMixin, DatasetSelectMixin, SpectralSubsetSelectMix
Dataset to use for computing line statistics.
* ``spectral_subset`` (:class:`~jdaviz.core.template_mixin.SubsetSelect`):
Subset to use for the line, or ``Entire Spectrum``.
* ``continuum`` (:class:`~jdaviz.core.template_mixin.SubsetSelect`):
Subset to use for the continuum, or ``None`` to skip continuum subtraction,
or ``Surrounding`` to use a region surrounding the
subset set in ``spectral_subset``.
* ``continuum_width``:
Width, relative to the overall line spectral region, to fit the linear continuum
(excluding the region containing the line). If 1, will use endpoints within line region
only.
* ``n_moment``
* ``output_unit``
Choice of "Wavelength" or "Velocity", applicable for n_moment >= 1.
Expand All @@ -53,6 +63,7 @@ class MomentMap(PluginTemplateMixin, DatasetSelectMixin, SpectralSubsetSelectMix
* :meth:`calculate_moment`
"""
template_file = __file__, "moment_maps.vue"
uses_active_status = Bool(True).tag(sync=True)

n_moment = IntHandleEmpty(0).tag(sync=True)
filename = Unicode().tag(sync=True)
Expand Down Expand Up @@ -88,20 +99,32 @@ def __init__(self, *args, **kwargs):

@property
def _default_image_viewer_reference_name(self):
return self.jdaviz_helper._default_image_viewer_reference_name
return getattr(
self.app._jdaviz_helper, '_default_spectrum_viewer_reference_name', 'flux-viewer'
)

@property
def _default_spectrum_viewer_reference_name(self):
return self.jdaviz_helper._default_spectrum_viewer_reference_name
return getattr(
self.app._jdaviz_helper, '_default_spectrum_viewer_reference_name', 'spectrum-viewer'
)

@property
def user_api(self):
# NOTE: leaving save_as_fits out for now - we may want a more general API to do that
# accross all plugins at some point
return PluginUserApi(self, expose=('dataset', 'spectral_subset', 'n_moment',
return PluginUserApi(self, expose=('dataset', 'spectral_subset',
'continuum', 'continuum_width',
'n_moment',
'output_unit', 'reference_wavelength',
'add_results', 'calculate_moment'))

@observe('is_active')
def _is_active_changed(self, msg):
for pos, mark in self.continuum_marks.items():
mark.visible = self.is_active
self._calculate_continuum(msg)

@observe("dataset_selected", "dataset_items", "n_moment")
def _set_default_results_label(self, event={}):
label_comps = []
Expand All @@ -120,6 +143,21 @@ def _set_data_units(self, event={}):
else:
self.dataset_spectral_unit = ""

@observe("dataset_selected", "spectral_subset_selected",
"continuum_subset_selected", "continuum_width")
@skip_if_no_updates_since_last_active()
def _calculate_continuum(self, msg=None):
if not hasattr(self, 'dataset') or self.app._jdaviz_helper is None: # noqa
# during initial init, this can trigger before the component is initialized
return

# 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)

@with_spinner()
def calculate_moment(self, add_data=True):
"""
Expand All @@ -130,12 +168,19 @@ def calculate_moment(self, add_data=True):
add_data : bool
Whether to add the resulting data object to the app according to ``add_results``.
"""
# Retrieve the data cube and slice out desired region, if specified
if "_orig_spec" in self.dataset.selected_obj.meta:
cube = self.dataset.selected_obj.meta["_orig_spec"]
if self.continuum.selected == 'None':
if "_orig_spec" in self.dataset.selected_obj.meta:
cube = self.dataset.selected_obj.meta["_orig_spec"]
else:
cube = self.dataset.selected_obj
else:
cube = self.dataset.selected_obj
_, _, cube = self._get_continuum(self.dataset,
'per-pixel',
self.spectral_subset,
update_marks=False)

# slice out desired region
# TODO: should we add a warning for a composite spectral subset?
spec_min, spec_max = self.spectral_subset.selected_min_max(cube)
slab = manipulation.spectral_slab(cube, spec_min, spec_max)

Expand Down
52 changes: 52 additions & 0 deletions jdaviz/configs/cubeviz/plugins/moment_maps/moment_maps.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
<j-tray-plugin
description='Create a 2D image from a data cube.'
:link="docs_link || 'https://jdaviz.readthedocs.io/en/'+vdocs+'/'+config+'/plugins.html#moment-maps'"
:uses_active_status="uses_active_status"
@plugin-ping="plugin_ping($event)"
:keep_active.sync="keep_active"
:popout_button="popout_button">

<j-plugin-section-header>Cube</j-plugin-section-header>
<v-row>
<j-docs-link>Choose the input cube and spectral subset.</j-docs-link>
</v-row>

<plugin-dataset-select
:items="dataset_items"
:selected.sync="dataset_selected"
Expand All @@ -22,6 +30,50 @@
hint="Spectral region to compute the moment map."
/>

<j-plugin-section-header>Continuum Subtraction</j-plugin-section-header>
<v-row>
<j-docs-link v-if="continuum_subset_selected==='None'">
Choose whether and how to compute the continuum for continuum subtraction.
</j-docs-link>
<j-docs-link v-else>
{{continuum_subset_selected=='Surrounding' && spectral_subset_selected=='Entire Spectrum' ? "Since using the entire spectrum, the end points will be used to fit a linear continuum." : "Choose a region to fit a linear line as the underlying continuum."}}
{{continuum_subset_selected=='Surrounding' && spectral_subset_selected!='Entire Spectrum' ? "Choose a width in number of data points to consider on each side of the line region defined above." : null}}
When this plugin is opened, a visual indicator will show on the spectrum plot showing the continuum fitted as a thick line, and interpolated into the line region as a thin line.
When computing the moment map, these same input options will be used to compute and subtract a linear continuum for each spaxel, independently.
</j-docs-link>
</v-row>

<plugin-subset-select
:items="continuum_subset_items"
:selected.sync="continuum_subset_selected"
:show_if_single_entry="true"
:rules="[() => continuum_subset_selected!==spectral_subset_selected || 'Must not match line selection.']"
label="Continuum"
hint="Select spectral region that defines the continuum."
/>

<v-row v-if="continuum_subset_selected=='Surrounding' && spectral_subset_selected!='Entire Spectrum'">
<!-- DEV NOTE: if changing the validation rules below, also update the logic to clear the results
in line_analysis.py -->
<v-text-field
label="Width"
type="number"
v-model.number="continuum_width"
step="0.1"
:rules="[() => continuum_width!=='' || 'This field is required.',
() => continuum_width<=10 || 'Width must be <= 10.',
() => continuum_width>=1 || 'Width must be >= 1.']"
hint="Width, relative to the overall line spectral region, to fit the linear continuum (excluding the region containing the line). If 1, will use endpoints within line region only."
persistent-hint
>
</v-text-field>
</v-row>

<j-plugin-section-header>Moment</j-plugin-section-header>
<v-row>
<j-docs-link>Options for generating the moment map.</j-docs-link>
</v-row>

<v-row>
<v-text-field
ref="n_moment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,30 @@
from jdaviz.configs.cubeviz.plugins.moment_maps.moment_maps import MomentMap


def test_user_api(cubeviz_helper, spectrum1d_cube):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", message="No observer defined on WCS.*")
cubeviz_helper.load_data(spectrum1d_cube, data_label='test')

mm = cubeviz_helper.plugins['Moment Maps']
assert not mm._obj.continuum_marks['center'].visible
with mm.as_active():
assert mm._obj.continuum_marks['center'].visible
mm.n_moment = 0
# no continuum so marks should be empty
assert len(mm._obj.continuum_marks['center'].x) == 0

mom = mm.calculate_moment()

mm.continuum = 'Surrounding'
mm.continuum_width = 10
assert len(mm._obj.continuum_marks['center'].x) > 0

mom_sub = mm.calculate_moment()

assert mom != mom_sub


def test_moment_calculation(cubeviz_helper, spectrum1d_cube, tmpdir):
dc = cubeviz_helper.app.data_collection
with warnings.catch_warnings():
Expand Down
Loading

0 comments on commit c88afa2

Please sign in to comment.