WaveScale Dark Enhancer

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

1. Workflow

  1. Open the dialog with an image active. A side Dark Mask window pops up immediately showing where the effect will be applied — this is the darkness mask built from the image's own wavelet decomposition at the current Mask Gamma.
  2. Adjust Mask Gamma so the mask lights up on the shadow regions where you want to recover detail (dark nebula lanes, LDN clouds, dust in galaxy arms). The side window updates live with a 250 ms debounce.
  3. Adjust Boost Factor for strength, Number of Scales for how large a dark structure the effect can reach, and Iterations for how many passes of the whole pipeline to run.
  4. Press Preview. A worker thread runs the full iterative pipeline and the main view updates.
  5. Use Show Original to A/B compare; iterate on the sliders and Preview again.
  6. Apply to Document commits. The dialog stays open for the next image.

2. Dark Enhancer controls

Number of Scales

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.

Boost Factor

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.

Mask Gamma

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.

Iterations

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.

3. The Dark Mask window

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.

4. Preview / Show Original / Apply

Standard controls:

5. Zoom and preset grip

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.

6. Technical details

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.

6.1 Pipeline overview

End-to-end, Preview runs:

RGB → Lab → L* ↓ for iter = 1 to N: build darkness mask from current L* combine with doc mask (if present) starlet-decompose L* for each intermediate plane: push its negatives further negative, scaled by mask reconstruct L* ↓ Lab → RGB → mask-blend with original → preview

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.

6.2 Darkness mask construction

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

n_i \;=\; \max(0,\;-w_i)

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:

m_{\mathrm{raw}} \;=\; \frac{1}{3}\sum_{i=1}^{3}\max(0,\;-w_i)
m_0 \;=\; \frac{m_{\mathrm{raw}}}{\max(m_{\mathrm{raw}}) + \varepsilon}\,,\qquad m_1 \;=\; m_0^{\,\gamma}
m_2 \;=\; G_{\sigma=3} \ast m_1
m \;=\; \mathrm{clip}(1.5\,m_2 - 0.5\,m_2^{\,2},\;0,\;1)

Three design choices worth unpacking:

If the document has an active mask mdoc, the effective mask used downstream is their product:

m_{\mathrm{eff}} \;=\; m_{\mathrm{dark}} \,\cdot\, m_{\mathrm{doc}}

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.

6.3 Per-scale enhancement by negative subtraction

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:

n_i \;=\; \max(0,\;-w_i)\,,\qquad \mathrm{decay} \;=\; \rho^{\,i}\;\;(\rho = 0.5)
e_i \;=\; n_i \,\cdot\, m_{\mathrm{eff}} \,\cdot\, (B - 1) \,\cdot\, \rho^{\,i}
w'_i \;=\; w_i \;-\; e_i

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.

6.4 Iteration

The whole mask-build → boost pipeline runs N times with the output of each pass becoming the input to the next:

L^{(k+1)} \;=\; \mathrm{DSE}\!\left(L^{(k)},\;m(L^{(k)})\right)\,,\quad k=0,\dots,N-1

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.

6.5 Reconstruction

At the end of each iteration the modified planes and the untouched coarse residual are summed back using the same identity reconstruction as HDR:

L' \;=\; \sum_{i=0}^{n-1} w'_i \;+\; c_n

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.

6.6 Mask-blended composite

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:

\mathrm{out} \;=\; (1 - m_{\mathrm{eff}})\,\mathrm{original} \;+\; m_{\mathrm{eff}}\,\mathrm{transformed}

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.

6.7 RGB vs mono handling

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

Credits. The B3-spline starlet (à trous) wavelet transform is the classical isotropic undecimated wavelet of Starck & Murtagh (see Astronomical Image and Data Analysis). SASpro uses it as its luminance decomposition primitive via the shared 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.