specsr.data.stitch¶
Combine the medium gratings into one high-resolution reference.
Coverage¶
The three medium gratings overlap rather than abutting:
G140M 0.70 -------- 2.20
G235M 1.66 --------------- 4.00
G395M 2.87 --------------- 5.48
Each covers only part of the 1.0-5.3 um grid on its own (45%, 48% and 36% respectively), but the overlaps fall exactly where individual arms degrade at their edges: G235M’s unusable blue edge is covered by G140M, and its unusable red run by G395M. Stitched, coverage is 98.3% +/- 2.8% of the grid, and no wavelength is invalid for a majority of targets. The residual ~1.7% is target-specific.
Never invent the target¶
The remaining invalid samples matter more than their 1.7% suggests, because this is the reference the model is trained to reproduce.
The original pipeline filled every non-finite sample with the per-spectrum median:
def replace_nans(arr):
median = np.median(arr[~np.isnan(arr)])
return np.where(np.isnan(arr), median, arr)
Applied to flux_high, that trains the model to emit a flat constant wherever
the detector did not measure — and penalises it for placing a real emission line
there. Filling is strictly worse than excluding: zero teaches “no flux here”,
the median teaches “featureless continuum here”, and both are fabrications.
So this module returns an explicit valid mask alongside flux and error, and
leaves invalid samples as nan so that a caller which ignores the mask fails
loudly instead of training on a fabricated value.
The consuming contract has two halves, and both are required
(see specsr.training.losses.sr1_deblend_loss()):
Replace invalid samples with a neutral numerical value — zero, in the per-spectrum normalised units the losses work in. Not because the value means anything, but because the line mask is built by smoothing the reference over a wide kernel, and an arbitrary number at an unmeasured wavelength would be dragged into neighbouring measured samples and mistaken for a line.
Pass
validto the loss so those samples contribute no gradient.
Filling without masking is what the original pipeline did. Masking without filling leaves the fill value free to contaminate neighbourhood statistics.
Functions
|
Resample one arm onto |
|
Combine medium gratings into one reference on the common grid. |
Classes
|
A reference spectrum on the common grid, with an explicit validity mask. |
- class specsr.data.stitch.StitchedSpectrum(wavelength, flux, flux_err, valid, n_arms)[source]¶
Bases:
objectA reference spectrum on the common grid, with an explicit validity mask.
- Variables:
wavelength (numpy.ndarray) – The common grid, microns.
flux_err (flux,) – Inverse-variance combined across arms where they overlap. Invalid samples hold
nan— deliberately, so that a caller which forgets the mask fails loudly rather than training on a fabricated value.valid (numpy.ndarray) – True where at least one arm genuinely measured this wavelength.
n_arms (numpy.ndarray) – How many arms contributed to each sample. Useful for diagnostics: a sample built from two arms in an overlap region is better constrained than one from a single arm at the edge of its range.
- Parameters:
- specsr.data.stitch.stitch_gratings(arms, grid=None, min_coverage=0.5)[source]¶
Combine medium gratings into one reference on the common grid.
- Parameters:
arms (dict[str, dict]) –
{name: {"wavelength": ..., "flux": ..., "flux_err": ...}}, as returned byspecsr.data.ingest.read_spectrum().grid (LogWavelengthGrid | None) – Target grid; defaults to
specsr.data.grid.DEFAULT_GRID.min_coverage (float) – Fraction of an output bin that must be genuinely measured for that sample to count as valid.
- Return type:
Notes
Overlapping arms are combined by inverse-variance weighting, which is the right estimator when the arms are independent measurements of the same quantity: it weights each by how well it constrains the flux, so the noisier edge of one arm does not degrade the well-measured middle of another. A simple average would let a barely-detected edge sample pull the combined value around.
Invalid samples are left as
nanand markedvalid=False. They are never filled — see the module docstring.
- specsr.data.stitch.resample_with_mask(wave_in, flux_in, err_in, wave_out, min_coverage=0.5)[source]¶
Resample one arm onto
wave_out, tracking which samples are real.Validity is resampled as an indicator alongside the flux, so a bin that draws mostly on non-finite input is marked invalid rather than silently inheriting whatever finite neighbours happened to overlap it.
min_coverageis the fraction of a bin that must come from valid input.