Narrowband Normalization

A direct port of Bill Blanshan's and Mike Cranfield's PixelMath narrowband normalization technique, with Bill's permission. Normalizes Ha / OIII / SII channels against each other so that classical palette mappings (SHO, HSO, HOS, HOO) land on balanced, controllable colour instead of whatever the raw channel intensities happen to produce.

Credits up front. The core normalization algorithm — the SHO / HSO / HOS / HOO scenario formulas, the linear and non-linear modes, the lightness-source mechanism, the highlight recover/reduction curves — is the original PixelMath work of Bill Blanshan (Another Astro Channel) and Mike Cranfield (cosmicphotons.com). This tool reproduces that algorithm in SASpro with their permission. What SASpro adds — linear fit integration, SCNR as a post-step, background neutralization, OSC synthesis, mapped-RGB view import, the async preview worker and the UI around it — is detailed in § 12.

1. What it does

Classic narrowband palettes (SHO, HOO, …) are a direct R / G / B channel-assignment: “SII in red, Ha in green, OIII in blue”, say. The visual result depends heavily on the relative integrated signal of each channel — a weak OIII makes everything look magenta; a dominant Ha drowns out subtle SII shock structure. Normalization reshapes each channel against a shared statistical baseline (minimum, median, highlight distribution) before the palette assignment, so you can control the final hue balance with a handful of parameters instead of fighting it in Curves afterward.

2. The three ways to load data

2.1 Direct mono channels

Load Ha / OIII / SII separately from the Narrowband channels group. Each button offers From View (any open SASpro document) or From File (PNG, TIF, FITS, XISF). The label underneath turns green with the source name when loaded (long paths and view names are elided and shown in full on hover). For HOO scenarios the SII slot is hidden; for all other scenarios all three are required.

2.2 OSC extractions

From the OSC extractions group:

If a direct mono channel is loaded alongside the OSC, the mono takes priority; OSC only fills in channels that would otherwise be missing. This differs from Perfect Palette Picker, which averages them.

2.3 Import a mapped RGB view

The Import mapped RGB view group at the top of column A has four buttons: Load SHO View, Load HSO View, Load HOS View, Load HOO View. Pick one, choose any currently open RGB document, and SASpro inverse-splits it back into Ha / OIII / SII channels according to the mapping you selected:

This is the fastest way to re-normalize an existing palette image from Perfect Palette Picker. The source document is tracked so that Apply to Current View writes back to the original mapped composite, not the temp split channels.

Dimension mismatch is detected automatically. If the loaded channels aren't the same pixel size, you're prompted once per session to resize everything to a reference frame (preferred: Ha, then OIII, then SII) using OpenCV's INTER_AREA / INTER_LINEAR.

3. Scenario

Four palette targets, selected from the Scenario combo. The normalization math changes per scenario because each one needs different relationships between the channels to look balanced:

ScenarioRGBNeeds
SHOSIIHaOIIIHa + OIII + SII
HSOHaSIIOIIIHa + OIII + SII
HOSHaOIIISIIHa + OIII + SII
HOOHaOIIIOIIIHa + OIII only

Switching scenario reshuffles which rows of the Normalization form are visible: HOO shows the Blend Mode and Ha Blend rows and only the first OIII Boost; the SHO family hides those and shows SII Boost plus its own OIII Boost.

4. Mode

Two modes, driven by the state of your input data:

Picking the wrong mode doesn't produce an error, just a less-good result. If you're not sure, Non-linear is the safer default for anything you'd open in a stretched state.

5. Lightness source (non-linear only)

Controls which signal provides the final luminance of the normalized composite:

6. The core knobs

6.1 Blackpoint (Min → Med)

Range 0.0–1.0, default 0.25. Controls the blackpoint reference the normalization measures against:

M \;=\; \min(C) \;+\; \mathrm{BP} \cdot (\mathrm{median}(C) - \min(C))

At 0.0 the reference is the channel minimum; at 1.0 it's the channel median. Higher values lift the baseline (brighter background); lower values preserve darker blacks. The 0.25 default sits closer to minimum but still above pure black, which handles the usual case of calibrated channels with faint background sky.

6.2 HL Recover and HL Reduction

Both range 0.5–2.0, default 1.0 (identity). These shape the upper end of each channel independently:

Minimum enforced value is 0.25 inside the normalization routine so you can't accidentally zero out highlight response.

6.3 Brightness

Range 0.5–2.0, default 1.0. Overall multiplicative gain applied after the other shaping. Also floored at 0.25 inside the routine.

7. HOO-only controls

7.1 Blend Mode

How the Ha and OIII signals combine in the green and blue channels. Options: Screen, Add, Linear Dodge, Normal. Screen is a soft-light-ish layer with less clipping; Add and Linear Dodge produce brighter, warmer results; Normal is a direct blend without any additive behaviour.

7.2 Ha Blend

Range 0.0–1.0, default 0.6. How much Ha contributes to the green and blue channels in the bicolour composite. Lower values keep HOO looking distinctly cyan-blue; higher values warm it toward Ha-dominant.

7.3 OIII Boost (HOO)

Range 0.5–2.0, default 1.0. Independent gain on the OIII contribution before blending. Pair with Ha Blend to tune the Ha / OIII ratio without touching the input channel data.

8. SHO-family controls

8.1 SII Boost

Range 0.5–2.0, default 1.0. Boosts (or dims) the SII contribution. Especially useful in SHO where the red channel is driven entirely by SII — a weak SII integration gives a magenta-looking image until you lift it with this.

8.2 OIII Boost (SHO family)

Range 0.5–2.0, default 1.0. Independent OIII gain for the SHO-family scenarios.

9. SCNR, Linear Fit, Background Neutralization

9.1 SCNR

The SCNR (reduce green cast) checkbox applies subtractive chromatic noise reduction as a post-step after the main normalization. SASpro uses the same SCNR core as the Remove Green tool. Two sub-controls:

SCNR is hidden for the HOO scenario because HOO's green channel is deliberately OIII-driven and shouldn't be clamped.

9.2 Linear Fit (highest signal)

The Linear Fit (highest signal) checkbox median-matches each channel to the one with the strongest signal before normalization. This evens out the input scales so the normalization math has a predictable starting point — particularly useful when one channel is noticeably weaker than the others. The reference is picked automatically from the loaded channels' medians (highest wins). See § 12.3.

9.3 Background Neutralization

The Background Neutralization (∇-descent) checkbox applies a gradient-descent background colour balance as a post-step on the normalized RGB. Uses an auto-detected 50×50 reference rectangle. See § 12.4.

9.4 Autostretch preview

Toggle for showing the preview with a statistical stretch applied (target median 0.25, linked channels). The underlying result is not stretched; only the display is. Use this if your input channels are linear and you want to see what the normalization is doing without the preview looking black.

10. Preview, Apply, Push

While a job runs, the action buttons are disabled and the status line shows a percent + stage name from the worker.

11. Preview interaction

12. Technical details

Channels normalised to float32 in [0, 1]. Compute flows through a background QThread with sequence gating so stale preview workers can't overwrite newer results. The core normalization engine is Bill Blanshan's PixelMath formulas in Python (implemented in imageops.narrowband_normalization); everything else below is the SASpro wrapper around it.

12.1 Pipeline order

  1. Load mono channels (or synthesize from OSC, or inverse-split a mapped RGB view).
  2. Resize to reference if dimensions differ (user-approved, once per session).
  3. For preview only: downsample to the preview resolution (see § 12.6).
  4. If Linear Fit is enabled, median-match each channel to the highest-signal reference (§ 12.3).
  5. Run normalize_narrowband(ha, oiii, sii, params) — the Blanshan/Cranfield core.
  6. If SCNR is enabled, run _apply_scnr_rgb with the user's amount and preserve-luminance setting (§ 12.2).
  7. If Background Neutralization is enabled, run the auto-rect ∇-descent neutralization (§ 12.4).
  8. For preview display only: apply statistical stretch at target median 0.25 if Autostretch is on. The underlying buffer is left in its unstretched state so Apply and Push write the raw normalized result.

12.2 SCNR formula

SCNR green suppression computes a per-pixel green-cap and blends with the amount slider:

G_{\mathrm{avg}} \;=\; \min\!\left(G,\; \frac{R + B}{2}\right)
G' \;=\; (1 - a)\,G \;+\; a \cdot G_{\mathrm{avg}}

where a is the SCNR Amount. When Preserve luminance is enabled, the Rec. 709 luminance is computed before and after:

L \;=\; 0.2126\,R + 0.7152\,G + 0.0722\,B

and the output RGB is rescaled so that L is preserved, preventing the overall image from darkening when green is pulled down. The relevant line in the SCNR core is _apply_scnr_rgb(rgb, amount, mode="avg", preserve_lightness=True, channel="G").

12.3 Linear Fit integration

When enabled, SASpro picks the channel with the highest median value as the reference and median-matches the others to it via linear_fit_mono_to_ref from the Linear Fit module:

C' \;=\; C \cdot \frac{\mathrm{median}(C_{\mathrm{ref}})}{\mathrm{median}(C)}

SASpro passes rescale_mode_idx=2 (“leave as is”) so the fit does not clip or renormalize — it just median-matches. The normalization routine downstream decides what to do with the resulting dynamic range.

12.4 Background Neutralization

Uses the same auto-rect strategy as the standalone BG Neutralization tool: a 50×50 pixel region is picked automatically from a dark part of the frame, and background_neutralize_rgb runs a gradient-descent fit that balances the per-channel offset so the measured background hits neutral grey. The result respects the destination document's active mask when running inside Apply.

12.5 OSC synthesis

If an OSC frame is loaded and the corresponding mono channel is not:

\mathrm{Ha}_{\mathrm{OSC1}} \;=\; \mathrm{OSC1}[R],\quad \mathrm{OIII}_{\mathrm{OSC1}} \;=\; \frac{\mathrm{OSC1}[G] + \mathrm{OSC1}[B]}{2}

and symmetrically for OSC2 (SII / OIII). Unlike Perfect Palette Picker, which blends 50/50 when both OSC and mono are present, NB Normalization gives priority to the mono channel — OSC is used only to fill in missing data. This is the stricter behaviour appropriate for a normalization step: mixing scales from different source types would defeat the point of normalizing.

12.6 Preview downsample strategy

The preview path targets < 2 megapixels and a maximum dimension of 1800 px. The computed scale is quantised to one of five buckets — 1.00, 0.75, 0.50, 0.33, 0.25 — so a small zoom in or crop doesn't change the preview path across runs. Downsampling uses OpenCV's INTER_AREA. The chosen scale is reported in the status bar during preview compute.

12.7 Async worker and sequence gating

Preview and the full-resolution Apply/Push both run through a _NBNWorker QThread. Each job increments a _calc_seq counter; when a worker finishes, its result is accepted only if the sequence still matches the current one. Older jobs that finish after a newer one has started are silently dropped. Progress is reported through a progress(int, str) signal and updates the status label as “<stage> (<pct>%)”.

12.8 Mapped RGB view import math

For the three three-channel scenarios the inverse-split is direct: SHO is (SII, Ha, OIII) = (R, G, B), HSO is (Ha, SII, OIII) = (R, G, B), HOS is (Ha, OIII, SII) = (R, G, B). For HOO the OIII channel is reconstructed as the average of G and B since the bicolour mapping doesn't preserve them separately:

\mathrm{OIII} \;=\; \frac{G + B}{2}

and SII is left as None (HOO doesn't use it). This isn't a mathematically exact inversion when the forward mapping included boosts or lightness overrides — it's the right starting point for re-normalizing an already-assembled composite, not for round-trip exact reconstruction.

Credits. The narrowband normalization algorithm — the SHO/HSO/HOS/HOO scenario math, linear and non-linear modes, lightness-source selection, highlight recover/reduction curves, blackpoint reference, Ha blend, OIII and SII boosts, and the PixelMath blend modes (Screen, Add, Linear Dodge, Normal) — is the original work of Bill Blanshan (YouTube) and Mike Cranfield (cosmicphotons.com). This SASpro port is published with Bill's permission; the core math is not Frank's and credit belongs to its originators.

The SASpro wrapper is Frank's work: integration with SASpro's Linear Fit engine, SCNR as a post-step via SASpro's remove_green core, auto-rect Background Neutralization via SASpro's backgroundneutral, OSC synthesis, mapped RGB view inverse-split import for Perfect Palette Picker interop, the async _NBNWorker + sequence-gating preview, the scaling-bucket downsample strategy, dimension-mismatch handling, and the dynamic form that shows/hides HOO vs SHO-family controls. Image loading uses SASpro's legacy.image_manager.load_image; display stretch uses SASpro's stretch_color_image.