Perfect Palette Picker

A gallery-grid palette browser for narrowband data. Load your Ha / OIII / SII (or OSC dual-band equivalents), click Create Palettes, and 18 standard and specialty palette mappings render side-by-side. Click any thumbnail to blow it up in the main preview, nudge the per-channel intensities to taste, and push the final to a new document.

PPP was built around a specific frustration: most tools make you pick a palette first, then look at the result, then pick a different one if you don't like it — a workflow that's bad at helping you discover the surprising combination that fits your data. Showing every palette at once collapses that into one glance.

Dynamic Inverse is Frank's own palette and appears only here. The other 17 palettes are the industry-standard permutations plus the published Foraxx algorithm (see § 13.4). See the credit block at the bottom.

1. The layout

Two windows:

Keeping the gallery in its own window lets you position it on a second monitor or beside the main preview without one eating the other's space.

2. Loading channels

Five load slots on the left:

Each load dialog asks whether to pull From View (any currently open SASpro document) or From File (PNG, TIF, FITS, XISF). The green status line under each slot confirms the loaded source. Minimum requirement is any two of the three narrowband channels, or one OSC image.

2.1 Linear input toggle

The Linear input (apply statistical stretch before build) checkbox at the top of the controls governs whether PPP stretches each loaded channel before combining them. For stretched data turn this off. For linear sub-stacks straight out of integration leave it on (the default): PPP runs its usual statistical stretch at target median 0.25 on each channel individually before building the palettes, so the result is a usable display image rather than an inky black square.

Toggling this at any time rebuilds the cached stretched versions of every loaded channel — the next palette render uses them immediately. The setting persists across sessions.

2.2 OSC synthesis

Both OSC slots split the loaded RGB into pseudo-narrowband channels and either seed or blend with existing loaded data:

If one of the narrowband slots is also loaded (say you have a dedicated Ha mono alongside your OSC1 data), PPP blends them 50 / 50 rather than discarding either. This gives you a clean upgrade path from OSC-only to OSC-plus-mono-narrowband without rebuilding the project.

2.3 Dimension mismatch

If the loaded channels aren't the same pixel size — different cameras, cropped files, or OSC frames at a different scale from mono — PPP offers to resize everything to a reference frame (preferred: Ha, then OIII, then SII). You get asked once per session. The resize is a simple spatial warp with no plate-solving alignment, so for serious work you want to register your channels upstream first; the resize is a convenience for lazy quick looks and for dropping in a lower-resolution Ha into a higher-resolution OIII / SII pair.

3. Create Palettes

The Create Palettes button builds a thumbnail for every palette in the gallery grid. Each thumbnail is a half-size view of the full frame (so the full composition is visible, not just a centre crop) with the palette name painted on a translucent strip at the bottom. The selected palette's name turns green.

3.1 Clicking a thumbnail

Clicking any thumbnail does two things:

Switching between palettes is fast because PPP uses the already-cached (and possibly stretched) channel buffers. There's no need to re-click Create Palettes between selections.

3.2 Preserving view state

Pan and zoom in the main preview are preserved when you switch palettes. This is the whole point: zoom in on the Running Man, click through SHO → HOO → Foraxx → Dynamic Inverse, and you're looking at the same patch each time, so differences in colour balance jump out. Only the first palette you generate fits automatically to the preview; everything after reuses whatever zoom and pan state you were already in.

4. The main preview

5. The 18 palettes

5.1 Standard permutations

Fourteen three-letter palettes assigning the three narrowband channels to R / G / B in different orders:

NameRGBNotes
SHOSIIHaOIII “Hubble palette.” The classic.
HOOHaOIIIOIII Bicolour, Ha+OIII. Workhorse for Ha+OIII datasets.
HOSHaOIIISII Ha-forward natural-ish rendering.
HSOHaSIIOIII Ha red, SII green, OIII blue.
HSSHaSIISII Bicolour, warm. Yellows the SII regions.
HHOHaHaOIII Bicolour Ha-forward, OIII in blue.
OHHOIIIHaHa OIII red, bicolour warm shadows.
OHSOIIIHaSII OIII-forward with SII in blue.
OOSOIIIOIIISII Cool bicolour, OIII-forward.
OSHOIIISIIHa Rotated SHO; Ha becomes blue.
OSSOIIISIISII OIII red, SII dominant in green and blue.
SHHSIIHaHa SII-red bicolour.
SOHSIIOIIIHa Completes the six true permutations.
SOOSIIOIIIOIII SII-red / OIII bicolour.

With only two of the three narrowband channels available, PPP substitutes the missing one from the available ones so every thumbnail still renders. The substitution rules are documented in § 13.1.

5.2 Realistic1 and Realistic2

Weighted blends aimed at approximating broadband-like colour from narrowband data:

Neither is a true LRGB combine; both bias OIII into the blue channel and spread Ha across red and green to approximate a natural-looking Hα emission in a red / orange hue.

5.3 Foraxx

Dynamic-gated palette that automatically switches character based on whether both Ha and OIII (or all three) are strong at a given pixel. Produces smooth, hue-aware blends without hard thresholds. Works in both bicolour (Ha + OIII) and tricolour (Ha + OIII + SII) modes. See § 13.4 for the math.

5.4 Dynamic Inverse

Frank's own palette, exclusive to SASpro. Uses the Foraxx-style gate function but applies it inversely: Ha and SII swap roles based on OIII presence, and the blue channel is driven by OIII weighted against SII absence. Tends to produce a cooler, more cyan / violet appearance than SHO or Foraxx, which can reveal structure in OIII-dominant regions that the standard palettes flatten. See § 13.5 for the full algorithm.

6. Push Final to New View

Once you've picked a palette, click Push Final to New View. This opens the Adjust Palette Intensities dialog:

This is where you tune the balance between emission lines without having to rebuild upstream: if the Dynamic Inverse looks great but SII is overpowering the midtones, drop its slider to ~60% and you've calibrated the final composite in two seconds.

Workflow suggestion. Create Palettes once, click through every thumbnail that looks promising at a glance, pin the two or three favourites, then run Push Final on each with the intensity sliders fine-tuned. You'll have three polished versions of the same object in different palettes within a few minutes, ready to compare side-by-side at the end.

7. Other buttons

8. Persistence

Saved to QSettings under the perfect_palette_picker/ namespace:

9. Technical details

All channels normalised to float32 in [0, 1]. Where cv2 is available it handles resize and interpolation; a PIL fallback kicks in on systems without it. The palette algorithms are pure NumPy; the stretch functions use SASpro's standard statistical stretch at target median 0.25.

13.1 Channel substitution

When only two of Ha / OIII / SII are loaded, PPP fills the missing one at palette-build time so every three-letter permutation still renders:

Substitution happens after OSC synthesis and after stretch, so a palette like HOS run on Ha+OIII only (no SII) uses SII := Ha, giving a result that is really a tinted bicolour but reads as a tricolour. The thumbnails make this visually obvious: palettes that depend on the missing channel look flat relative to those that don't.

13.2 OSC synthesis

OSC filters (Optolong L-eXtreme, L-eNhance, IDAS NBZ, Antlia ALP-T, etc.) deliver two emission lines on a Bayer sensor. The red site captures the long-wavelength line; the green and blue sites share the shorter-wavelength line because both are inside the filter's second passband. PPP exploits this:

\mathrm{Ha} \;\approx\; \mathrm{OSC}[R]
\mathrm{OIII} \;\approx\; \frac{\mathrm{OSC}[G] + \mathrm{OSC}[B]}{2}

and symmetrically for OSC2 with SII in place of Ha. When a mono channel is also loaded alongside the OSC, the two signals are averaged:

\mathrm{Ha}' \;=\; 0.5\,\mathrm{Ha}_{\mathrm{mono}} \;+\; 0.5\,\mathrm{OSC}[R]

This averaging is deliberately simple: the user who went to the trouble of adding a dedicated mono panel probably wants that signal to count equally, not dominate or be dominated by the OSC estimate.

13.3 Statistical stretch

When Linear input is checked, each loaded channel is run through SASpro's own statistical stretch at a target median of 0.25 (stretch_mono_image for narrowband, stretch_color_image with linked=False for OSC). The stretched buffer is cached per channel; it's rebuilt only when either the Linear toggle changes or the user loads a new channel. Palette construction always uses whatever cache is current, so switching Linear on and off is essentially free after the first build.

13.4 Foraxx algorithm

At the heart of Foraxx is a dynamic gate function:

g(x) \;=\; x^{(1 - x)}

on x ∈ [0, 1]. Properties: g(0) = 0, g(1) = 1, and g(x) stays high across the midrange (for example g(0.5) ≈ 0.707) with a sharp drop only near zero. Used as a blend weight, it produces hue-aware transitions that are smooth where the signal is present and sharp where it isn't.

Tricolour (Ha + OIII + SII):

R \;=\; g(\mathrm{OIII}) \,\cdot\, \mathrm{SII} \;+\; (1 - g(\mathrm{OIII})) \,\cdot\, \mathrm{Ha}
G \;=\; g(\mathrm{Ha} \cdot \mathrm{OIII}) \,\cdot\, \mathrm{Ha} \;+\; (1 - g(\mathrm{Ha} \cdot \mathrm{OIII})) \,\cdot\, \mathrm{OIII}
B \;=\; \mathrm{OIII}

Bicolour (Ha + OIII, no SII):

R \;=\; \mathrm{Ha}
G \;=\; g(\mathrm{Ha} \cdot \mathrm{OIII}) \,\cdot\, \mathrm{Ha} \;+\; (1 - g(\mathrm{Ha} \cdot \mathrm{OIII})) \,\cdot\, \mathrm{OIII}
B \;=\; \mathrm{OIII}

PPP picks between tricolour and bicolour automatically based on whether a real SII channel was loaded (not a substituted one). If only two of Ha / OIII are present with a dummy SII substitution, the bicolour form runs so you don't get a smeared-SII artefact.

13.5 Dynamic Inverse (Frank's algorithm)

Dynamic Inverse reuses the Foraxx gate but applies it to a different structural question: “where OIII is absent, emphasise SII; where OIII is present, emphasise Ha.” This inverts the usual SHO logic where SII sits in red regardless of what OIII is doing.

R \;=\; (1 - \mathrm{OIII}) \,\cdot\, \mathrm{SII} \;+\; \mathrm{OIII} \,\cdot\, \mathrm{Ha}
G \;=\; (1 - g(1 - \mathrm{SII})) \,\cdot\, \mathrm{SII} \;+\; g(1 - \mathrm{SII}) \,\cdot\, \mathrm{OIII}
B \;=\; g(1 - \mathrm{SII}) \,\cdot\, \mathrm{OIII} \;+\; (1 - g(1 - \mathrm{SII})) \,\cdot\, 0.5\,\mathrm{OIII}

Interpretation:

The visual effect is a palette that reads more like a true tri-colour photograph than SHO does — emission regions keep distinct hues even when they overlap — while the inverse logic in the red channel reveals structure that gets buried in flat SHO.

13.6 Thumbnail sizing strategy

Thumbnails are built from a half-size view of the full rectangle, not a centre crop. The first step resizes every loaded channel to the reference frame's full dimensions; the second step scales uniformly to 50% of width and height. The result is a 220×110 button icon that shows the whole composition at reduced resolution, so you can see edge features, nebular extent, and overall balance — information lost in a centre crop. This is why clicking a thumbnail that looks subtle in the gallery can reveal a very striking full-resolution composition in the main preview.

13.7 View state preservation across palette switches

When you click a different thumbnail, PPP captures the current viewport centre as a fraction of the base image size and the current zoom level, builds the new palette, and restores both. Because the palette outputs always share the same dimensions and spatial layout (they're the same channels combined differently), the restored view lands on exactly the same patch of sky. Only the first build fits-to-view automatically.

13.8 Push Final flow

Push Final doesn't just send the current preview — it opens the Adjust Palette Intensities dialog with sliders backed by the same prepared Ha / OIII / SII buffers the palette build used. The sliders multiply each channel before the palette formula runs, and the formula runs again from scratch on every slider move (debounced at 300 ms). The “Accept” path writes the adjusted result back into the main preview and creates a new document through DocManager with the palette name as the title. If DocManager isn't available (unusual — standalone usage), a basic viewer window is opened as a fallback.

Credits. Dynamic Inverse is Frank's own palette, developed for SASpro. The Foraxx algorithm (bicolour and tricolour forms, and the dynamic gate function x1−x) is the published creation of Bob Franke / “Foraxx” in the astrophotography community, implemented here under its original public specification. The 14 three-letter palette permutations (SHO, HOO, HSO, …) are the industry-standard narrowband conventions dating back to the Hubble Space Telescope imaging team's work; the specific set and naming follow general community practice. The Realistic1 and Realistic2 weighted blends are classical mixing formulas with slight custom weight choices in SASpro. Image loading uses SASpro's legacy.image_manager.load_image; stretching uses SASpro's own stretch_mono_image and stretch_color_image. OSC filter examples (Optolong L-eXtreme / L-eNhance, IDAS NBZ, Antlia ALP-T) are third-party products referenced for context only.