Multiscale Decomposition

Split an image into independent spatial-frequency bands, edit each one on its own, then reassemble — a general-purpose detail amplifier, noise reducer, and texture sculptor built on a dyadic Gaussian pyramid.

SASpro-exclusive tool. Multiscale Decomposition is an original SetiAstro Suite Pro implementation by Franklin Marek, not a port of or derivative of any other platform's multiscale transform. It uses a dyadic Gaussian pyramid with σ-aware per-layer soft thresholding, scale-weighted multiscale denoising, and a torch-accelerated separable-blur engine designed from the ground up for SASpro's pipeline, mask system, and ROI preview.

Each decomposition pass splits the input into N detail layers (increasingly coarse spatial scales) plus a residual (the lowest-frequency "DC" image). Every layer can be independently amplified, soft-thresholded against its own measured noise σ, denoised with a scale-weighted multiscale shrinkage, or turned off — then everything is summed back together. The pyramid is dyadic: each successive layer blurs with twice the sigma of the previous one, so Layer 1 holds the finest grain, Layer 4 holds structure several pixels wide, Layer 7+ holds large nebular or lunar-feature-scale content.

Where it shines. This is a genuine miracle on planetary, lunar, and solar images — crank up the Gain on the mid-scale detail layers and watch surface structure, cloud bands, limb detail and crater rims leap out of a seemingly flat frame. On deep-sky work it doubles as a surgical sharpener for nebular filaments and galaxy spiral arms, and the Denoise column gives you a per-scale noise-reduction knob that most tools don't expose.

1. The preview area

The left pane shows the current reconstruction (or the single layer you've selected, see §2). It uses a graphics view with wheel-zoom under the cursor and click-drag panning — the same interaction model as the rest of SASpro.

Preview controls.
Mouse wheel — zoom in / out, anchored under the cursor.
Left-click drag — pan the view.
Zoom Out / Zoom In toolbar buttons — 0.8× / 1.25× steps.
Fit — fit the whole image into the preview pane.
1:1 — reset to native pixel scale.

Two toggles above the layer table change what the preview is doing:

A small spinner next to the zoom toolbar lights up whenever a recomputation is in flight, and a Computing… overlay appears over the preview if a pass takes longer than ~120 ms.

2. Global settings

The top-right Global group controls the shape of the pyramid itself and the top-level preview mode.

Layer-preview trick. Flipping through the detail-layer previews is the fastest way to learn where your data lives. On a planetary frame you'll typically see crater rims and cloud edges concentrated in Layers 2–4; on a galaxy frame, spiral-arm structure shows up in Layers 4–6; on a wide nebula, large-scale brightness variations live in Layers 6–8. Edit the layers where the structure you care about is actually present.

3. The Layers table

Below the Global group is the Layers table — one row per detail layer, plus a final R row for the residual. Columns are:

ColumnMeaning
OnCheckbox — enable or disable this layer. A disabled detail layer contributes zero to the reconstruction (useful for isolating what a layer is actually doing). Unchecking the residual row holds back the DC image, giving you a high-pass view (see §7).
LayerLayer index: 1 (finest) through N, then R for residual.
ScaleThe Gaussian sigma this layer corresponds to, in pixels — base_sigma × 2k. Residual shows "—" because it's an accumulated image, not a scale.
GainMultiplier applied to the layer's detail coefficients. 1.00 is unchanged, >1 boosts, <1 attenuates. Negative values are not supported (use 0 to zero out).
Thr (σ)Soft-threshold level in multiples of the layer's measured noise σ (see §4). 0 is no thresholding; 1–3 is mild to strong grain suppression.
AmtBlend factor between the raw and thresholded version of the layer. 0 means ignore thresholding entirely; 1 means use the thresholded layer fully. Lets you dial in how much of the Thr action to apply.
NRMultiscale noise reduction amount for this layer, 0–1. See §9.3 for the scale-weighted formula.
TypeD for a detail layer, R for the residual.

Click a row to load that layer into the Selected Layer editor below. Clicking any cell in the Gain / Thr / Amt / NR columns lets you edit the value directly — handy for setting the same number across several rows without going slider-by-slider. The checkbox and the editor stay in sync automatically.

4. The Selected Layer editor

Each of the four edit sliders is paired with a spin box, and either side updates the other. The ranges are:

When the R (Residual) row is selected, all four controls grey out — the residual is a reconstructed image, not a set of coefficients, so none of the per-layer operations apply to it. In Linear mode, Threshold / Amount / Denoise grey out on every row (the mode is literally gain-only); Gain stays active.

Threshold vs Denoise. They're both σ-based shrinkage, but they're tuned for different jobs. Threshold + Amount is a sharpening-safe grain filter: it keeps everything above Nσ at its full strength (minus the σ-sized soft knee) and blends how aggressively it suppresses the rest. Denoise is a smoother, scale-aware shrinkage that ramps up on coarse scales, so a single Denoise value gives you balanced NR across the whole pyramid. Use Threshold when you also want to boost — denoising first keeps the Gain from amplifying residual grain.

5. Masking

Multiscale Decomposition respects the document's active mask. If a mask is set when you Apply, the final result is blended against the untouched input using the mask's intensity, so you can confine detail boosts to a nebula while leaving background sky linear and quiet, or protect a star field while sharpening galaxy arms.

Mask handling: non-finite values fall through as "no change," masks are rescaled to 0–1 whether they came in as 8-bit, 16-bit or float, and if the mask resolution doesn't match the image a fast nearest-neighbour resample brings them into alignment. See §9.8 for the formula.

6. Applying your result

Four buttons at the bottom of the right pane finish the session:

7. Residual off: the high-pass trick

If you uncheck the R row's "On" box, the preview and Apply both drop the residual from the reconstruction — you keep only the sum of your tuned detail layers. Because the detail layers are signed (zero-centred), the result is remapped to mid-gray for display:

v = 0.5 + 4 \sum_{k=0}^{N-1} w_k^{(\text{tuned})}

That mid-gray image is the high-pass of your data, filtered through whatever gain / threshold / NR profile you set per layer. It is useful on its own as an edge/structure map, or as input to subsequent processing — the Split Layers button sends each tuned layer out this way automatically.

8. Shortcuts and presets

The small grip icon at the bottom-left of the dialog is a drag-to-canvas preset handle. It captures your current state (layer count, base sigma, mode, per-layer config, residual on/off, linked-RGB flag) and does one of two things depending on where you drop it:

Combined with Replay, this makes it trivial to carry a particular detail-enhancement recipe from one subject to the next — tune it once on a representative frame, drag off a shortcut, and apply it to a whole mosaic panel set.

9. Workflow recipes

Planetary / lunar / solar sharpening.
Base sigma: 0.7–1.0. Layers: 5–6. Mode: μ–σ Thresholding.
• Layers 1–2: Gain 1.2–1.5, Threshold 1.5–2.5 at Amount 0.6–0.8, Denoise 0.15–0.25 — tames high-frequency camera and seeing grain so the next step doesn't just amplify it.
• Layers 3–4: Gain 2.0–3.5, no threshold or denoise — this is where crater rims, cloud banding, and surface structure live. Push until it looks dramatic, back off until it looks real.
• Layer 5+: Gain 1.0 or slightly less; let the base image carry the broad tonality.
• Residual: leave on.
Deep-sky structure boost (galaxies, nebular filaments).
Base sigma: 1.0. Layers: 5. Mode: μ–σ Thresholding.
• Layer 1: Gain 1.0, Threshold 2.0 at Amount 0.8, Denoise 0.3 — pure grain filter.
• Layer 2: Gain 1.15, Threshold 1.5 at Amount 0.6, Denoise 0.2 — small-scale detail with light NR.
• Layers 3–4: Gain 1.3–1.6 — spiral-arm and filament scale.
• Layer 5: Gain 1.0.
• Mask: use a star-protection mask or a range mask excluding the background, so the boost lands on the object only.
Multiscale noise reduction only.
Mode: μ–σ Thresholding. All Gains at 1.0. Denoise 0.25–0.45 on Layers 1–2, 0.15 on Layer 3, 0 beyond. Threshold / Amount left at 0. Residual on. This gives you SASpro's scale-weighted NR profile as a standalone step without any sharpening — a nice complement to the dedicated denoisers.
Frequency-separation retouching.
Click Split Layers to Documents on a portrait or close-up — you now have Layer 1 (grain), Layer 2–3 (texture), Layer 4+ (form), and the residual (tone/colour) as separate documents. Paint, blur, or clone on the one you need, then re-import and combine with Pixel Math or by sending back through this tool with those layers set to Gain 1 and the rest to 0.

10. Technical details

What the pipeline actually does, in order, with the formulas and the engineering choices behind them.

10.1 The multiscale pyramid

Multiscale Decomposition builds a dyadic Gaussian pyramid. Starting from the input image c0 = x, each successive scale is generated by Gaussian blur with a doubling sigma:

c_0 = x
c_{k+1} = G_{\sigma_k}(c_k)
\sigma_k = \sigma_0 \cdot 2^k

where σ0 is Base sigma from the Global group and Gσ is a reflect-padded Gaussian blur. The detail layers are the differences between consecutive scales:

w_k = c_k - c_{k+1}, \quad k = 0,\, 1,\, \dots,\, N-1

The residual is the deepest blur:

r = c_N

This structure is a perfect-reconstruction transform: without any tuning, summing the layers plus the residual recovers the input bit-exactly (to float32 rounding):

x = r + \sum_{k=0}^{N-1} w_k

That identity is why unchecking "On" boxes gives you clean partial reconstructions, why disabling the residual yields a pure high-pass, and why the preview can show you individual layers that mean something.

For RGB images each channel is decomposed independently in parallel (one thread per channel), then merged channel-wise. For mono images the pyramid is computed on the single plane and expanded to 3-channel only for preview display.

10.2 Robust noise estimation

Every detail layer has its own noise σ, estimated by the Median Absolute Deviation:

\hat{\sigma}_k = 1.4826 \cdot \mathrm{median}\!\left(\left|w_k - \mathrm{median}(w_k)\right|\right)

The 1.4826 factor converts MAD to a standard-deviation estimate under Gaussian assumptions. MAD is used instead of plain std because detail layers contain real structure (edges, point sources) that would inflate a straight std and make all σ-based thresholds too generous. For layers larger than ~100,000 samples SASpro grid-subsamples the array first — the cost of a full-image median scales badly, and MAD on a 1% stride samples is statistically indistinguishable from the full computation.

Non-finite values are stripped before the median, so NaNs or Infs in a region of the frame don't poison the σ estimate for the entire layer.

10.3 μ–σ Thresholding mode

In the default mode three operations are applied per layer, in order: denoise, then threshold-with-amount, then gain. The shared primitive is a soft threshold:

S_t(x) = \mathrm{sign}(x) \cdot \max(0,\, |x| - t)

Soft thresholding is preferred over hard thresholding here because it preserves continuity: coefficients just above the threshold aren't yanked to their raw value, they are smoothly attenuated by t. That matters because detail layers are what the reconstruction sums — hard thresholding introduces visible discontinuities at structure boundaries.

Denoise step. With Denoise amount βk, a scale-weighted threshold is applied:

s_k = \sqrt{2^k}
\tau_k = 3\, \beta_k \cdot s_k \cdot \hat{\sigma}_k
w_k^{(1)} = (1 - \beta_k)\, w_k + \beta_k \cdot S_{\tau_k}(w_k)

The √(2k) scale factor is the key piece — coarser layers get a proportionally larger threshold, because that's where the energy of real smooth structure lives and the layers accumulate noise variance across scales. A single Denoise value therefore delivers balanced NR across the whole pyramid without the user needing to tune each layer separately.

Threshold-with-amount step. Using Thr Nσ and Amount αk:

t_k = N_\sigma \cdot \hat{\sigma}_k
w_k^{(2)} = (1 - \alpha_k)\, w_k^{(1)} + \alpha_k \cdot S_{t_k}(w_k^{(1)})

Amount is the blend lever. α = 0 completely ignores the threshold; α = 1 uses it fully. In between is the usual sweet spot — Amount around 0.5–0.8 keeps enough of the original coefficient energy that boosted detail doesn't look thin while still cleaning up the grain.

Gain step. Simple multiply:

w_k^{(\text{tuned})} = g_k \cdot w_k^{(2)}

10.4 Linear mode

Linear mode skips Denoise, Threshold and Amount entirely and keeps only Gain:

w_k^{(\text{tuned})} = g_k \cdot w_k

This makes the whole pipeline strictly linear — a multiscale frequency-weighted multiplier. Use it when you want pure band-boost/cut with zero shrinkage artifacts, when you're prototyping a filter design, or when the output is feeding another linear step and nonlinearities would complicate the math downstream.

10.5 Reconstruction and output modes

After all layers are tuned, the output is the straightforward sum:

y = r_{\text{on}} + \sum_{k=0}^{N-1} w_k^{(\text{tuned})}

where ron is the residual if its "On" box is checked, zero otherwise. When the residual is off, the sum is signed and remapped to mid-gray for display and output:

y_{\text{display}} = \mathrm{clip}\!\left(0.5 + 4\,y,\; 0,\; 1\right)

With the residual on, the result is clipped straight into [0, 1]. Mono inputs are preserved as mono — the pyramid runs on the single plane, and only the preview is expanded to RGB internally; Apply writes back the single-channel reconstruction in the original shape.

10.6 The Gaussian blur engine

The Gaussian pyramid's performance is the dialog's performance — on a 24-megapixel RGB frame with 6 layers, that's 18 full-image blurs with sigmas ranging from 1 to 32 pixels. SASpro picks the best available backend at each call:

  1. Images under 256×256 or sigma below 0.5 go straight to OpenCV's GaussianBlur — no backend overhead is worth it at that scale.
  2. Otherwise, a torch separable-conv path is tried first. The 2D Gaussian factors into two 1D convolutions, which SASpro implements as grouped conv2d operations with reflect padding — once with a (1 × K) horizontal kernel and once with a (K × 1) vertical kernel. This runs on CUDA when a GPU is present and on torch's CPU backend otherwise. Both paths are genuinely parallel across all cores and don't fight with the per-channel thread pool.
  3. If torch is unavailable, SASpro falls back to a tiled SciPy path: the image is divided into roughly 2× worker-count tiles sized to the image aspect ratio, each tile is extended by an overlap of ⌈4σ⌉ + 4 pixels on all sides, and the tiles are blurred in parallel with scipy.ndimage.gaussian_filter before being stitched back without the overlap. The overlap guarantees reflect-pad continuity at the tile seams.
  4. If neither is available, OpenCV's GaussianBlur is used unconditionally.

The kernel size is picked from sigma the standard way:

K = \max\!\left(3,\, 2 \lfloor 3\sigma \rfloor + 1\right) \;|\; 1

(the | 1 forces an odd kernel size, which both OpenCV and torch's conv2d want for centred padding).

Separable 1D convolution is the second big win here. A 2D Gaussian is the outer product of two 1D Gaussians, so a K × K blur can be done as:

G_\sigma(I) = \left(I \ast g_\sigma^{\mathsf{T}}\right) \ast g_\sigma

at 2K multiplies per pixel instead of K2. On a 32-pixel-sigma blur that's roughly a 20× arithmetic saving, which stacks with the GPU/multicore gains.

GPU vs CPU vs scipy. On a mid-range CUDA card the torch GPU path is roughly 10–20× faster than tiled scipy on large frames; on CPU torch is typically 2–4× faster than tiled scipy because it keeps the arithmetic in a single contiguous tensor and avoids NumPy per-tile overhead. The dialog's "init progress" dialog you see on first open is almost entirely the time of this first pyramid build; subsequent slider changes reuse the cached pyramid and only recompute what has to change.

10.7 Fast ROI preview

With Fast ROI preview on, the preview computes only the currently visible region of the image, with a safety margin on each side so the deepest blur still sees valid pixels. The pad is sized to the coarsest layer in the pyramid:

\sigma_{\max} = \sigma_0 \cdot 2^{N-1}
p = \lceil 3 \sigma_{\max} \rceil + 2

The ROI is further capped at 1400 px per side to protect responsiveness when the user is zoomed way out. For a 24-megapixel frame zoomed to a 1200×800 ROI with 6 layers, this means decomposing ~1.1M pixels instead of ~24M on each slider move — a 20× cost reduction that puts slider latency in the 20–50 ms range even with CPU-only torch.

The viewport scrollbars and the zoom/pan handlers all schedule the same debounced ROI recompute (60 ms coalescing) so a rapid pan or wheel-roll produces one recomputation at the end rather than a storm of them during the gesture.

ROI is a preview only. Apply, Send to New Document and Split Layers always run on the full image and always use the full-resolution pyramid. The ROI path exists so you can tune parameters interactively on huge frames without waiting; the committed result is never a stitched-up ROI.

10.8 Mask blending

If the document has an active mask, the final reconstruction is blended against the untouched source:

y_{\text{final}} = m \cdot y + (1 - m) \cdot x

where m is the mask value in [0, 1], y is the full reconstruction, and x is the original input. Masks are normalised regardless of their input dtype — integer masks are divided by their type-max, float masks greater than 1 are rescaled by their observed max, and anything outside [0, 1] is clipped. If the mask resolution doesn't match the image, SASpro resamples with a fast nearest-neighbour index map; if the blend produces non-finite values anywhere (bad mask data), the entire mask step is bypassed and the plain reconstruction is written through instead.

The mask id, blend formula and whether the step was masked are all recorded in the history-step metadata so Replay reproduces the exact masked composition on the next frame.

Credits. Multiscale Decomposition — algorithm, implementation, UI and ROI/preview engineering — is original work by Franklin Marek for SetiAstro Suite Pro, with the following standard components credited to community: the Gaussian pyramid is a classic construction (Burt & Adelson 1983, "The Laplacian Pyramid as a Compact Image Code"); soft thresholding is Donoho (1995, "De-noising by soft-thresholding"); robust σ from MAD follows the Rousseeuw & Croux convention; separable Gaussian factorization is textbook image processing. OpenCV, PyTorch and SciPy are credited as the compute backends. The scale-weighted multiscale denoiser (τk = 3βk√(2k)σ̂k), the mode system, the ROI-preview architecture, the mask blending integration, and the overall per-layer editor are SASpro-exclusive.