Tutorial: long-slit reduction (aurora, stellar, solar)¶
Take a raw 2-D detector frame and walk it all the way to a flux-calibrated 1-D spectrum on a uniform Å grid, ready for line fitting or BeSS submission. This tutorial shows how the 13 long-slit bricks added in v0.2.0 compose into a complete reduction. The example uses an aurora-style setup (sky emission lines as wavelength reference), but the same recipe works for stellar long-slit, solar twilight, or any small-spectrograph field setup.
If you only need 1-D analysis on an already-reduced spectrum, see Analyse a spectrum instead.
Pipeline overview¶
The new bricks form three groups, with one entry point each:
| Group | Bricks | When to use |
|---|---|---|
| Geometry | correct_tilt_affine, correct_slant_affine, correct_smile_polynomial |
Once per instrument: align slit with columns, rectify monochromatic lines, undo cylindrical smile of concave gratings. |
| 2-D denoising | outlier_rejection_mad_adaptive, denoise_gaussian_2d, denoise_median_2d |
After cosmic-ray clipping, before extraction. CMOS RTS / hot pixels. |
| In-situ λ | extract_sky_lateral_bands + fit_emission_lines_gaussian + wavelength_calibration_in_situ (or wavelength_calibration_solar) |
When no arc lamp is available - field aurora monitors, twilight, daytime solar. |
Two postprocessing helpers and the BeSS exporter round out the set:
| Group | Bricks |
|---|---|
| Postprocessing 1-D | combine_spectra_arithmetic (response/std-star division), normalize_to_region (normalise to a chosen continuum band) |
| Export | export_fits_bess (BeSS / ARAS-compliant FITS with full header, JD/MJD, canonical filename) |
A complete preset¶
The kernel's preset format strings these bricks together. The example below is the field-aurora workflow; substitute parameters for stellar or solar setups.
# presets/aurora_long_slit.yaml
name: aurora_long_slit
description: |
Long-slit aurora reduction: geometry → denoising → extraction →
in-situ wavelength calibration → continuum normalisation.
steps:
# ── 1. Geometric rectification (instrument-specific, one-shot values).
- algorithm: correct_tilt_affine
params: {tilt_deg: 0.4}
- algorithm: correct_slant_affine
params: {slant_deg: 0.6, pivot_row: 120}
- algorithm: correct_smile_polynomial
params: {reference_row: 120, smile_radius: 1500.0, polynomial_order: 4}
# ── 2. Per-frame denoising (skip the science band when running median).
- algorithm: outlier_rejection_mad_adaptive
params: {kernel_size: 3, threshold: 0.6}
- algorithm: denoise_median_2d
params: {kernel_size: 3, row_lo: 0, row_hi: 100} # sky strip only
# ── 3. Sky-aware extraction.
- algorithm: extract_sky_lateral_bands
params:
band_above_lo: 80
band_above_hi: 110
band_below_lo: 130
band_below_hi: 160
- algorithm: subtract_sky_2d
params: {trace_row: 120, trace_half_width: 6, sky_offset: 4, sky_half_width: 10}
# extract_spectrum_boxcar is the edge-safe boxcar for extended sources
# (aurora, nebulae). For a stellar point source, extract_spectrum_sum
# or extract_spectrum_easyspec are also valid choices.
- algorithm: extract_spectrum_boxcar
params: {trace_method: "argmax", trace_half_width: 6, extraction_weights: "tophat", shift_y_pixels: 0}
# ── 4. In-situ wavelength calibration from the simultaneously-acquired
# sky reference (no arc lamp needed).
- algorithm: wavelength_calibration_in_situ
params:
polynomial_coef: [1.0, 4000.0] # initial λ(x) ≈ 4000 + x
reference_wavelengths: [5577.34, 6300.30, 6363.78]
guess_positions: [1577, 2300, 2363]
search_width: 40.0
oversampling: 2.0
# ── 5. Continuum normalisation to a known line-free band.
- algorithm: normalize_to_region
params: {wave_lo: 6400.0, wave_hi: 6500.0}
# ── 6. BeSS-compliant FITS for sharing.
- algorithm: export_fits_bess
params:
object_name: "aurora_2026-06-09"
instrument: "Alpy-600"
site: "Skibotn"
observer: "field-monitor"
date_obs_utc: "2026-06-09T22:13:45"
exposure_seconds: 30.0
vhelio_kms: 0.0
Save and run with the CLI:
… or in Python:
from spectro_kernel import WorkContext, run_preset
from spectro_kernel.io import read_fits_image # 2D variant
ctx = WorkContext(image=read_fits_image("my_raw_frame.fits"))
result = run_preset("aurora_long_slit", ctx)
print(result.history) # full audit trail
bess_blob = ctx.exports["fits_bess"]
Variants¶
Stellar long-slit (arc-lamp calibration)¶
Replace step 4 with wavelength_calibrate_polynomial (existing brick)
on identifications from your arc-lamp frame. Steps 1–3, 5 and 6 stay
as-is.
Solar / twilight (no sky lines, no arc lamp)¶
Use wavelength_calibration_solar in place of step 3+4. It needs only
two instrument-specific values (an approximate λ_min and a
dispersion guess in Å/pixel) and the built-in Fraunhofer catalogue:
- algorithm: wavelength_calibration_solar
params:
approx_wavelength_min_angstrom: 3800.0
approx_dispersion_angstrom_per_pixel: 1.0
poly_order: 3
min_lines_for_fit: 6
Response correction with a standard star¶
After step 5 (or in place of it), divide by the instrumental response:
- algorithm: combine_spectra_arithmetic
params:
operation: div
reference_path: "/path/to/response.fits"
min_denominator: 1.0e-5
The reference can also live in ctx.extras["reference_spectrum"] if
your pipeline derives it on the fly - same idiom as the existing
remove_telluric_division.
What the bricks share¶
Every algorithm above follows the same kernel contract:
- No new context fields: 2-D frames go through
ctx.image, the science spectrum throughctx.spectrum, second spectra (sky, response, standard) throughctx.extras[...]. - Literature references on every class (
references = […]). - No-op when zero: geometric corrections and the Gaussian denoiser pass through unchanged when their amplitude parameter is zero, so a single preset can run on instruments with and without those distortions.
- Provenance: every step writes a fingerprint of what it did into
ctx.image.meta(for 2-D) orctx.spectrum.meta(for 1-D), and the per-stepmetricsare JSON-serialisable for later auditing.
See Algorithm catalogue for the full parameter and reference list of each brick.