Aurora line monitor¶
Aurorae are noisy on every time scale. detect_lines with the bundled
aurorae catalogue + plot_dynamic_spectrum turn a string of short
exposures into a single picture of the night. Useful for aurorex-style
near-real-time pipelines.
This walk-through synthesises five aurora-like spectra, runs the catalogue chain on them, then asks for the time-series visualisation.
1. Synthesise five minutes of green-line variability¶
The [O I] 5577 Å line is the bright auroral one. Throw in N₂⁺ 4278 and 3914 as secondary features, scale the green-line amplitude over time, sprinkle noise.
import numpy as np
from spectro_kernel.types import Spectrum1D, WorkContext
def _gauss(wave, centre, amp, fwhm):
sigma = fwhm / 2.3548
return amp * np.exp(-0.5 * ((wave - centre) / sigma) ** 2)
wave = np.linspace(3800, 6000, 4000)
green_strength = [0.6, 1.0, 1.4, 1.1, 0.5] # five short exposures
spectra = []
for i, scale in enumerate(green_strength):
flux = (
50.0
+ _gauss(wave, 5577.34, amp=400.0 * scale, fwhm=1.2) # [O I] green
+ _gauss(wave, 4278.10, amp=180.0, fwhm=2.0) # N2+ 1NG
+ _gauss(wave, 3914.40, amp=140.0, fwhm=1.8) # N2+ 1NG
+ np.random.default_rng(10 + i).normal(0.0, 4.0, wave.size)
)
spectra.append(
Spectrum1D(wave, flux, meta={"date_obs": f"2026-01-12T22:0{i}:00"})
)
2. Identify auroral lines on every frame¶
from spectro_kernel import run_algorithm
per_epoch = []
for i, sp in enumerate(spectra):
ctx_i = WorkContext(spectrum=sp)
run_algorithm("detect_lines", ctx_i, {"catalog": "aurorae", "prominence_sigma": 5.0})
matched = [d["matched_line"] for d in ctx_i.extras["detected_lines"] if d["matched_line"]]
per_epoch.append(matched)
print(f"frame {i + 1}:", matched)
Every frame should pick up [O I] green plus the two N₂⁺ bands (and possibly a
spurious match - that's why this is a monitor, not an absolute identifier).
3. Variability metric on the green line¶
For each epoch, fit a Gaussian to 5577 Å and grab the amplitude; that's the proxy you'd plot on the dashboard's left rail.
amplitudes = []
for sp in spectra:
ctx_i = WorkContext(spectrum=sp)
run_algorithm(
"fit_gaussian_line",
ctx_i,
{"line_center_angstrom": 5577.34, "window_angstrom": 8.0, "label": "OI"},
)
amplitudes.append(ctx_i.line_fits["OI"].amplitude)
print("5577 Å amplitudes:", [round(a, 1) for a in amplitudes])
4. The dashboard view¶
Drop every epoch into a single WorkContext.spectra and let the
time_series_overview preset render the three complementary views (dynamic
heatmap + 3D surface + animation) in one call.
from spectro_kernel import PipelineBuilder
stack_ctx = WorkContext(spectra=spectra)
result = PipelineBuilder().from_preset("time_series_overview").build().execute(stack_ctx)
# ctx.figures now holds 3 ready-to-render Plotly JSON dicts:
for name in stack_ctx.figures:
print(name, "→", type(stack_ctx.figures[name]).__name__)
For a real aurora pipeline the steps above run continuously: each new exposure
appends to ctx.spectra, you re-run stack_spectra + plot_dynamic_spectrum
on a 10-second cadence, and the dashboard refreshes its single PNG. That is
exactly what a thin wrapper around spectro-kernel buys you over hand-coding it.
Going further¶
- Pair with
compare_normalisationsonce you have a brighter exposure: the N₂⁺ continuum is steep and the "best" normalisation changes between bands. - Drop the green-line
amplitudeslist into a light curve and runlomb_scargleto look for sub-hour periodicity (geomagnetic substorms). - Switch the catalogue to
"nebular"and you have a faint-target dashboard that uses the same algorithms.