extract_spectrum_optimal¶
Optimal (Horne 1986) aperture extraction of ctx.image.
| Category | Extraction (2-D to 1-D) |
| Backend | scipy - implemented here on top of scipy primitives |
| Version | 2.0.0 |
| Reads | ctx.image (an ImageFrame) |
| Writes | spectrum, metrics.sky_median |
Faithful Horne 1986 recipe (Table I) : sky from ctx.extras / the flanking rows / none ; spatial profile from low-order polynomial fits along the dispersion of the normalised data (D − S)/f per detector row (eq. 14, weights f²/V, σ-clipped, P ≥ 0, unit sum per column) ; model variance V = (RON/gain)² + |f·P + S|/gain (eq. 13) ; optimal sum f = Σ M P (D − S)/V ÷ Σ M P²/V with variance 1/Σ M P²/V (eqs. 8–9) ; cosmic rays rejected one pixel per column per iteration (§II.E) ; the profile → extraction sequence runs twice, the second time normalised by the optimal spectrum and without the rejected pixels (§II.F). v2.0.0 (audit A4/A9) : v1 built the variance from the data (|D|/gain + RON²) and the profile from a 21-column running median of the raw cutouts, whose noise is correlated with the pixel noise — measured bias on a σ = 1.5 px Gaussian trace at gain 1, RON 3 e⁻, 65 536 columns : −23.1 % at 5 e⁻/column, −13.7 % at 20, −4.8 % at 100, −0.8 % at 1000 (v2 : −0.3 % ± 0.6 %, +0.6 %, +0.1 %, 0.0 %, the last three identical to the boxcar control on the same frames) ; v1 added RON² in e⁻² to a variance in ADU², so the reported σ was ×2.05 the empirical scatter at gain 2.5 / RON 6 e⁻, 100 e⁻/column (v2 : ×1.00, 0.995–1.03 over 20–1000 e⁻) ; v1 rejected every pixel above the threshold at once. The aperture is now the symmetric window round(y) ± trace_half_width (2·hw + 1 rows) instead of [int(y) − hw, int(y) + hw) — the old window sat 0.5–1.5 px below the trace, so the captured fraction rippled with the fractional trace position (0.947 at row 20.0, 0.860 at 20.9 for σ = 1.5 px, hw = 3 ; now 0.983, 0.976 at the worst half-pixel offset, 0.982 at 20.9). Profile normalisation : a column is normalised by its own flux when its S/N ≥ 10, else by the running median of the spectrum over profile_smooth_columns — Horne's per-column normaliser attenuates the profile at low S/N (errors-in-variables ; +17 % at 5 e⁻/column, profile peak 0.195 instead of 0.266), which biases the sum ; the running median keeps the estimator unbiased down to a few e⁻ per column. Single-pixel spikes along the dispersion are excluded from the profile fit only. The flanking sky fit is unweighted : Horne's 1/V weights (eq. 12) bias a Poisson mean by ≈ −1 count per pixel, i.e. +(2·hw + 1) ADU per column in the flux. Uncertainty : √(1/Σ M P²/V), sky taken as noise-free (Horne §II.A). Edge columns keep the aperture rows that fit the detector. Strongly tilted or curved traces (drift of several rows) need a higher profile_poly_order or a prior geometric rectification (correct_slant_affine / correct_smile_polynomial) — the profile of a detector row is fitted along the dispersion in fixed rows, as in the paper, not in trace-relative coordinates (Marsh 1989). When to choose this vs the lighter alternatives : extract_spectrum_sum is the simplest pure sum (no trace fit) ; extract_spectrum_boxcar is the edge-safe boxcar with optional Gaussian profile weighting but no variance model, no uncertainty propagation and no cosmic rejection — pick it for high-SNR sources or extended emission. Pick extract_spectrum_optimal when SNR matters most (faint, read-noise-limited targets), when residual cosmics may remain after clip_cosmic_rays, or when the downstream pipeline needs per-pixel Spectrum1D.uncertainty (e.g. radial-velocity error propagation). The pixel axis is 0-based (numpy.arange(npix)).
Parameters¶
| Parameter | Default | Required | Description |
|---|---|---|---|
trace_method |
'argmax' |
- | easyspec trace method ('argmax' / 'moments' / 'multi'). |
trace_poly_order |
2 |
- | Polynomial order of the trace fit. |
trace_y_pixel_range |
15 |
- | Half-window (rows) for the trace search. |
trace_peak_height |
100.0 |
- | Minimum peak height when locating the trace. |
trace_peak_distance |
50 |
- | Minimum separation between traces (multi mode). |
trace_half_width |
12 |
- | Aperture half-width (rows) for trace + extraction : the aperture is the symmetric window round(y(x)) ± trace_half_width, i.e. 2·trace_half_width + 1 rows. |
gain |
1.0 |
- | Detector gain in e⁻/ADU (variance model, Horne's Q). |
readnoise |
6.0 |
- | Detector read noise in e⁻ (variance model) ; converted to ADU² as V0 = (readnoise / gain)². |
sky_source |
'auto' |
- | Where the sky S comes from. 'extras' : the per-column 1-D Spectrum1D at ctx.extras[sky_extras_key] (ADU per pixel on the pixel axis, as written by extract_sky_lateral_bands ; fails if absent). 'flanking' : per-column weighted polynomial fit across the slit of the rows flanking the aperture (sky_offset, sky_half_width, sky_poly_order — the geometry of subtract_sky_2d). 'none' : S = 0, for frames already sky-subtracted. 'auto' (default) : 'none' when ctx.image.meta['sky_subtracted'] is set (subtract_sky_2d output), else 'extras' when the key is present, else 'flanking' when sky_half_width > 0, else 'none'. The resolved choice is recorded in ctx.spectrum.meta['sky_source']. |
sky_extras_key |
'sky_spectrum' |
- | ctx.extras key holding the 1-D sky spectrum ('extras' / 'auto'). |
sky_offset |
4 |
- | Rows between the aperture edge and the start of each flanking sky window ('flanking' / 'auto'). |
sky_half_width |
10 |
- | Height (rows) of each flanking sky window ('flanking' / 'auto') ; 0 disables the flanking estimate. |
sky_poly_order |
1 |
- | Order of the per-column polynomial fitted across the slit to the flanking sky rows (0 = weighted mean, 1 = linear gradient). |
profile_poly_order |
2 |
- | Order of the polynomial fitted along the dispersion to the normalised data of each detector row (Horne step 5). Low (0–2) for a straight trace ; raise it for a drifting trace. |
profile_smooth_columns |
401 |
- | Odd window (columns) of the running median of the spectrum used to normalise the profile points of columns whose S/N is below 10 (brighter columns use their own flux, Horne eq. 14). Wider ⇒ less attenuation of the profile at low S/N ; a window ≥ the number of columns uses the global median. |
reject_sigma |
5.0 |
- | σ threshold of the cosmic-ray rejection (one pixel per column per iteration, Horne uses 5) and of the σ-clipping in the sky and profile fits. |
iterations |
3 |
- | Maximum extract → reject → re-extract cycles per pass (one pixel per column per cycle) and maximum σ-clipping rounds of the profile fits. |
Use it¶
from spectro_kernel import run_algorithm
output = run_algorithm("extract_spectrum_optimal", ctx, {
"trace_method": "argmax",
"trace_poly_order": 2,
"trace_y_pixel_range": 15,
"trace_peak_height": 100.0,
"trace_peak_distance": 50,
"trace_half_width": 12,
"gain": 1.0,
"readnoise": 6.0,
"sky_source": "auto",
"sky_extras_key": "sky_spectrum",
"sky_offset": 4,
"sky_half_width": 10,
"sky_poly_order": 1,
"profile_poly_order": 2,
"profile_smooth_columns": 401,
"reject_sigma": 5.0,
"iterations": 3
})
The CLI loads a single 1-D spectrum with --input; this algorithm needs
ctx.image (an ImageFrame). Run it from Python or as a step of a pipeline preset.
{
"tool": "extract_spectrum_optimal",
"arguments": {
"session_id": "<session_id>",
"params": {
"trace_method": "argmax",
"trace_poly_order": 2,
"trace_y_pixel_range": 15,
"trace_peak_height": 100.0,
"trace_peak_distance": 50,
"trace_half_width": 12,
"gain": 1.0,
"readnoise": 6.0,
"sky_source": "auto",
"sky_extras_key": "sky_spectrum",
"sky_offset": 4,
"sky_half_width": 10,
"sky_poly_order": 1,
"profile_poly_order": 2,
"profile_smooth_columns": 401,
"reject_sigma": 5.0,
"iterations": 3
}
}
}
Every algorithm is an MCP tool of the same name; describe_algorithm returns
this page's metadata as JSON.
References¶
- Horne 1986, PASP 98, 609 — optimal extraction algorithm for CCD spectroscopy.
- Marsh 1989, PASP 101, 1032 — empirical profile for tilted traces.
- Tody 1986, SPIE 627, 733 — IRAF apall heritage.
Related algorithms¶
detect_trace- Detect the spectral trace onctx.imageand expose it onctx.extras["trace"].extract_sky_lateral_bands- Median-combine two off-trace sky bands into a 1D pixel-axis reference.extract_spectrum_boxcar- Trace (easyspec) + pure-numpy aperture extraction onctx.image.extract_spectrum_easyspec- Trace and extract a 1D spectrum from a 2D frame via easyspec.extract_spectrum_sum- Extract a 1D spectrum fromctx.imageby summing across the trace.