Attenuates diffuse haloes around bright stars in a stars-only image. Built for the halo patterns typical of achromatic and semi-apo refractors, uncorrected spherochromatism, and dew- or scatter-induced glows — the big soft rings and bluish halos that nothing else wants to deal with.
Halo-B-Gon is a targeted retouch, not a generic denoiser or deconvolution. It operates on stars-only images (the output of Remove Stars), finds concentric ring-like structure around point sources, and darkens it. The star cores themselves are preserved.
The standard sequence is:
Running Halo-B-Gon on a full starless+stars image is not what the math is tuned for — the ring-detector will fire on nebular edges too, and the gamma pass will darken everything. Keep it to the stars image.
Why it works best on refractor data. Achromatic and semi-apo refractors can leave big, soft, often blue or violet rings around bright stars because secondary-spectrum focus differs between wavelengths. Those rings are extended light that the star removal step kept with the stars layer. Halo-B-Gon targets the concentric spatial pattern directly, so it attenuates them without touching the star cores.
A slider with four discrete settings:
Each level affects two things at once: the strength of the ring-detection mask that gets multiplied into the image (0.33 × level, so Extra Low = 0, Medium = 0.66), and a final darkening gamma curve (γ = 1.2, 1.5, 1.8, 2.2 across the four levels). The two effects compound — stepping up from Low to Medium is a real jump, not a small tweak.
Check this if you are running Halo-B-Gon on linear data (data
that has not been stretched). The algorithm's unsharp step is
scale-sensitive and designed for display-range intensities, so for
linear data the dialog applies a x1/5 pre-pass
to lift the shadows into a workable range before running the main
pipeline. For stretched data this box stays off.
The dialog stays open after each Apply and refreshes its preview to whatever document is now active, so you can keep processing other images without reopening the tool.
The preview panel shows the current result in real time:
Ctrl + wheel — cursor-anchored zoom
(0.1× to 10×).Preview and Apply run the same code path — what you see is exactly what you get.
If the active document has a mask active, Halo-B-Gon's output is blended with the untouched original through that mask on Apply: the halo reduction only lands where the mask is bright. This lets you process just one quadrant of a mosaic, or protect a nebular region that happens to have ring-like structure the algorithm would otherwise dim. The preview does not account for the mask; it shows the raw algorithm output.
At the lower-left corner the grip icon lets you drag the current settings out as a reusable preset: drop it on the shortcut bar to make a one-click Halo-B-Gon button with these exact parameters, or drop it directly onto an open image to apply them headlessly without opening this dialog. The preset carries reduction level and the Linear data flag; the output-target choice (overwrite / new view) is a session-level preference and is deliberately not part of the preset.
Direct port of the SASv2
HaloProcessingThread.applyHaloReduction implementation.
All processing on float32 in [0, 1]; mono stays mono, RGB stays
RGB. Where OpenCV is used (cvtColor, GaussianBlur,
addWeighted, multiply, LUT)
Halo-B-Gon uses it — this is a tuned algorithm with specific
numeric constants rather than a parameter-rich one.
When Linear data is checked, the pipeline first lifts intensities with a strong gamma:
bringing shadows into the mid-range the unsharp step was tuned for.
The rest of the pipeline then runs on x' and the result
is kept in that space — there is no inverse gamma at the end,
because Halo-B-Gon's final gamma LUT already provides shaping.
From the (possibly pre-passed) working image, the pipeline builds a single-channel lightness:
For RGB this is the OpenCV
COLOR_RGB2GRAY conversion; for mono input it uses the
plane directly. The division by 255 looks surprising on a float image
already in [0, 1] — the result lies in [0, 1/255] —
and it is load-bearing: the whole pipeline is calibrated around
these small values. Changing the normalisation changes the mask
strength and would require re-tuning the 1.66, 0.66, and 0.33
constants below. The /255 is kept intentionally for byte-exact match
with SASv2.
Build an unsharp version of the lightness with a Gaussian blur:
where Gσ is the Gaussian blur at
σ = 2. This emphasises point-source centres
and damps smooth gradients — which is exactly what you
need to tell halo rings apart from the surrounding sky.
The core trick:
where k is the reduction level
(0, 1, 2, 3). Term by term:
1 − U is close to 1 almost everywhere and dips
wherever U rose — i.e. at and around star cores.Gσ(U) smears those dips outward into
ring-shaped signatures with the same scale as the halos.0.33 k · Gσ(U)
from (1 − U) carves ring-shaped reductions
into the mask with amplitude controlled by the reduction level
k.At level 0 (k = 0) the mask reduces to
(1 − U), so only the final gamma pass darkens the
image. From level 1 onward, the ring-detector contribution kicks in
and halo structure is actively suppressed.
The mask broadcasts across colour channels (so all three RGB channels get the same spatial correction). Because the mask is close to 1 almost everywhere and dips at halos, multiplying leaves most of the image alone and darkens exactly where halos were.
A per-level gamma curve applied through an 8-bit LUT finishes the job:
with γ drawn from the fixed set
{1.2, 1.5, 1.8, 2.2}:
γ = 1.2 at Extra Low, up to
γ = 2.2 at High. Every γ is > 1, so the
curve darkens mid-tones; the higher the level, the harder the darkening.
This provides a global contrast boost on top of the ring-targeted
reduction and is what gives the different levels their perceivable step
in strength.
and, if a document mask is active, the result is blended back through it (output in the mask's bright regions, original everywhere else).
Input shape round-trips exactly: a 2-D mono image comes out 2-D,
a 3-D single-channel image (H×W×1) stays that
shape, and RGB stays RGB. The mask is replicated across channels for
RGB input; for mono it stays 2-D. The output is clipped to [0, 1]
and returned as float32.
The function
apply_halo_b_gon_to_doc(parent, doc, preset)
performs the entire pipeline from a preset dict:
reduction — integer 0–3 (clamped if
out of range).linear — boolean flag for the linear pre-pass.This is what the preset drag handle registers for Replay, and what drops of the handle onto an image invoke directly without opening the dialog.
cvtColor, GaussianBlur,
addWeighted, multiply, LUT) for
the heavy per-pixel work.