Synthesize believable broadband-looking star colours from narrowband data (Ha / OIII / SII mono, dual-band OSC, or any combination). Dedicated for the case where you want to spend every minute of a clear night collecting narrowband signal and nothing on separate RGB star frames, and still end up with a composite whose stars don't look like coloured dots.
The use case is specific. If you image under a mediocre sky (bortle 7, hazy, half the year humid), the clear nights that produce real signal are too valuable to spend on broadband RGB star frames. NB→RGB Stars extracts an approximate but visually credible star colour from the narrowband data you already have, by exploiting the fact that star continua spill into every narrowband filter at different relative weights depending on their spectral type.
This tool only makes sense inside a specific pipeline:
The point of step 5 is to decouple hue from the palette: your starless nebula can be as SHO-weird as you like, and your stars will still read as roughly natural-coloured.
Four load slots on the left column, each with a From View or From File picker:
Minimum: either an OSC stars frame, or Ha + OIII mono. When both are provided, OSC drives the base R / G / B and the mono channels are blended in. Each loaded source shows a green confirmation line underneath its load button.
Make these star-only images. Feed this tool images produced by Remove Stars on your raw channels (not the full nebular channels). The combine math is tuned for star signal, which is relatively broadband-leaky; nebular signal is nearly pure emission-line and will give odd colours if treated the same way.
Range 0.00 to 1.00 (default 0.30). This controls how much Ha vs. OIII contributes to the green channel of the synthesized stars. The red channel is always a 50 / 50 Ha / SII blend, and the blue channel is always OIII; what the slider governs is the warm-to-cool tilt of the midtone response.
The Enable star stretch checkbox toggles a bounded non-linear boost (midtone transfer function). The Stretch factor slider (0.00 to 8.00, default 5.00) controls the strength.
Because the curve is monotonic and bounded in [0, 1],
it never clips or inverts; it just reshapes the midtones. See
§ 11.3.
The Saturation slider (0.00× to 3.00×, default 1.00×) applies a chromatic scaling against the luminance axis after the combine and SCNR passes. At 1.00× it's a no-op; at 0.00× stars become grayscale; at 2.0×–3.0× colours get vivid for showing off hue variety across spectral types. Push this harder than you would for broadband data — synthetic NB stars are naturally a little muted.
Preview Combine runs the full pipeline (combine → star stretch → SCNR → saturation) and renders the result in the right-side preview. A spinner shows while work runs; action buttons are disabled during the pass to prevent re-entry on big images. The pipeline is lightweight — all four stages are NumPy or Numba, so a 32-megapixel star field previews in seconds, not minutes.
Opens the combined result as a new SASpro document titled “NB→RGB Stars”. From there, feed it into Add Stars alongside your starless palette image to produce the final composite. The pushed image is the exact pixels shown in the preview — adjust the sliders until the preview looks right, then push.
Saved to QSettings under nbtorgb_stars/:
Loaded images are not persisted; reopening the tool starts with empty slots so you can't accidentally combine stale data.
All inputs normalised to float32 in [0, 1].
Sizes must match across channels — if they differ the combine
raises an error, which is why you should feed in channels already
registered and star-extracted from the same project. The combine
runs in NumPy; SCNR and saturation use Numba-compiled kernels from
SASpro's legacy.numba_utils.
Narrowband filters are not infinitely narrow. A typical 3 nm Ha filter still passes a few nanometres of continuum, and stellar spectra (unlike nebular emission) are dominated by continuum. So a star's measured brightness through each NB filter is roughly proportional to its spectral energy distribution integrated over that filter's passband, which carries real colour information: hot blue O/B stars have more OIII-band continuum, cool red K/M stars have more Ha-band continuum, and the ratio differs across the Hertzsprung–Russell diagram. The combine formulas below exploit that: they build an R / G / B triplet whose relative weights mirror what a Johnson B / V / R photometric system would produce, within the limits of the filter bandwidths.
With r ≡ the Ha:OIII ratio slider value.
NB-only mode (Ha + OIII required; SII optional):
If SII is not loaded, the red formula collapses to
R = Ha.
OSC-driven mode (OSC loaded, with optional mono blending):
In OSC mode the fallbacks are: if SII is missing,
SII := OSC[R]; if Ha is missing,
Ha := OSC[R]; if OIII is missing,
OIII := OSC[B]. If a mono OSC frame was
loaded (single-channel), it is treated as if the three channels all
hold the same mono data; the combine still runs but the result is
effectively monochromatic until another channel is added.
When Star Stretch is enabled, each RGB plane is passed through the classical bounded rational MTF:
and k is the Stretch factor slider value in [0, 8].
Properties:
s(0) = 0, s(1) = 1 —
bounded, no clipping ever.k = 0, t = 1 and
s(x) = x (identity).k grows, the curve pulls dim pixels upward
faster than bright pixels — so dim stars brighten
proportionally more than already-bright ones, which is exactly
what you want for star fields.The exponential base of 3 is chosen so that k = 5
(the default) gives a visibly strong but not blown-out boost on
typical narrowband star data (t = 243, which
maps x = 0.01 to roughly 0.71).
After combine (and star stretch if enabled), SCNR is applied via
applySCNR_numba. The standard SCNR formulation suppresses
excess green by clamping the green channel to the average of red
and blue:
This is important for NB→RGB synthesis because the green channel is a weighted average of Ha and OIII — both strong emission lines — so without SCNR synthetic stars often come out with an unnatural green cast. SCNR removes it without touching the luminance of real colour information in R or B.
adjust_saturation_numba implements the standard
luminance-preserving saturation scaling:
where s is the slider factor and L is
the per-pixel luminance (Rec. 709 weights
0.2126 R + 0.7152 G + 0.0722 B).
Values below 1 desaturate toward grey; values above 1 push colour
away from the luminance axis, bounded by the final clip to
[0, 1].
The ordering matters for the final look:
Star stretch happens before SCNR so that any green cast amplified by the stretch is then removed; and saturation happens last so it operates on the final luminance of the composite, not the raw combine.
The combine runs on the GUI thread but is fast enough that it doesn't need background threading for typical star fields. A spinner (resource-loaded QMovie) is shown and action buttons are disabled during the pass so you can't fire two combines on top of each other and get a corrupted preview. If future performance work moves the pipeline into a QThread, the spinner infrastructure is already there to support it.
min(G, (R+B)/2) variant is implemented in SASpro's
applySCNR_numba. The MTF rational stretch is the
classical bounded tone curve used across many astroimaging packages;
the t = 3k parameterisation is
SASpro's choice. Saturation uses the Rec. 709 luminance basis and is
implemented in adjust_saturation_numba. Image loading
uses SASpro's legacy.image_manager.load_image.
OSC filter examples (Optolong L-eXtreme / L-eNhance, IDAS NBZ,
Antlia ALP-T) are third-party products referenced for context.