-
-
Notifications
You must be signed in to change notification settings - Fork 39
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
Implement 1D wavelength calibration classes #162
Changes from 21 commits
d58f652
1f3483a
c54aed7
5eb4aaa
baa706c
02ed67e
1df8621
eb5feea
6ea98e4
af4a523
568fd09
be35c09
f3b1b9f
8117864
8f1535b
22e2d68
4a508e3
a8ca23f
1941a26
829dc5c
c3ba30d
62c8025
12a8661
5063b5e
b6a92f8
1aa62c6
7f31b61
45939a9
f92bac5
d573219
82170a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
.. _wavelength_calibration: | ||
|
||
Wavelength Calibration | ||
====================== | ||
|
||
Wavelength calibration is currently supported for 1D spectra. Given a list of spectral | ||
lines with known wavelengths and estimated pixel positions on an input calibration | ||
spectrum, you can currently use ``specreduce`` to: | ||
|
||
#. Refine the pixel position estimates of the lines based on features in the input | ||
calibration spectrum. | ||
#. Fit an ``astropy`` model to the wavelength/pixel pairs to generate a spectral WCS | ||
solution for the dispersion. | ||
#. Apply the generated spectral WCS to other `~specutils.Spectrum1D` objects. | ||
|
||
Calibration Lines | ||
----------------- | ||
|
||
``specreduce`` provides a `~specreduce.wavelength_calibration.CalibrationLine` class for | ||
encoding the information about each spectral line to be used in the dispersion model | ||
solution fitting. The minimum information required is the wavelength and estimated pixel | ||
location on the calibration spectrum of each line. By default, no refinement of the pixel | ||
position is done; to enable this feature you must provide a refinement method and any | ||
associated keywords (currently ``range`` defines the number of pixels on either side | ||
of the estimated location to use for all available refinement options). For example, | ||
to define a line and use ``specreduce`` to update the pixel location the the centroid | ||
of a gaussian fit to the line, you would do the following:: | ||
|
||
import astropy.units as u | ||
from specreduce import CalibrationLine | ||
test_line = CalibrationLine(calibration_spectrum, 6562.81*u.AA, 500, | ||
refinement_method="gaussian", | ||
refinement_kwargs={"range": 10}) | ||
refined_line = test_line.with_refined_pixel() | ||
|
||
Note that in this example and in the example below, ``calibration_spectrum`` is a | ||
`~specutils.Spectrum1D` spectrum to be used to refine the line locations (for example | ||
a lamp spectrum). | ||
|
||
1D Wavelength Calibration | ||
------------------------- | ||
|
||
Of course, refining the location of a line is only really useful when done for multiple | ||
lines to be fit as part of the dispersion model. This can be accomplished with the | ||
`~specreduce.wavelength_calibration.WavelengthCalibration1D` class as follows below. | ||
Note that the lines can be input as simple (wavelength, pixel) pairs rather than as | ||
`~specreduce.wavelength_calibration.CalibrationLine` objects - in this case the | ||
``default_refinement_method`` and ``default_refinement_kwargs`` will be applied to | ||
each input line:: | ||
|
||
import astropy.units as u | ||
from specreduce import WavelengthCalibration1D | ||
test_cal = WavelengthCalibration1D(calibration_spectrum, | ||
[(5000*u.AA, 0), (5100*u.AA, 10), | ||
(5198*u.AA, 20), (5305*u.AA, 30)], | ||
default_refinement_method="gaussian") | ||
calibrated_spectrum = test_cal.apply_to_spectrum(science_spectrum) | ||
|
||
The example above uses the default model (`~astropy.modeling.functional_models.Linear1D`) | ||
to fit the input spectral lines, and then applies the calculated WCS solution to a second | ||
spectrum (``science_spectrum``). Any other ``astropy`` model can be provided as the | ||
input ``model`` parameter to the `~specreduce.wavelength_calibration.WavelengthCalibration1D`. | ||
In the above example, the model fit and WCS construction is all done as part of the | ||
``apply_to_spectrum()`` call, but you could also access the `~gwcs.wcs.WCS` object itself | ||
by calling:: | ||
|
||
test_cal.wcs | ||
|
||
The calculated WCS is a cached property that will be cleared if the ``lines``, ``model``, | ||
or ``input_spectrum`` properties are updated, since these will alter the calculated dispersion | ||
fit. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,10 @@ | |
import os | ||
|
||
from astropy.version import version as astropy_version | ||
import astropy.units as u | ||
import numpy as np | ||
import pytest | ||
from specutils import Spectrum1D | ||
|
||
# For Astropy 3.0 and later, we can use the standalone pytest plugin | ||
if astropy_version < '3.0': | ||
|
@@ -20,6 +24,37 @@ | |
ASTROPY_HEADER = False | ||
|
||
|
||
@pytest.fixture | ||
def spec1d(): | ||
np.random.seed(7) | ||
flux = np.random.random(50)*u.Jy | ||
sa = np.arange(0, 50)*u.pix | ||
spec = Spectrum1D(flux, spectral_axis=sa) | ||
return spec | ||
|
||
|
||
@pytest.fixture | ||
def spec1d_with_emission_line(): | ||
np.random.seed(7) | ||
sa = np.arange(0, 200)*u.pix | ||
flux = (np.random.randn(200) + | ||
10*np.exp(-0.01*((sa.value-130)**2)) + | ||
sa.value/100) * u.Jy | ||
spec = Spectrum1D(flux, spectral_axis=sa) | ||
return spec | ||
|
||
|
||
@pytest.fixture | ||
def spec1d_with_absorption_line(): | ||
np.random.seed(7) | ||
sa = np.arange(0, 200)*u.pix | ||
flux = (np.random.randn(200) - | ||
10*np.exp(-0.01*((sa.value-130)**2)) + | ||
sa.value/100) * u.Jy | ||
spec = Spectrum1D(flux, spectral_axis=sa) | ||
return spec | ||
|
||
|
||
Comment on lines
+27
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would this benefit from using the synthetic spectra from #165? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but I think using that to expand/improve the tests can be another PR once both of these are merged. |
||
def pytest_configure(config): | ||
|
||
if ASTROPY_HEADER: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
from numpy.testing import assert_allclose | ||
|
||
from astropy.table import QTable | ||
import astropy.units as u | ||
from astropy.modeling.models import Polynomial1D | ||
from astropy.tests.helper import assert_quantity_allclose | ||
|
||
from specreduce import WavelengthCalibration1D | ||
|
||
|
||
def test_linear_from_list(spec1d): | ||
centers = [0, 10, 20, 30] | ||
w = [5000, 5100, 5198, 5305]*u.AA | ||
test = WavelengthCalibration1D(spec1d, centers, line_wavelengths=w) | ||
spec2 = test.apply_to_spectrum(spec1d) | ||
|
||
assert_quantity_allclose(spec2.spectral_axis[0], 4998.8*u.AA) | ||
assert_quantity_allclose(spec2.spectral_axis[-1], 5495.169999*u.AA) | ||
|
||
|
||
def test_linear_from_table(spec1d): | ||
centers = [0, 10, 20, 30] | ||
w = [5000, 5100, 5198, 5305]*u.AA | ||
table = QTable([centers, w], names=["pixel_center", "wavelength"]) | ||
test = WavelengthCalibration1D(spec1d, table) | ||
spec2 = test.apply_to_spectrum(spec1d) | ||
|
||
assert_quantity_allclose(spec2.spectral_axis[0], 4998.8*u.AA) | ||
assert_quantity_allclose(spec2.spectral_axis[-1], 5495.169999*u.AA) | ||
|
||
|
||
def test_poly_from_table(spec1d): | ||
# This test is mostly to prove that you can use other models | ||
centers = [0, 10, 20, 30, 40] | ||
w = [5005, 5110, 5214, 5330, 5438]*u.AA | ||
table = QTable([centers, w], names=["pixel_center", "wavelength"]) | ||
|
||
test = WavelengthCalibration1D(spec1d, table, model=Polynomial1D(2)) | ||
test.apply_to_spectrum(spec1d) | ||
|
||
assert_allclose(test.model.parameters, [5.00477143e+03, 1.03457143e+01, 1.28571429e-02]) | ||
|
||
|
||
def test_replace_spectrum(spec1d, spec1d_with_emission_line): | ||
centers = [0, 10, 20, 30]*u.pix | ||
w = [5000, 5100, 5198, 5305]*u.AA | ||
test = WavelengthCalibration1D(spec1d, centers, line_wavelengths=w) | ||
# Accessing this property causes fits the model and caches the resulting WCS | ||
test.wcs | ||
assert "wcs" in test.__dict__ | ||
|
||
# Replace the input spectrum, which should clear the cached properties | ||
test.input_spectrum = spec1d_with_emission_line | ||
assert "wcs" not in test.__dict__ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe specify "any 1D model"