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

Add option of using self derived interpolated profile for horne extract in specviz2d/spectral extraction plugin #2845

Merged
merged 5 commits into from
Jun 21, 2024
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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Specviz
Specviz2d
^^^^^^^^^


API Changes
-----------

Expand Down Expand Up @@ -132,6 +133,7 @@ Specviz

Specviz2d
^^^^^^^^^
- Add option to use self-derived spatial profile for Horne extract in spectral extraction plugin. [#2845]

API Changes
-----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ class SpectralExtraction(PluginTemplateMixin):
* ``ext_type`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`)
* :attr:`ext_width` :
full width of the extraction window.
* ``horne_ext_profile`` (:class:`~jdaviz.core.template_mixin.SelectPluginComponent`):
For Horne extract, choice of 'Gaussian' or 'Self (interpolated)' to use
empirical profile from data.
* :attr:`self_prof_n_bins` :
Number of bins to use when computing the self-derived profile for Horne Extract.
* :attr:`self_prof_interp_degree_x` :
Interpolation degree (in X) to use when computing the self-derived profile
for Horne Extract.
* :attr:`self_prof_interp_degree_y` :
Interpolation degree (in Y) to use when computing the self-derived profile
for Horne Extract.
* ``ext_add_results`` (:class:`~jdaviz.core.template_mixin.AddResults`)
* :meth:`import_extract`
* :meth:`export_extract`
Expand Down Expand Up @@ -190,6 +201,13 @@ class SpectralExtraction(PluginTemplateMixin):
ext_type_items = List().tag(sync=True)
ext_type_selected = Unicode().tag(sync=True)

horne_ext_profile_items = List().tag(sync=True)
horne_ext_profile_selected = Unicode().tag(sync=True)

self_prof_n_bins = IntHandleEmpty(10).tag(sync=True)
self_prof_interp_degree_x = IntHandleEmpty(1).tag(sync=True)
self_prof_interp_degree_y = IntHandleEmpty(1).tag(sync=True)

ext_width = FloatHandleEmpty(0).tag(sync=True)

ext_uncert_warn = Bool(False).tag(sync=True)
Expand Down Expand Up @@ -313,6 +331,11 @@ def __init__(self, *args, **kwargs):
selected='ext_type_selected',
manual_options=['Boxcar', 'Horne'])

self.horne_ext_profile = SelectPluginComponent(self,
items='horne_ext_profile_items',
selected='horne_ext_profile_selected',
manual_options=['Gaussian', 'Self (interpolated)']) # noqa

self.ext_add_results = AddResults(self, 'ext_results_label',
'ext_results_label_default',
'ext_results_label_auto',
Expand Down Expand Up @@ -350,6 +373,10 @@ def user_api(self):
'export_bg', 'export_bg_img', 'export_bg_sub',
'ext_dataset', 'ext_trace', 'ext_type',
'ext_width', 'ext_add_results',
'horne_ext_profile',
'self_prof_n_bins',
'self_prof_interp_degree_x',
'self_prof_interp_degree_y',
cshanahan1 marked this conversation as resolved.
Show resolved Hide resolved
'import_extract',
'export_extract', 'export_extract_spectrum'))

Expand Down Expand Up @@ -600,7 +627,9 @@ def _interaction_in_bg_step(self, event={}):
self.active_step = 'bg'

@observe('is_active', 'ext_dataset_selected', 'ext_trace_selected',
'ext_type_selected', 'ext_width', 'active_step')
'ext_type_selected', 'ext_width', 'active_step',
'horne_ext_profile_selected', 'self_prof_n_bins',
'self_prof_interp_degree_x', 'self_prof_interp_degree_y')
@skip_if_not_tray_instance()
@skip_if_no_updates_since_last_active()
def _interaction_in_ext_step(self, event={}):
Expand Down Expand Up @@ -925,11 +954,37 @@ def export_extract(self, **kwargs):
if self.ext_type_selected == 'Boxcar':
ext = extract.BoxcarExtract(inp_sp2d, trace, width=self.ext_width)
elif self.ext_type_selected == 'Horne':
spatial_profile = None
if inp_sp2d.uncertainty is None:
inp_sp2d.uncertainty = VarianceUncertainty(np.ones_like(inp_sp2d.data))

if not hasattr(inp_sp2d.uncertainty, 'uncertainty_type'):
inp_sp2d.uncertainty = StdDevUncertainty(inp_sp2d.uncert)
ext = extract.HorneExtract(inp_sp2d, trace)

if self.horne_ext_profile_selected == 'Self (interpolated)':

# check inputs
if self.self_prof_n_bins <= 0:
raise ValueError('`self_prof_n_bins` must be greater than 0.')
if self.self_prof_interp_degree_x <= 0:
raise ValueError('`self_prof_interp_degree_x` must be greater than 0.')
if self.self_prof_interp_degree_y <= 0:
raise ValueError('`self_prof_interp_degree_y` must be greater than 0.')

# setup dict of interpolation options
n_bins_interpolated_profile = self.self_prof_n_bins
interp_degree = (self.self_prof_interp_degree_x, self.self_prof_interp_degree_y)
spatial_profile = {'name': 'interpolated_profile',
'n_bins_interpolated_profile': n_bins_interpolated_profile,
'interp_degree': interp_degree}

elif self.horne_ext_profile_selected == 'Gaussian':
spatial_profile = 'gaussian'

else:
raise ValueError("Horne extraction profile must either be 'Gaussian' or 'Self (interpolated)'") # noqa

ext = extract.HorneExtract(inp_sp2d, trace, spatial_profile=spatial_profile)
else:
raise NotImplementedError(f"extraction type '{self.ext_type_selected}' not supported") # noqa

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,59 @@
</v-text-field>
</v-row>

<v-row v-if="ext_type_selected === 'Horne'">
<v-select
attach
:menu-props="{ left: true }"
:items=" horne_ext_profile_items.map(i => i.label)"
v-model="horne_ext_profile_selected"
label="Profile Type"
hint="Profile to use for Horne extractoin."
persistent-hint
></v-select>
</v-row>

<v-row v-if="horne_ext_profile_selected === 'Self (interpolated)'">
cshanahan1 marked this conversation as resolved.
Show resolved Hide resolved
<v-text-field
label="N Bins"
type="number"
v-model.number=self_prof_n_bins
:rules="[() => self_prof_n_bins !== '' || 'This field is required',
() => self_prof_n_bins >0 || 'Number of bins must be greater than zero']"
hint="Number of bins used to measure self profile."
persistent-hint
>
</v-text-field>
</v-row>

<div v-if="ext_type_selected === 'Horne'">
<v-row v-if="horne_ext_profile_selected === 'Self (interpolated)'">
<v-text-field
label="Interpolation Degree (X)"
type="number"
v-model.number=self_prof_interp_degree_x
:rules="[() => self_prof_interp_degree_x !== '' || 'This field is required',
() => self_prof_interp_degree_x >0 || 'X interpolation degree must be be greater than zero']"
hint="Interpolation degree (X) to measure self profile."
persistent-hint
>
</v-text-field>
</v-row>

<v-row v-if="horne_ext_profile_selected === 'Self (interpolated)'">
<v-text-field
label="Interpolation Degree (Y)"
type="number"
v-model.number=self_prof_interp_degree_y
:rules="[() => self_prof_interp_degree_y !== '' || 'This field is required',
() => self_prof_interp_degree_y >0 || 'Y interpolation degree must be be greater than zero']"
hint="Interpolation degree (Y) to measure self profile."
persistent-hint
>
</v-text-field>
</v-row>
</div>

<plugin-add-results
:label.sync="ext_results_label"
:label_default="ext_results_label_default"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import gwcs
import pytest
from astropy.modeling import models
from astropy.nddata import VarianceUncertainty
from astropy.tests.helper import assert_quantity_allclose
import astropy.units as u
from astropy.utils.data import download_file
import numpy as np
from packaging.version import Version
from specreduce import tracing, background, extract
from specutils import Spectrum1D
Expand Down Expand Up @@ -185,3 +190,78 @@ def test_spectrum_on_top(specviz2d_helper):
pext = specviz2d_helper.app.get_tray_item_from_name('spectral-extraction')
assert pext.bg_type_selected == 'OneSided'
assert pext.bg_separation < 0


@pytest.mark.filterwarnings('ignore')
def test_horne_extract_self_profile(specviz2d_helper):

spec2d = np.zeros((40, 100))
spec2dvar = np.ones((40, 100))

for ii in range(spec2d.shape[1]):
mgaus = models.Gaussian1D(amplitude=10,
mean=(9.+(20/spec2d.shape[1])*ii),
stddev=2)
rg = np.arange(0, spec2d.shape[0], 1)
gaus = mgaus(rg)
spec2d[:, ii] = gaus

wave = np.arange(0, spec2d.shape[1], 1)
objectspec = Spectrum1D(spectral_axis=wave*u.m,
flux=spec2d*u.Jy,
uncertainty=VarianceUncertainty(spec2dvar*u.Jy*u.Jy))

specviz2d_helper.load_data(objectspec)
pext = specviz2d_helper.app.get_tray_item_from_name('spectral-extraction')

trace_fit = tracing.FitTrace(objectspec,
trace_model=models.Polynomial1D(degree=1),
window=13, peak_method='gaussian', guess=20)
pext.import_trace(trace_fit)

pext.ext_type.selected = "Horne"
pext.horne_ext_profile.selected = "Self (interpolated)"

# check that correct defaults are set
assert pext.self_prof_n_bins == 10
assert pext.self_prof_interp_degree_x == 1
assert pext.self_prof_interp_degree_y == 1

sp_ext = pext.export_extract_spectrum()

bg_sub = pext.export_bg_sub()

extract_horne_interp = extract.HorneExtract(bg_sub, trace_fit,
spatial_profile='interpolated_profile')

assert_quantity_allclose(extract_horne_interp.spectrum.flux, sp_ext.flux)

# now try changing from defaults
pext.self_prof_n_bins = 5
pext.self_prof_interp_degree_x = 2
pext.self_prof_interp_degree_y = 2

sp_ext = pext.export_extract_spectrum()
bg_sub = pext.export_bg_sub()

extract_horne_interp = extract.HorneExtract(bg_sub, trace_fit,
spatial_profile={'name': 'interpolated_profile',
'n_bins_interpolated_profile': 5,
'interp_degree': (2, 2)})

assert_quantity_allclose(extract_horne_interp.spectrum.flux, sp_ext.flux)

# test that correct errors are raised
pext.self_prof_n_bins = 0
with pytest.raises(ValueError, match='must be greater than 0'):
sp_ext = pext.export_extract_spectrum()

pext.self_prof_n_bins = 1
pext.self_prof_interp_degree_x = 0
with pytest.raises(ValueError, match='must be greater than 0'):
sp_ext = pext.export_extract_spectrum()

pext.self_prof_interp_degree_x = 1
pext.self_prof_interp_degree_y = 0
with pytest.raises(ValueError, match='`self_prof_interp_degree_y` must be greater than 0.'):
sp_ext = pext.export_extract_spectrum()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ dependencies = [
"voila>=0.4,<0.5",
"pyyaml>=5.4.1",
"specutils>=1.15",
"specreduce>=1.3.0,<1.4.0",
"specreduce>=1.4.1",
"photutils>=1.4",
"glue-astronomy>=0.10",
"asteval>=0.9.23",
Expand Down
Loading