specmangle 0.1.0b1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Caden Gobat
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its contributors
17
+ may be used to endorse or promote products derived from this software
18
+ without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,252 @@
1
+ Metadata-Version: 2.4
2
+ Name: specmangle
3
+ Version: 0.1.0b1
4
+ Summary: Photometry-based 'mangling' (calibration) of astronomical spectra
5
+ Author-email: Caden Gobat <cgobat@sdsu.edu>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Documentation, https://specmangle.readthedocs.io/
8
+ Project-URL: Repository, https://github.com/cgobat/specmangle
9
+ Requires-Python: >=3.10
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: astropy
13
+ Requires-Dist: astroquery>=0.4.12.dev10784
14
+ Requires-Dist: numpy
15
+ Requires-Dist: scipy
16
+ Requires-Dist: specutils
17
+ Provides-Extra: test
18
+ Requires-Dist: pytest; extra == "test"
19
+ Provides-Extra: docs
20
+ Requires-Dist: sphinx-astropy[confv3]>=1.11; extra == "docs"
21
+ Requires-Dist: sphinx-design; extra == "docs"
22
+ Dynamic: license-file
23
+
24
+ # specmangle
25
+
26
+ [![CI status](https://github.com/cgobat/specmangle/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/cgobat/specmangle/actions/workflows/tests.yml) [![Documentation Status](https://app.readthedocs.org/projects/specmangle/badge/?version=latest)](https://specmangle.readthedocs.io/en/latest/)
27
+
28
+ `specmangle` applies a smooth, wavelength-dependent multiplicative correction to
29
+ an observed astronomical spectrum such that synthetic broadband photometry based
30
+ on that spectrum matches contemporaneous observations.
31
+
32
+ The package is deliberately narrow in scope, and uses existing
33
+ Astropy-ecosystem data structures and APIs:
34
+
35
+ - spectra are [`specutils.Spectrum`](https://specutils.readthedocs.io/en/stable/types_of_spectra.html) objects
36
+ - photometric measurements are stored in [`astropy.table.Table`](https://docs.astropy.org/en/stable/table/index.html) objects
37
+ - passband profiles are retrieved from the [SVO Filter Profile Service](https://svo2.cab.inta-csic.es/theory/fps/index.php) via [`astroquery.svo_fps`](https://astroquery.readthedocs.io/en/latest/svo_fps/svo_fps.html), or user-defined
38
+
39
+ ## Installation
40
+
41
+ From the project directory:
42
+
43
+ ```bash
44
+ python -m pip install .
45
+ ```
46
+
47
+ For development:
48
+
49
+ ```bash
50
+ python -m pip install -e .
51
+ ```
52
+
53
+ ## Basic use
54
+
55
+ ```python
56
+ import astropy.units as u
57
+ from astropy.nddata import StdDevUncertainty
58
+ from astropy.table import Table
59
+ from specutils import Spectrum
60
+
61
+ from specmangle import mangle
62
+
63
+ spectrum = Spectrum(
64
+ spectral_axis=wavelength * u.AA,
65
+ flux=flux * u.erg / u.s / u.cm**2 / u.AA,
66
+ uncertainty=StdDevUncertainty(
67
+ flux_error,
68
+ unit=u.erg / u.s / u.cm**2 / u.AA,
69
+ ),
70
+ )
71
+
72
+ photometry = Table(
73
+ rows=[
74
+ ("SLOAN/SDSS.g", 18.42, 0.03),
75
+ ("SLOAN/SDSS.r", 18.17, 0.02),
76
+ ("SLOAN/SDSS.i", 18.05, 0.04),
77
+ ],
78
+ names=("band", "mag", "mag_err"),
79
+ )
80
+
81
+ result = mangle(spectrum, photometry)
82
+
83
+ mangled = result.spectrum
84
+ correction = result.correction
85
+ print(result.photometry)
86
+ ```
87
+
88
+ The input photometry table must contain:
89
+
90
+ - `band`: passband identifier, normally an SVO FPS filter ID
91
+ - `mag`: AB magnitude
92
+
93
+ It may additionally contain:
94
+
95
+ - `mag_err`: 1-sigma magnitude uncertainty. Masked values are treated as missing
96
+ uncertainties.
97
+
98
+ Additional columns are preserved in `result.photometry`, which adds `used`,
99
+ `coverage`, `skip_reason`, `synthetic_mag`, `synthetic_mag_err`, and `residual`
100
+ columns. Bands that do not meet `min_coverage` are excluded from the fit and
101
+ reported explicitly in these diagnostic columns.
102
+
103
+ ## Mangling model
104
+
105
+ The correction is positive and multiplicative. It is represented by a natural
106
+ cubic spline in log wavelength and log flux scale, with one free anchor at the
107
+ pivot wavelength of each unique passband. The correction is held constant
108
+ beyond the bluest and reddest anchors rather than extrapolating the spline.
109
+
110
+ The fit minimizes the difference between the mangled spectrum's synthetic AB
111
+ magnitudes and the supplied photometry. When uncertainties are available,
112
+ photometric uncertainties and synthetic-photometry uncertainties propagated from
113
+ the spectrum are combined in quadrature.
114
+
115
+ A nonzero `smoothness` can be supplied to penalize changes in slope between
116
+ neighboring spline anchors:
117
+
118
+ ```python
119
+ result = mangle(spectrum, photometry, smoothness=0.1)
120
+ ```
121
+
122
+ The default is `smoothness=0.0`, so no additional regularization is imposed.
123
+
124
+ The fitted correction can be evaluated on any wavelength grid:
125
+
126
+ ```python
127
+ correction = result.correction_at(new_wavelength)
128
+ ```
129
+
130
+ ## History & heritage
131
+
132
+ The term "spectral mangling" is commonly used for smooth wavelength-dependent
133
+ corrections that are applied to a spectrum to make it reproduce contemporaneous
134
+ broadband photometry. The approach has roots in earlier spectral "warping"
135
+ methods (e.g.
136
+ [Tonry *et al.* 2003](https://scixplorer.org/abs/2003ApJ...594....1T)), was
137
+ formalized under the "mangling" nomenclature by
138
+ [Hsiao *et al.* (2007)](https://scixplorer.org/abs/2007ApJ...663.1187H) and
139
+ [Conley *et al.* (2008)](https://scixplorer.org/abs/2008ApJ...681..482C), and
140
+ was later applied directly to spectrophotometric calibration of observed
141
+ core-collapse supernova spectra by
142
+ [Vincenzi *et al.* (2019)](https://scixplorer.org/abs/2019MNRAS.489.5802V).
143
+
144
+ ## Spectral uncertainties
145
+
146
+ `specmangle` supports the same uncertainty representations supported by
147
+ `specutils` for arithmetic propagation:
148
+
149
+ - `astropy.nddata.StdDevUncertainty`;
150
+ - `astropy.nddata.VarianceUncertainty`;
151
+ - `astropy.nddata.InverseVariance`.
152
+
153
+ For synthetic photometry, uncertainties are converted to standard deviations and
154
+ spectral samples are assumed to be statistically independent. Masked spectral
155
+ samples are omitted.
156
+
157
+ The uncertainty stored on `result.spectrum` is the input spectral uncertainty
158
+ scaled by the best-fit mangling correction. Uncertainty in the correction
159
+ itself is returned separately as `result.correction_uncertainty`, because errors
160
+ in the fitted correction are correlated across wavelength.
161
+
162
+ ## Bandpasses
163
+
164
+ By default, values in the `band` column are passed to the SVO Filter Profile
165
+ Service:
166
+
167
+ ```python
168
+ from specmangle import Bandpass
169
+
170
+ bandpass = Bandpass.from_svo("SLOAN/SDSS.g")
171
+ ```
172
+
173
+ Custom response curves can be supplied instead:
174
+
175
+ ```python
176
+ import astropy.units as u
177
+ from specmangle import Bandpass, mangle
178
+
179
+ custom = Bandpass(
180
+ filter_id="my/filter",
181
+ wavelength=filter_wavelength * u.AA,
182
+ transmission=filter_transmission,
183
+ detector_type="photon",
184
+ )
185
+
186
+ result = mangle(
187
+ spectrum,
188
+ photometry,
189
+ bandpasses={"my/filter": custom},
190
+ )
191
+ ```
192
+
193
+ The synthetic-photometry calculation distinguishes photon-counting and
194
+ energy-counting response curves. SVO's `DetectorType` metadata is used
195
+ automatically for SVO filters.
196
+
197
+ ## Passband coverage
198
+
199
+ By default, at least 98% of the passband's AB-reference signal must fall within
200
+ the wavelength range covered by the valid spectrum samples. This prevents a
201
+ synthetic magnitude from silently relying on a substantially incomplete
202
+ bandpass.
203
+
204
+ The threshold can be changed with `min_coverage`:
205
+
206
+ ```python
207
+ result = mangle(spectrum, photometry, min_coverage=0.95)
208
+ ```
209
+
210
+ ## Direct synthetic photometry
211
+
212
+ For the common/general case of computing an AB magnitude directly from an SVO
213
+ FPS identifier:
214
+
215
+ ```python
216
+ from specmangle import spectrum_to_magnitude
217
+
218
+ mag = spectrum_to_magnitude(spectrum, "SLOAN/SDSS.r")
219
+ ```
220
+
221
+ If the input `Spectrum` includes an uncertainty, the function returns
222
+ `(mag, mag_err)`. Both values carry Astropy magnitude units. An already loaded
223
+ `Bandpass` can be supplied instead of an SVO identifier.
224
+
225
+ The lower-level synthetic-photometry result is also exposed when coverage and
226
+ other diagnostics are useful:
227
+
228
+ ```python
229
+ from specmangle import Bandpass, synthetic_ab_magnitude
230
+
231
+ bandpass = Bandpass.from_svo("SLOAN/SDSS.r")
232
+ synthetic = synthetic_ab_magnitude(spectrum, bandpass)
233
+
234
+ print(synthetic.magnitude)
235
+ print(synthetic.uncertainty)
236
+ print(synthetic.coverage)
237
+ ```
238
+
239
+ ## Result object
240
+
241
+ `mangle` returns a `MangleResult` containing:
242
+
243
+ - `spectrum`: the mangled `specutils.Spectrum`
244
+ - `correction`: multiplicative correction evaluated at each input spectral sample
245
+ - `correction_at(wavelength)`: evaluate the fitted correction at arbitrary wavelengths
246
+ - `correction_uncertainty`: estimated 1-sigma uncertainty in that correction, when identifiable
247
+ - `anchor_wavelengths`: spline-anchor pivot wavelengths
248
+ - `log_correction_parameters`: fitted log-correction values at the anchors
249
+ - `parameter_covariance`: fitted parameter covariance when absolute uncertainties are available
250
+ - `photometry`: the input table with synthetic-photometry diagnostics appended
251
+ - `bandpasses`: the bandpasses used in the fit
252
+ - `optimizer`: the underlying `scipy.optimize.OptimizeResult`
@@ -0,0 +1,229 @@
1
+ # specmangle
2
+
3
+ [![CI status](https://github.com/cgobat/specmangle/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/cgobat/specmangle/actions/workflows/tests.yml) [![Documentation Status](https://app.readthedocs.org/projects/specmangle/badge/?version=latest)](https://specmangle.readthedocs.io/en/latest/)
4
+
5
+ `specmangle` applies a smooth, wavelength-dependent multiplicative correction to
6
+ an observed astronomical spectrum such that synthetic broadband photometry based
7
+ on that spectrum matches contemporaneous observations.
8
+
9
+ The package is deliberately narrow in scope, and uses existing
10
+ Astropy-ecosystem data structures and APIs:
11
+
12
+ - spectra are [`specutils.Spectrum`](https://specutils.readthedocs.io/en/stable/types_of_spectra.html) objects
13
+ - photometric measurements are stored in [`astropy.table.Table`](https://docs.astropy.org/en/stable/table/index.html) objects
14
+ - passband profiles are retrieved from the [SVO Filter Profile Service](https://svo2.cab.inta-csic.es/theory/fps/index.php) via [`astroquery.svo_fps`](https://astroquery.readthedocs.io/en/latest/svo_fps/svo_fps.html), or user-defined
15
+
16
+ ## Installation
17
+
18
+ From the project directory:
19
+
20
+ ```bash
21
+ python -m pip install .
22
+ ```
23
+
24
+ For development:
25
+
26
+ ```bash
27
+ python -m pip install -e .
28
+ ```
29
+
30
+ ## Basic use
31
+
32
+ ```python
33
+ import astropy.units as u
34
+ from astropy.nddata import StdDevUncertainty
35
+ from astropy.table import Table
36
+ from specutils import Spectrum
37
+
38
+ from specmangle import mangle
39
+
40
+ spectrum = Spectrum(
41
+ spectral_axis=wavelength * u.AA,
42
+ flux=flux * u.erg / u.s / u.cm**2 / u.AA,
43
+ uncertainty=StdDevUncertainty(
44
+ flux_error,
45
+ unit=u.erg / u.s / u.cm**2 / u.AA,
46
+ ),
47
+ )
48
+
49
+ photometry = Table(
50
+ rows=[
51
+ ("SLOAN/SDSS.g", 18.42, 0.03),
52
+ ("SLOAN/SDSS.r", 18.17, 0.02),
53
+ ("SLOAN/SDSS.i", 18.05, 0.04),
54
+ ],
55
+ names=("band", "mag", "mag_err"),
56
+ )
57
+
58
+ result = mangle(spectrum, photometry)
59
+
60
+ mangled = result.spectrum
61
+ correction = result.correction
62
+ print(result.photometry)
63
+ ```
64
+
65
+ The input photometry table must contain:
66
+
67
+ - `band`: passband identifier, normally an SVO FPS filter ID
68
+ - `mag`: AB magnitude
69
+
70
+ It may additionally contain:
71
+
72
+ - `mag_err`: 1-sigma magnitude uncertainty. Masked values are treated as missing
73
+ uncertainties.
74
+
75
+ Additional columns are preserved in `result.photometry`, which adds `used`,
76
+ `coverage`, `skip_reason`, `synthetic_mag`, `synthetic_mag_err`, and `residual`
77
+ columns. Bands that do not meet `min_coverage` are excluded from the fit and
78
+ reported explicitly in these diagnostic columns.
79
+
80
+ ## Mangling model
81
+
82
+ The correction is positive and multiplicative. It is represented by a natural
83
+ cubic spline in log wavelength and log flux scale, with one free anchor at the
84
+ pivot wavelength of each unique passband. The correction is held constant
85
+ beyond the bluest and reddest anchors rather than extrapolating the spline.
86
+
87
+ The fit minimizes the difference between the mangled spectrum's synthetic AB
88
+ magnitudes and the supplied photometry. When uncertainties are available,
89
+ photometric uncertainties and synthetic-photometry uncertainties propagated from
90
+ the spectrum are combined in quadrature.
91
+
92
+ A nonzero `smoothness` can be supplied to penalize changes in slope between
93
+ neighboring spline anchors:
94
+
95
+ ```python
96
+ result = mangle(spectrum, photometry, smoothness=0.1)
97
+ ```
98
+
99
+ The default is `smoothness=0.0`, so no additional regularization is imposed.
100
+
101
+ The fitted correction can be evaluated on any wavelength grid:
102
+
103
+ ```python
104
+ correction = result.correction_at(new_wavelength)
105
+ ```
106
+
107
+ ## History & heritage
108
+
109
+ The term "spectral mangling" is commonly used for smooth wavelength-dependent
110
+ corrections that are applied to a spectrum to make it reproduce contemporaneous
111
+ broadband photometry. The approach has roots in earlier spectral "warping"
112
+ methods (e.g.
113
+ [Tonry *et al.* 2003](https://scixplorer.org/abs/2003ApJ...594....1T)), was
114
+ formalized under the "mangling" nomenclature by
115
+ [Hsiao *et al.* (2007)](https://scixplorer.org/abs/2007ApJ...663.1187H) and
116
+ [Conley *et al.* (2008)](https://scixplorer.org/abs/2008ApJ...681..482C), and
117
+ was later applied directly to spectrophotometric calibration of observed
118
+ core-collapse supernova spectra by
119
+ [Vincenzi *et al.* (2019)](https://scixplorer.org/abs/2019MNRAS.489.5802V).
120
+
121
+ ## Spectral uncertainties
122
+
123
+ `specmangle` supports the same uncertainty representations supported by
124
+ `specutils` for arithmetic propagation:
125
+
126
+ - `astropy.nddata.StdDevUncertainty`;
127
+ - `astropy.nddata.VarianceUncertainty`;
128
+ - `astropy.nddata.InverseVariance`.
129
+
130
+ For synthetic photometry, uncertainties are converted to standard deviations and
131
+ spectral samples are assumed to be statistically independent. Masked spectral
132
+ samples are omitted.
133
+
134
+ The uncertainty stored on `result.spectrum` is the input spectral uncertainty
135
+ scaled by the best-fit mangling correction. Uncertainty in the correction
136
+ itself is returned separately as `result.correction_uncertainty`, because errors
137
+ in the fitted correction are correlated across wavelength.
138
+
139
+ ## Bandpasses
140
+
141
+ By default, values in the `band` column are passed to the SVO Filter Profile
142
+ Service:
143
+
144
+ ```python
145
+ from specmangle import Bandpass
146
+
147
+ bandpass = Bandpass.from_svo("SLOAN/SDSS.g")
148
+ ```
149
+
150
+ Custom response curves can be supplied instead:
151
+
152
+ ```python
153
+ import astropy.units as u
154
+ from specmangle import Bandpass, mangle
155
+
156
+ custom = Bandpass(
157
+ filter_id="my/filter",
158
+ wavelength=filter_wavelength * u.AA,
159
+ transmission=filter_transmission,
160
+ detector_type="photon",
161
+ )
162
+
163
+ result = mangle(
164
+ spectrum,
165
+ photometry,
166
+ bandpasses={"my/filter": custom},
167
+ )
168
+ ```
169
+
170
+ The synthetic-photometry calculation distinguishes photon-counting and
171
+ energy-counting response curves. SVO's `DetectorType` metadata is used
172
+ automatically for SVO filters.
173
+
174
+ ## Passband coverage
175
+
176
+ By default, at least 98% of the passband's AB-reference signal must fall within
177
+ the wavelength range covered by the valid spectrum samples. This prevents a
178
+ synthetic magnitude from silently relying on a substantially incomplete
179
+ bandpass.
180
+
181
+ The threshold can be changed with `min_coverage`:
182
+
183
+ ```python
184
+ result = mangle(spectrum, photometry, min_coverage=0.95)
185
+ ```
186
+
187
+ ## Direct synthetic photometry
188
+
189
+ For the common/general case of computing an AB magnitude directly from an SVO
190
+ FPS identifier:
191
+
192
+ ```python
193
+ from specmangle import spectrum_to_magnitude
194
+
195
+ mag = spectrum_to_magnitude(spectrum, "SLOAN/SDSS.r")
196
+ ```
197
+
198
+ If the input `Spectrum` includes an uncertainty, the function returns
199
+ `(mag, mag_err)`. Both values carry Astropy magnitude units. An already loaded
200
+ `Bandpass` can be supplied instead of an SVO identifier.
201
+
202
+ The lower-level synthetic-photometry result is also exposed when coverage and
203
+ other diagnostics are useful:
204
+
205
+ ```python
206
+ from specmangle import Bandpass, synthetic_ab_magnitude
207
+
208
+ bandpass = Bandpass.from_svo("SLOAN/SDSS.r")
209
+ synthetic = synthetic_ab_magnitude(spectrum, bandpass)
210
+
211
+ print(synthetic.magnitude)
212
+ print(synthetic.uncertainty)
213
+ print(synthetic.coverage)
214
+ ```
215
+
216
+ ## Result object
217
+
218
+ `mangle` returns a `MangleResult` containing:
219
+
220
+ - `spectrum`: the mangled `specutils.Spectrum`
221
+ - `correction`: multiplicative correction evaluated at each input spectral sample
222
+ - `correction_at(wavelength)`: evaluate the fitted correction at arbitrary wavelengths
223
+ - `correction_uncertainty`: estimated 1-sigma uncertainty in that correction, when identifiable
224
+ - `anchor_wavelengths`: spline-anchor pivot wavelengths
225
+ - `log_correction_parameters`: fitted log-correction values at the anchors
226
+ - `parameter_covariance`: fitted parameter covariance when absolute uncertainties are available
227
+ - `photometry`: the input table with synthetic-photometry diagnostics appended
228
+ - `bandpasses`: the bandpasses used in the fit
229
+ - `optimizer`: the underlying `scipy.optimize.OptimizeResult`
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "specmangle"
7
+ dynamic = ["version"]
8
+ description = "Photometry-based 'mangling' (calibration) of astronomical spectra"
9
+ readme = "README.md"
10
+ license = "BSD-3-Clause"
11
+ license-files = ["LICENSE"]
12
+ authors = [
13
+ {name = "Caden Gobat", email = "cgobat@sdsu.edu"},
14
+ ]
15
+ requires-python = ">=3.10"
16
+ dependencies = [
17
+ "astropy",
18
+ "astroquery>=0.4.12.dev10784",
19
+ "numpy",
20
+ "scipy",
21
+ "specutils",
22
+ ]
23
+
24
+ [project.optional-dependencies]
25
+ test = ["pytest"]
26
+ docs = [
27
+ "sphinx-astropy[confv3]>=1.11",
28
+ "sphinx-design",
29
+ ]
30
+
31
+ [project.urls]
32
+ Documentation = "https://specmangle.readthedocs.io/"
33
+ Repository = "https://github.com/cgobat/specmangle"
34
+
35
+ [tool.setuptools]
36
+ package-dir = {specmangle = "src"}
37
+
38
+ [tool.setuptools.dynamic]
39
+ version = {attr = "specmangle.__version__"}
40
+
41
+ [tool.pytest.ini_options]
42
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+