Source code for sofia_redux.spectroscopy.speccor
# Licensed under a 3-clause BSD style license - see LICENSE.rst
from astropy import log
import numpy as np
from sofia_redux.toolkit.stats import medcomb
from sofia_redux.toolkit.convolve.kernel import savitzky_golay
__all__ = ['speccor']
[docs]
def speccor(stack, fwidth=4, err_stack=None, refspec=None, select=None,
window=11, info=None):
"""
Correct a _stack of spectra for shape differences
Returns an array of spectra corrected to the reference spectrum
Parameters
----------
stack : array_like of (int or float)
(n_spectra, n_wave) array of spectra to be corrected.
fwidth : (int or float), optional
FFT filter width
err_stack : array_like of (int or float)
(n_spectra, n_wave) array of errors corresponding to the _stack.
If given, the corrected error array is given as a second output.
refspec : array_like of (int or float)
(n_wave,) reference spectrum that the spectra in the _stack
are corrected to.
select : array_like of (int or bool)
(n_spectra,) array denoting which spectra to use to determine
the reference spectrum. However, all the spectra in the _stack
are scaled to the reference spectrum (True = good, False = bad).
window : int, optional
Positive odd integer defining the width of the Savitzky-Golay used
to smooth each spectra before FFT.
info : dict, optional
If supplied will be updated with mask and corrections
Returns
-------
numpy.ndarray or (numpy.ndarray, numpy.ndarray)
(n_spectra, n_wave) array of spectra corrected to the reference
spectrum. If `err_stack` is supplied, it will be returned as
a second array.
"""
stack = np.array(stack)
if stack.ndim < 2:
log.error("Invalid _stack input")
return
if err_stack is not None:
err_stack = np.array(err_stack)
if err_stack.shape[:2] != stack.shape[:2]:
log.error("Error _stack does not match input _stack")
return
doerr = True
else:
doerr = False
n_spec, n_wave = stack.shape[:2]
if select is None:
goodspec = np.arange(n_spec)
else:
goodspec = np.where(select)[0]
goodpix = np.all(np.isfinite(stack), axis=0)
npoints = goodpix.sum()
if not goodpix.all():
badidx = np.arange(n_spec), np.argwhere(~goodpix)
stack[badidx] = np.nan
# Get shape reference spectrum
if refspec is not None:
rspec = refspec
else:
rspec, _ = medcomb(stack[goodspec], axis=0)
# Create filter
ffilter = np.empty(npoints)
mid, mod2 = np.divmod(npoints, 2)[:2]
ffilter[:mid + mod2] = np.arange(mid + mod2)
ffilter[mid + mod2:] = np.arange(mid)[::-1]
ffilter = 1 / (1 + (ffilter / fwidth) ** 10)
# Filter the reference spectrum
ref_low_freq = np.fft.ifft(np.fft.fft(rspec[goodpix]) * ffilter).real
update = isinstance(info, dict)
if update:
info['corrections'] = np.zeros((n_spec, npoints))
info['correction_mask'] = goodpix
x = np.arange(n_wave).astype(float)
for i, spectrum in enumerate(stack):
# Smooth to remove bad pixels that interfere with the FFT
spec_sg = savitzky_golay(
x, spectrum, window, robust=5, eps=0.1)
low_freq = np.fft.ifft(np.fft.fft(spec_sg[goodpix]) * ffilter).real
correction = ref_low_freq / low_freq
stack[i, goodpix] *= correction
if update:
info['corrections'][i] = correction
if doerr:
err_stack[i, goodpix] *= correction
if doerr:
return stack, err_stack
else:
return stack