Physics-grounded colour calibration. SASpro detects stars in the
plate-solved frame, matches each one to a Gaia DR3 XP calibrated spectrum
(or a Pickles library template as fallback), integrates its spectrum through the
telescope's filter/sensor/light-pollution curves to predict the expected
R/G, B/G ratios, measures them in the image, fits a correction
model, and applies it.
The dialog is a two-step workflow with a shared panel of filter/sensor selections between them. Both steps run against the active image window; switching the active window between runs is fine.
The White Reference dropdown picks the SED that defines “neutral
grey” (default G2V; Vega / A0V is the usual alternative). It
only affects the reference integrals S_ref_c, nothing else at this
stage.
Click Step 1: Fetch Stars & Spectra from Current View and the pipeline runs:
(0.2, 10]
px.RADECSYS and EPOCH
keywords are normalized first).B−V (§2.3).gaiaxpy.generate(system=JKC), caches it, and infers the
letter class from the synthetic B−V.ids
lookup in batches of 200 to resolve a Gaia DR3 source id.The result is a summary plot in two panels. The status line below shows the raw counts; the plot shows the quality of what you got.
B−V inference (SIMBAD or Gaia
synthetic) because no spectral type was published. A healthy
astrophotography field is bell-shaped around F / G / K with tails to A
and M; a histogram that's all one letter usually means the SIMBAD query
failed or the field is pathologically narrow (star cluster, galactic
plane crowding). Blue dominating is good; a lot of orange means SASpro
had to fall back to colour-only typing for most of the field.Pick the actual optical chain: R Filter, G Filter,
B Filter, Sensor (QE) and up to two LP/Cut Filter curves
(e.g. an IR-cut and a light-pollution filter). (None) is a
flat-unity curve. These are read from the shipped
sasp_data.fits plus a per-user usercustomcurves.fits.
Choices persist via QSettings.
λ_nm, response) or digitize a scanned curve from an
image (click bottom-left, bottom-right, top-left on the plot axes, enter
λ_min and λ_max, SASpro extracts
the darkest-pixel-per-column trace, interpolates anomalies, median-smooths,
and writes it to the user FITS as a FILTER or
SENSOR extension).usercustomcurves.fits without the chosen extension.Click Open SASP Viewer (next to the Step 1 button) to launch a standalone window that plots any stellar SED through any R/G/B filter set, any sensor QE, and up to two LP/cut filters. It doesn't touch the image — it's a free-form exploration of what your optical chain actually sees, and it's where you get a feel for why one filter choice gives you deeper red stars than another, or why that bargain CMOS sensor can't tell a late K from an M.
The dropdowns across the top are the same vocabulary as the main dialog: Star Template (any Pickles SED from O supergiants through late M dwarfs), R / G / B Filter, two optional LP/Cut Filter slots, and Sensor (QE). Change any dropdown and press Plot to redraw.
One figure, two y-axes:
T_filter × QE × LP₁ × LP₂
alone, independent of the star. These are the instrument; the gold
curves are what the instrument does to the star. If the dashed curves
look clean and symmetric but the gold curves are chopped off at one
side, the sensor's QE is choking that end of the band.R→SII: 3.42 | G→OIII: 4.11).
These are system magnitudes you can sanity-check against a known
calibrated star; a large asymmetry (one channel much fainter than the
others for a G2V) is a clue that your filter balance is off.M5V, leave an
ordinary R+G+B+IR-cut chain selected: the gold B response is a flat
line against the floor while R has a tall peak. That's why red giants
look blue-starved before any colour correction.With Step 1 done and the chain picked, click Step 2: Run Color Calibration. SASpro re-detects stars on the live frame, matches them within 3 px to the Step-1 star list, caps the sample to the brightest 500 by SEP flux, performs annulus-background-subtracted aperture photometry per channel, computes the model (§2.4) and applies it (§2.5).
x = 1.Two side-by-side panels, both plotting the fractional residual
(measured_ratio / expected_ratio) − 1 on the y-axis against
the expected ratio on the x-axis. Red circles are the R/G
channel, blue squares are B/G. The shaded bands behind each colour
are the Q1–Q3 interquartile range — the central half of the
scatter.
A separate Run Gradient Extraction button and method combo
(poly2, poly3, rbf) are hidden by
default but available on code paths that enable them. It uses the same
spectra and photometry to measure a per-channel spectrophotometric
gradient and remove it — see §2.7.
The result pane shows three side-by-side panels, one per channel, each a
divergent red/blue heatmap of the fitted gradient surface in
magnitudes. Panel titles report the peak magnitude for that channel so
you can tell at a glance how aggressive the correction was — a 0.03 mag
peak is a cosmetic tweak, a 0.3 mag peak was clamped (§2.7) because the fit
tried to go further. Yellow dots overlay the star positions used for
that channel's fit after sigma-clipping; sparse corners mean the surface is
extrapolating there and should be treated with caution. The three channels
usually show similar shape (same corner darker, same edge brighter)
with different amplitudes — that's a real optical gradient. If the
three channels show unrelated patterns, the fit is chasing noise and
poly2 is probably the right method to retry with.
RGB only; no narrowband. The core idea: use each star's real spectrum as a photometric standard. SIMBAD + Pickles can approximate the spectrum from a type label; Gaia XP gives you the actual observed spectrum of that star. SFCC uses XP where it has it and falls back to a hierarchical Pickles match where it doesn't.
gaiaxpy, the
Pickles 1998 stellar flux library, SIMBAD (CDS Strasbourg), and
SEP (Source Extractor as a Python library). The calibration pipeline,
the three-model fit, the pivot-at-median application, and all SASpro-specific
heuristics below are implemented in SASpro.SEP runs on the mean of the three channels. Rather than fixing a detection threshold:
If no rung reaches 1200 the lowest σ is kept so there's still something
to work with. Detections are filtered by flux-radius to (0.2, 10] px
to drop galaxies/plate flaws. Only sep.extract is retried per rung
— the one-off background estimation runs once.
The optical chain combines multiplicatively on a shared 3000–11000 Å 1-Å grid:
For each star, the expected integrated flux through channel c
is
where f_star(λ) is either the Gaia XP calibrated
spectrum (interpolated onto the grid) or the matched Pickles SED. Integrals are
cached per (source_id, system_key) in an SQLite store so repeat
runs are instant — system_key is an MD5 over the wavelength
grid and the three throughputs so any filter change triggers a recompute.
Reference integrals S_ref_c are the same formula applied to the
chosen white reference (default G2V).
SIMBAD spectral type (e.g. K2III) is matched against the
available Pickles extensions by letter class, subclass, and luminosity —
exact match first, then same letter + same luminosity + nearest
subclass, then same letter + any luminosity + nearest subclass, finally all
templates in that letter. When SIMBAD gives no type but SIMBAD or Gaia-synthetic
Johnson magnitudes are available, the letter is inferred from B−V:
and the match runs again on the letter alone (so a B−V-only star picks,
say, the main-sequence K5V rather than K5III).
Step-1 catalogue stars are re-matched to Step-2 SEP detections within 3 px
and capped to the brightest 500. For each kept star the aperture radii scale
with the SEP semi-major axis a:
Each channel's background-subtracted flux comes from an annulus-estimated pedestal:
Photometry runs in parallel across up to 8 threads; stars with any non-positive channel are dropped. The result is the two measured ratios per star compared against the predicted ratios from §2.2:
SASpro then fits three models independently for the R and B channels and keeps the best per channel. The slope-only solution is closed-form:
and affine / quadratic are standard least-squares and polynomial fits:
Each is scored by the fractional-residual RMS:
and the winner minimises the sum over both channels:
A fractional loss is used, not absolute, because stars span orders of magnitude in flux and residuals should be measured relative to the expected value. Quadratic is only entered when there are ≥ 6 valid stars.
With the winning per-channel polynomials, the per-pixel correction factors
are the polynomials evaluated at the measured pixel ratio
(x_R = I_R/I_G, x_B = I_B/I_G) and clipped to a safe
dynamic range:
And applied with a pivot at the channel median rather than at zero, so the overall channel brightness is preserved and only the colour balance moves:
Green is the reference channel and is left unchanged. The clip at
[0.25, 4] prevents extreme ratios on bright or dark pixels from driving
runaway gains (e.g. a saturated red core where G→0).
In ratio space, with G normalised to 1, each star gives a measurement vector and a target vector:
SASpro solves this by least-squares row-by-row (per output channel), then
forces the G row to [0, 1, 0] so green maps to itself
exactly and the matrix stays near-identity. 3σ-MAD clipping on residuals
removes outliers, and the solution is rejected if any of:
κ(M) > 50,|M_ii| > 3 for any diagonal entry, or|M_ij| > 1.5 for any off-diagonal entry.Applied per pixel in ratio space with G held fixed, then a per-pixel
negative fix (shift up by min(channels)) and a
luminance-pivoted ceiling compression keep the output in gamut without
hard-clipping any one channel.
Removes a spatially-varying colour gradient per channel using the same star catalogue. For each star and channel the differential magnitude is computed (no annulus — raw aperture only, since the gradient is the pedestal we're after), then field-median-subtracted so only the spatial variation remains:
A 2.5σ-MAD clip removes outliers, then a 2D surface
(poly2, poly3, or RBF with a thin-plate-spline
kernel) is fit per channel, re-centred, and clamped to ±0.3
mag so a bad fit can't blow up the image. The correction is multiplicative in
flux:
At least 10 stars are required post-clipping, and at least 5 per channel to proceed with the fit.
The current pipeline trusts the manufacturer's published QE curve. That
curve is a room-temperature bare-die measurement; a real system's response is
the product of sensor QE, cover glass, AR coatings, telescope
throughput, atmosphere and airmass. With Gaia DR4's ~10× denser XP
coverage, the plan is to drop the QE combobox entirely and solve the
effective system response R(λ) directly from the
stellar population — the same approach Gaia uses internally for its own
photometric calibration (Carrasco et al. 2021, Riello et al. 2021). Filters
stay as inputs; QE becomes a solved output, with the matrix of star / channel
observations providing the overconstrained system.