Skip to content

Commit 6e26d79

Browse files
committed
First draft of continuum normalization
1 parent fd60860 commit 6e26d79

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

specutils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
# Allow loading spectrum object from top level module
2323
from .spectra import *
24+
from . import manipulation
2425

2526

2627
def load_user():

specutils/manipulation/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22

33
from .smoothing import * # noqa
44
from .estimate_uncertainty import * # noqa
5+
from .continuum import *
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
from __future__ import division
2+
3+
from astropy import modeling
4+
from astropy.modeling import models, fitting
5+
from ..spectra import Spectrum1D
6+
7+
import numpy as np
8+
9+
import logging
10+
11+
__all__ = ['fit_continuum_generic']
12+
13+
def fit_continuum_generic(spectrum,
14+
model=None, fitter=None,
15+
sigma=3.0, sigma_lower=None, sigma_upper=None, iters=5,
16+
exclude_regions=[],
17+
full_output=False):
18+
"""
19+
Fit a generic continuum model to a spectrum.
20+
21+
The default algorithm is iterative sigma clipping
22+
23+
Parameters
24+
----------
25+
spectrum : `~specutils.Spectrum1D`
26+
The `~specutils.Spectrum1D` object to which a continuum model is fit
27+
28+
model : `XXXX`
29+
The type of model to use for the continuum.
30+
astropy.modeling.models
31+
Must either be astropy.modeling.Fittable1DModel
32+
or the string "spline" (since this is not currently implemented)
33+
Default: models.Chebyshev1D(3)
34+
35+
fitter : `XXXX`
36+
The type of fitter to use for the continuum.
37+
astropy.modeling.fitting
38+
Default: fitting.LevMarLSQFitter()
39+
40+
sigma : float, optional
41+
The number of standard deviations to use for both lower and upper clipping limit.
42+
Defaults to 3.0
43+
44+
sigma_lower : float or None, optional
45+
Number of standard deviations for lower bound clipping limit.
46+
If None (default), then `sigma` is used.
47+
48+
sigma_upper : float or None, optional
49+
Number of standard deviations for upper bound clipping limit.
50+
If None (default), then `sigma` is used.
51+
52+
iters : int or None, optional
53+
Number of iterations to perform sigma clipping.
54+
If None, clips until convergence achieved.
55+
Defaults to 5
56+
57+
exclude_regions : list of tuples, optional
58+
A list of dispersion regions to exclude.
59+
Each tuple must be sorted.
60+
e.g. [(6555,6575)]
61+
62+
full_output : bool, optional
63+
If True, return more information.
64+
Currently, just the model and the pixels-used boolean array
65+
66+
Returns
67+
-------
68+
continuum_model : `XXXX`
69+
Output `XXXX` which is a model for the continuum
70+
71+
Raises
72+
------
73+
ValueError
74+
In the case that ``spectrum`` .... is not the correct type
75+
76+
"""
77+
78+
## Parameter checks
79+
if not isinstance(spectrum, Spectrum1D):
80+
raise ValueError('The spectrum parameter must be a Spectrum1D object')
81+
for exclude_region in exclude_regions:
82+
if len(exclude_region) != 2:
83+
raise ValueError('All exclusion regions must be of length 2')
84+
if exclude_region[0] >= exclude_region[1]:
85+
raise ValueError('All exclusion regions must be (low, high)')
86+
87+
## Set default model and fitter
88+
if model is None:
89+
logging.info("Using Chebyshev1D(3) as default continuum model")
90+
model = models.Chebyshev1D(3)
91+
if fitter is None:
92+
fitter = fitting.LevMarLSQFitter()
93+
if not isinstance(model, modeling.FittableModel):
94+
raise ValueError('The model parameter must be a astropy.modeling.FittableModel object')
95+
## TODO this is waiting on a refactor in fitting to work
96+
#if not isinstance(fitter, fitting.Fitter):
97+
# raise ValueError('The model parameter must be a astropy.modeling.fitting.Fitter object')
98+
99+
## Get input spectrum data
100+
x = spectrum.spectral_axis.value
101+
y = spectrum.flux.value
102+
103+
## Set up valid pixels mask
104+
## Exclude non-finite values
105+
good = np.isfinite(y)
106+
## Exclude regions
107+
for (excl1, excl2) in exclude_regions:
108+
good[np.logical_and(x > excl1, x < excl2)] = False
109+
110+
## Set up sigma clipping
111+
if sigma_lower is None: sigma_lower = sigma
112+
if sigma_upper is None: sigma_upper = sigma
113+
114+
for i_iter in range(iters):
115+
logging.info("Iter {}: Fitting {}/{} pixels".format(i_iter, good.sum(), len(good)))
116+
## Fit model
117+
## TODO include data uncertainties
118+
new_model = fitter(model, x[good], y[good])
119+
120+
## Sigma clip
121+
difference = new_model(x) - y
122+
finite = np.isfinite(difference)
123+
sigma_difference = difference / np.std(difference[np.logical_and(good, finite)])
124+
good[sigma_difference > sigma_upper] = False
125+
good[sigma_difference < -sigma_lower] = False
126+
127+
## Update model iteratively: it is initialized at the previous fit's values
128+
#model = new_model
129+
130+
model = new_model
131+
if full_output:
132+
return model, good
133+
return model

0 commit comments

Comments
 (0)