SASpro-exclusive dynamic-range decompressor for the dark half of the histogram — the symmetric partner to WaveScale HDR. The problem it solves: after stretching and processing, shadow regions in an astronomical image often collapse into a nearly flat dark grey. There is real structure in there — dark nebulae, dust lanes, LDN filaments, molecular cloud texture that was present in the raw data — but it has been compressed into such a narrow luminance range above pure black that it reads as featureless. The Dark Enhancer decomposes luminance into starlet wavelet planes, builds a mask that highlights where the darkness is, and then subtracts from the negative wavelet coefficients only in those regions to re-expand the shadow micro-contrast. Dust lanes, LDN texture, and subtle shadow structure reappear without the background sky or the mid-tones being touched.
If WaveScale HDR works on the bright half of your tonal range, this works on the dark half. The algorithm shares the same starlet backbone and side-mask-window UX, so if you've used HDR this will feel familiar immediately; the differences are in what the mask selects (dark structure, not bright regions) and how the wavelet planes are modified (negative coefficients get pushed more negative, rather than all coefficients being amplified).
Slider, 2–10, default 6. How many wavelet detail layers the luminance is split into. The algorithm skips the finest plane (plane 0) and the coarse residual (so the finest-scale noise and the overall brightness level are both left alone), and the enhancement runs on every intermediate scale.
Slider, 0.10–10.00, default 5.00. Strength of the shadow contrast enhancement — the per-plane amplification applied to the negative wavelet coefficients where the darkness mask says to.
Slider, 0.10–10.00, default 1.00. Shapes the darkness mask by raising it to this power after normalisation. Unlike HDR's gamma (which directly operates on luminance), this one shapes the already normalised darkness response — so 1.0 is neutral, higher values narrow the mask onto the strongest-dark features, lower values spread it to more subtle shadow variation.
The side Dark Mask window shows exactly what this slider produces, with the window title changing to “Dark Mask (Algo × Active Mask)” when the document has an active mask that's being multiplied in.
Slider, 1–10, default 2. How many times the whole pipeline (mask build → decompose → boost → reconstruct) is applied in series. Crucially the mask is rebuilt from the current luminance at each iteration — so previously flat shadow regions that now have more contrast get a fresh mask that can target newly-revealed sub-structure.
A 400×400 side window showing the current effective mask — the algorithmic darkness mask multiplied by the document's active mask if one exists. White means “full effect here”, black means “leave alone”, greys are proportional blends. Updates live as you drag Mask Gamma (250 ms debounce) and after every Preview completes. Purely display — nothing you do here changes the result.
Standard controls:
Zoom In / Zoom Out / Fit to Preview buttons sit above the preview area; the view also supports cursor-anchored wheel/trackpad zoom. The preset grip at the bottom-left carries the four slider values and can be dragged onto the canvas to make a shortcut tile, or dropped onto an image to apply those settings headlessly.
All processing on float32. Luminance is CIE L* in [0, 100] for RGB inputs, or the mono channel in [0, 1] directly. The algorithm shares its starlet decomposition and document-mask integration with WaveScale HDR; see that tool's documentation for the B3-spline starlet and mask-multiplication specifics.
End-to-end, Preview runs:
The iteration loop is the main structural difference from HDR — the mask is rebuilt from the current luminance at each pass, so what was flat shadow at iteration 1 can reveal new sub-structure at iteration 2 which then gets its own fresh mask.
Where HDR builds its mask directly from normalised luminance
(L / 100)γ, the Dark Enhancer
builds its mask from the wavelet decomposition itself —
specifically, from the negative parts of mid-scale planes, which
mark regions that are darker than their local background at those
scales. For each mid-scale plane
wi with i = 1, 2, 3
(the second, third, and fourth planes — skipping the finest
noise-dominated plane 0):
which captures only the darker-than-local-average parts of each plane. These are averaged, normalised by their own maximum, gamma-shaped by the user's slider, Gaussian-blurred, and finally passed through a mild S-curve brightener:
Three design choices worth unpacking:
f(x) = 1.5x − 0.5x²
is a mild midtone-lift: it fixes
f(0) = 0 and f(1) = 1
with f'(0) = 1.5, so weak dark signals
get boosted into the usable mask range without the strongest
ones overshooting unity. Essentially a one-knee gamma that
makes the mask more sensitive without blowing out its top end:
If the document has an active mask mdoc,
the effective mask used downstream is their product:
so you can spatially restrict the dark-recovery effect to any arbitrary shape drawn in a mask layer on top of the automatic darkness-driven selection.
This is where the algorithm diverges from HDR most dramatically.
HDR amplifies all wavelet coefficients (both positive and
negative) in bright regions. The Dark Enhancer instead subtracts
from only the negative parts of each plane, in shadow regions. For
each intermediate plane i ≥ 1:
Pixels where the original plane coefficient was positive
(wi > 0, locally brighter than
background) have ni = 0 and so
ei = 0 — they're untouched.
Pixels where the coefficient was negative get their value pushed
more negative by an amount proportional to how negative they
already were, how strong the mask is at that location, and how fine
the scale is (via the decay rate). Plane 0 is explicitly skipped (its
scale is dominated by noise), and the coarse residual is passed
through unchanged.
When the modified planes and the untouched residual are reconstructed, the pixels that were already in shadow get darker, creating stronger local contrast against the surrounding not-shadow pixels. The effect reads as revealed structure in the shadows: dust lanes become more defined against the galaxy's broad light, LDN filaments stand out from their surrounding faint nebulosity, subtle grain within a dark cloud becomes visible.
The whole mask-build → boost pipeline runs
N times with the output of each pass becoming the input
to the next:
Crucially the mask is rebuilt from the current
L(k) at each pass, not frozen to its initial
value. This matters: after the first iteration, previously flat
shadow regions have more contrast, which changes which wavelet
coefficients are negative, which changes the darkness mask. The
second pass can then target newly-revealed sub-structure that the
first pass unlocked. Each iteration ends with a clip to the valid
L* range ([0, 100] for Lab, [0, 1] for mono) before the next pass
starts.
At the end of each iteration the modified planes and the untouched coarse residual are summed back using the same identity reconstruction as HDR:
No midtone realignment or dimming gamma is applied here, unlike HDR. The Dark Enhancer only pushes values downward (toward zero), so there's no risk of highlights clipping past unity — the clip to the valid L* range at the end of each iteration is sufficient to keep the result in bounds.
The gamma-dimming step from HDR is absent, but the final composite with the untouched original still uses the effective mask so that pixels outside the shadow regions remain exactly as they were:
So midtones and highlights pass through untouched, and only the regions the darkness mask selected get the enhanced version. This is what makes the tool safe to use before or after any other tonal adjustment — the background sky and brighter regions are guaranteed-unchanged by construction.
RGB inputs are converted to Lab, operated on in the L* channel, and converted back — so chroma (a*, b*) is bit-for-bit preserved and only luminance structure changes. Mono inputs skip the Lab step and operate directly on the normalised intensity in [0, 1]; the mask construction and plane modification formulas are the same, with the L / 100 normalisation step omitted. On Apply, mono inputs are collapsed back to their original channel count (2-D or (H, W, 1)).
wavelet_utils module. Lab colour-space conversion
follows CIE 1976.
The whole WaveScale Dark Enhancer algorithm — the
mid-scale-negatives darkness mask with its Gaussian smoothing and
S-curve brightener, the per-scale enhancement by subtraction from
negative plane coefficients only, the geometric decay across scales,
the mask-rebuilding iteration scheme, the integration with the
document's active mask, and the final mask-blended composite
— is SASpro-exclusive, designed and implemented by
SetiAstro as the complementary dark-end companion to WaveScale HDR.