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

Moment map continuum subtraction #2587

Merged
merged 10 commits into from
Dec 12, 2023
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
Loading