NB→RGB Stars

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.

1. Workflow

This tool only makes sense inside a specific pipeline:

1. Image narrowband (Ha, OIII, and optionally SII, or a dual-band OSC)
2. Combine your palette (SHO, Foraxx, Dynamic Inverse, HOO…) using Perfect Palette Picker
3. Run Remove Stars on the palette image — keep both the starless result and the star-only image
4. Also run Remove Stars on each raw NB channel; keep the star-only versions of Ha, OIII, (and SII if you have it)
5. Open NB→RGB Stars — load those star-only frames and build a synthetic RGB star image
6. Composite with Add Stars: starless palette + RGB-looking stars from this tool → final image

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.

2. Loading inputs

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.

3. The Ha:OIII ratio slider

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.

4. Star stretch

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.

5. Saturation

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.

6. Preview Combine

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.

7. Reset Adjustments vs Clear Inputs

8. Push Final to New View

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.

9. Preview interaction

10. Persistence

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.

11. Technical details

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.

11.1 Why synthesis works at all

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.

11.2 Combine formulas

With r ≡ the Ha:OIII ratio slider value.

NB-only mode (Ha + OIII required; SII optional):

R \;=\; \frac{\mathrm{Ha} + \mathrm{SII}}{2}
G \;=\; r \cdot \mathrm{Ha} \;+\; (1 - r) \cdot \mathrm{OIII}
B \;=\; \mathrm{OIII}

If SII is not loaded, the red formula collapses to R = Ha.

OSC-driven mode (OSC loaded, with optional mono blending):

R \;=\; 0.5 \cdot \mathrm{OSC}[R] \;+\; 0.5 \cdot \mathrm{SII}
G \;=\; r \cdot \mathrm{Ha} \;+\; (1 - r) \cdot \mathrm{OSC}[G]
B \;=\; \mathrm{OIII}

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.

11.3 Star stretch (midtone transfer function)

When Star Stretch is enabled, each RGB plane is passed through the classical bounded rational MTF:

s(x) \;=\; \frac{t \cdot x}{(t - 1)\,x \;+\; 1}, \quad\mathrm{where}\quad t = 3^{k}

and k is the Stretch factor slider value in [0, 8]. Properties:

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).

11.4 SCNR (green noise removal)

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:

G' \;=\; \min\!\left(G,\; \frac{R + B}{2}\right)

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.

11.5 Saturation

adjust_saturation_numba implements the standard luminance-preserving saturation scaling:

\mathrm{RGB}' \;=\; L + s \cdot (\mathrm{RGB} - L)

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].

11.6 Pipeline order

The ordering matters for the final look:

  1. Combine Ha / OIII / SII / OSC into R, G, B per the formulas in § 11.2.
  2. Clip to [0, 1].
  3. If Star Stretch is enabled, apply the MTF of § 11.3 to each plane independently.
  4. Clip again.
  5. Apply SCNR (§ 11.4).
  6. Apply saturation (§ 11.5) if the slider isn't at 1.00×.
  7. Final clip, cast back to float32.

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.

11.7 Threading and spinner

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.

Credits. The specific NB→RGB combine formulas and the pipeline ordering (combine → star-stretch → SCNR → saturation) are Frank's recipe for SASpro; they're tuned on imaging data from Louisiana skies where the clear-night budget makes synthesising stars from narrowband data the pragmatic choice. SCNR (Subtractive Chromatic Noise Reduction) is the standard astroimaging convention for suppressing green bias; the specific 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.