Convolution / Deconvolution

Four tools in one dialog. The Convolution tab blurs with a user-shaped PSF (useful for softening synthetic stars, generating calibration kernels, or deliberately matching another image's seeing). Deconvolution runs four classical sharpeners — Richardson–Lucy, Wiener, Larson–Sekanina (comet feature enhancer), and Van Cittert. PSF Estimator extracts a real stellar PSF from the frame with SEP and feeds it back to RL/Wiener. Classical Denoise hosts six denoisers (TV, NLM, Bilateral, Gaussian, Median, Wavelet) running on a background thread with a Cancel button.

1. Shared controls (always visible)

Preview panel

The right-hand view shows the active image (or current preview result). Scroll-wheel and trackpad gestures zoom with cursor as the anchor — small trackpad swipes give fine control, click-wheel detents coarse, hold Ctrl for faster zoom in either case. Click-and-drag pans (scroll-hand). The zoom row above the view holds explicit buttons for Zoom In, Zoom Out, and Fit-to-Preview.

Auto-stretch display

The checkbox above the view applies a statistical stretch to the preview only — the underlying pixel data is never modified by this control. It exists so you can judge denoise/deconv results on linear data without having to Push and then Undo a dedicated stretch. Target median picks the stretch strength (0.01–0.60, default 0.25). Linked keeps the three RGB channels tied together; uncheck to stretch each channel independently (useful when the image has a strong colour cast you want to see through). Both controls are disabled until the checkbox is on.

PSF preview chip

The 64×64 chip below the tabs always shows the PSF that will actually be used by Preview on the current tab. For the Convolution tab, that's the elliptical super-Gaussian built from the four sliders. For RL/Wiener on the Deconvolution tab, it's either those sliders' Gaussian or the custom stellar PSF when one is active. For other tabs the chip is empty. Change any slider and the chip updates live.

Strength

Global blend between the processed result and the original, applied at the end of every Preview on every tab:

\mathrm{out} \;=\; s\cdot\mathrm{processed} \;+\; (1-s)\cdot\mathrm{src}

Lets you “dial back” an aggressive deconv or denoise rather than having to re-run with weaker parameters. The slider accepts values from 0.00 (no change) to 1.00 (full effect).

Preview / Undo / Push

Masks

If the destination document carries an active mask, the processed result is blended through it against the pre-processed image when Preview runs, so only masked regions are affected. The mask is nearest-neighbour resampled to match the image shape and broadcast to the right channel count. See § 6.6 for the formula.

Preset grip

The handle at the bottom-left of the controls captures the current tab's state as a preset — drag it onto the canvas to make a shortcut tile, or drop it onto an image to apply those settings headlessly. Each tab has its own preset schema: Convolution carries the four PSF sliders + strength; Deconvolution carries every slider on every algorithm's panel (plus the Larson–Sekanina centre if one has been clicked) so that Replay Last Action and the shortcut re-create the exact run; Classical Denoise carries the method + its parameters.

2. Convolution tab

Blurs the image with a parametric elliptical super-Gaussian. All four sliders feed the live PSF chip; see § 6.1 for the formula.

Preview convolves each channel independently with reflect padding, so borders don't darken (see § 6.2).

3. Deconvolution tab

Algorithm selector

Dropdown at the top of the tab chooses which algorithm Preview runs. The parameter panel below updates to show only that algorithm's controls. The PSF slider group is visible only for Richardson–Lucy and Wiener; the other two define their own kernels internally.

Custom PSF bar

When a stellar PSF has been loaded from the PSF Estimator tab, a blue “Using Stellar PSF” bar appears above the parameter stack with a Disable Stellar PSF button. While the bar is lit, RL and Wiener use the measured PSF instead of the slider-built Gaussian, and the preview chip shows the stellar kernel. Clicking Disable, or changing any of the four PSF sliders, drops the custom PSF and reverts to the Gaussian.

Richardson–Lucy

Iterative multiplicative deconvolution. The classical choice for restoring stars and small-scale detail on photon-limited data.

Wiener

Single-pass frequency-domain deconvolution. Much faster than RL and less prone to ringing, but less adaptive to local statistics.

Larson–Sekanina

Comet feature enhancer. Rotates a copy of the image by a small angle around a chosen centre, optionally shifts it radially, then divides or subtracts the rotated copy from the original. The result enhances anisotropic features (jets, tails, shells) by cancelling the roughly circular comet coma, and is then blended back into the original with either SoftLight or Screen.

Tip: for a first look at a comet, place the centre, set radial step 0 and angular step 1° with Divide + SoftLight, Preview, then push the angular step up in 0.5° steps watching the jets lengthen and the coma vanish. Add a radial step of 2–5 px once you see what direction the jets want.

Van Cittert

The oldest practical deconvolution scheme — additive, non-blind, non-regularised. Fast, intuitive, and prone to runaway amplification with too many iterations. Useful when you want a mild sharpening pass without the full RL machinery, and the PSF is a smooth Gaussian.

Van Cittert uses an internally-fixed σ = 3 Gaussian PSF and is applied to each channel independently; the four PSF sliders at the top of the Deconvolution tab are ignored.

4. PSF Estimator tab

Measures the real point-spread function of the image by stacking normalised postage stamps around detected stars, using SEP for detection. The result can be sent straight into RL or Wiener as a custom PSF, or saved for use later.

Run SEP Extraction runs detection, stacks the stamps, and updates the small PSF preview below. Use as Current PSF loads that kernel into RL/Wiener, switches the algorithm to Richardson–Lucy, lights the Custom PSF bar on the Deconvolution tab, and refuses to reset until you click Disable or move a PSF slider. Save PSF… writes the kernel to FITS (float32, no header) or TIFF (float32) for reuse in other tools or later sessions.

5. Classical Denoise tab

Six classical denoisers under one roof, selected from a dropdown. Preview runs on a background thread with a Cancel button next to the status line — the dialog stays responsive and you can abort a too-slow run without force-killing SASpro. The strength and mask blends from § 1 apply on the GUI thread once the worker finishes.

Operate on L* only (shared)

Default on. For colour images, converts to Lab, denoises only the L* channel, converts back. Preserves chroma, which is nearly always what you want — running the per-channel path on RGB tends to desaturate and introduce colour mottling in low-SNR regions. Uncheck to run on each channel independently.

Methods

6. Technical details

All algorithms operate on float32 [0,1]. Mask and strength blends apply uniformly at the end of every Preview path.

6.1 Elliptical super-Gaussian PSF

Both the Convolution tab and the slider path for RL/Wiener build their kernel from the same four parameters: radius r, kurtosis exponent β, aspect ratio a, and rotation θ. With σx = r, σy = r/a, and the rotated grid:

x_r=\cos\theta\,x+\sin\theta\,y\,,\quad y_r=-\sin\theta\,x+\cos\theta\,y
h(x,y) \;=\; \exp\!\left(-\left(\left(\frac{x_r}{\sigma_x}\right)^{2} + \left(\frac{y_r}{\sigma_y}\right)^{2}\right)^{\!\beta}\right)

normalised to unit sum so convolution preserves image mean. The exponent β is the “kurtosis” slider: at β = 1 the formula reduces to the standard elliptical Gaussian; β > 1 produces a flat-topped, sharply falling kernel (approaching a top-hat as β grows); β < 1 produces a peaked, long-wing kernel (approaching a Moffat profile for small β). The kernel grid size is ⌈6σx⌉, forced odd. Results are LRU-cached by (r, β, a, θ) so the live PSF preview and repeated previews with the same kernel don't rebuild.

6.2 Convolution with reflect padding

Each channel is reflect-padded by half the kernel size on each edge, convolved via scipy.signal.fftconvolve in "same" mode, then cropped back. Reflect padding (as opposed to zero padding) is what keeps the image border from darkening when a large soft kernel reaches past the frame — the reflected pixels present the kernel with sensible data instead of zeros.

6.3 Richardson–Lucy

The classical multiplicative update:

f_{k+1} \;=\; f_k \;\cdot\; \left(\frac{y}{f_k \ast h} \;\ast\; h^{*}\right)

where y is the observed image, h is the PSF, h* is h flipped in both axes, and ∗ denotes convolution. The multiplicative form guarantees non-negativity automatically. SASpro initialises f0 from y clipped to ≥ 10-8, then runs the stated number of iterations.

Regularised variants

After each RL step, when a regulariser is selected:

\mathrm{Tikhonov\;(L2):}\quad f_{k+1} \;\leftarrow\; f_{k+1} \;-\; \alpha_{L_2}\,\nabla^{2} f_{k+1}
\mathrm{Total\;Variation:}\quad f_{k+1} \;\leftarrow\; \mathrm{TV}_{\alpha_{\mathrm{TV}}}(f_{k+1})

with αL2 = 0.01 and αTV = 0.01 hard-coded, chosen to be gentle enough that the regulariser nudges rather than overrides the data term. The Laplacian in the L2 term uses scipy.ndimage.laplace; the TV step calls skimage.restoration.denoise_tv_chambolle on the current estimate. Each iteration ends with a clip to [0, 1] before the next step.

De-ring

When the checkbox is on, a single pass of skimage.restoration.denoise_bilateral (sigma_color = 0.08, sigma_spatial = 1) runs after the last iteration. This is applied per-tile just before blending (see below), so ring artefacts around bright edges are suppressed without a big post-pass over the whole image.

Tiled parallel execution

Full-frame RL on a large image is slow and FFT-dominated, so SASpro splits the frame into horizontal tiles, one per CPU core, deconvolves them in parallel with a ThreadPoolExecutor, and seamlessly blends them back together. Each tile is extended by overlap = max(psf_h, psf_w)/2 + 15 rows on each side before the RL loop, so neighbouring tiles overlap and the central rows are far from any padding-induced artefacts. The overlapping rows are blended with a vertical ramp:

w_i \;=\; \min\!\left(1,\;\frac{i+1}{\mathrm{overlap}},\;\frac{L-i}{\mathrm{overlap}}\right)

that goes smoothly from 0 at the tile's extreme edge to 1 in its core. Overlapping contributions are accumulated weighted by this ramp and divided by the summed weights:

\tilde f(y,x) \;=\; \frac{\sum_t w_t(y)\,\tilde f_t(y,x)}{\sum_t w_t(y)}

which gives a smooth hand-off across tile seams. For colour images, each RGB channel (or the L* channel in L*-only mode) runs through this pipeline independently; the status label at the bottom-left of the dialog shows which channel and iteration count is active.

6.4 Wiener

The PSF is embedded in a zero-padded, image-sized array, FFT'd, and the standard Wiener filter is applied in the frequency domain:

W(u,v) \;=\; \frac{H^{*}(u,v)}{|H(u,v)|^{2} + K}

with K = λ for classical Wiener and K = λ² for Tikhonov L2, where λ is the Noise/Signal slider. The result is IFFT(W · FFT(y)), clipped to [0, 1]. When de-ring is on, a single bilateral pass with the same parameters as the RL de-ring runs on the output. Colour handling (L*-only vs per-channel) is identical to RL.

6.5 Larson–Sekanina

For each pixel at image position (x, y) with respect to the chosen centre (x0, y0), compute polar coordinates (r, θ) and sample the image at the shifted polar position:

J(r,\theta) \;=\; I\!\left(r + \Delta r,\;\theta + \Delta\theta\right)

using bilinear interpolation (SASpro's own _bilinear_interpolate_gray on the vectorised polar grid, not scipy.ndimage.map_coordinates, so the memory footprint scales with image size only). Out-of-bounds samples return 0. The resulting J is then combined with the original I by the chosen operator:

\mathrm{Divide:}\quad B \;=\; I \cdot \frac{\mathrm{median}(J)}{J + \varepsilon}
\mathrm{Subtract:}\quad B \;=\; \max(0,\;I - J)\,/\,\max_{x,y}(I-J)

The subtract variant is renormalised to [0, 1] so the final blend has a predictable range. For colour inputs the image is reduced to its luma before the LS step and then broadcast back to three channels for blending. The final composite with the original uses one of:

\mathrm{Screen:}\quad C \;=\; A + B - A\cdot B
\mathrm{SoftLight:}\quad C \;=\; (1 - 2B)\cdot A^{2} + 2B\cdot A

chosen to match the operator (Divide → SoftLight, Subtract → Screen); the dropdown lets you override.

6.6 Van Cittert

Simple additive update toward the observed image:

f_{k+1} \;=\; f_k \;+\; \beta\,(y - f_k \ast h)

with β the relaxation parameter and h a fixed σ = 3 separable Gaussian built from a 1-D outer product. No stabilisation; stays well-behaved only for small β and modest iteration counts. Each channel runs independently.

6.7 SEP stellar PSF estimator

For each selected source i inside the saturation cap, a (2w+1)×(2w+1) stamp is cut from the background-subtracted frame around its integer-rounded centre, flux-normalised, and summed:

\mathrm{PSF}(x,y) \;=\; \frac{1}{N}\sum_{i=1}^{N}\frac{\mathrm{stamp}_i(x,y)}{\sum_{x,y}\mathrm{stamp}_i(x,y)}

Sources are sorted by SEP's reported peak value descending, so the brightest unsaturated stars dominate the first entries; the Max stars cap trims the tail. The final kernel is sum-normalised so convolution preserves image mean. If a source's stamp runs off the edge or has non-positive total flux it is skipped; if every candidate fails, Preview reports the error and no PSF is produced.

6.8 Classical denoisers

Each method dispatches through _denoise_single_channel, so L*-only vs per-channel is decided once at the top level. The underlying implementations are:

Every result is clipped to [0, 1] before returning to the GUI thread.

6.9 Strength and mask blend

After every Preview on every tab, the strength slider blends the processed result with the original:

\mathrm{processed} \;\leftarrow\; s\cdot\mathrm{processed} \;+\; (1-s)\cdot\mathrm{src}

and, if the active document carries a mask m ∈ [0, 1]:

\mathrm{out} \;=\; \mathrm{processed}\cdot m \;+\; \mathrm{original}\cdot(1-m)

in that order. The mask is read from the destination document, resized with nearest-neighbour sampling (OpenCV when available, NumPy index fallback otherwise), and broadcast to the right channel count.

Credits. Richardson–Lucy, Wiener, Van Cittert, Larson–Sekanina, TV Chambolle, non-local means, and wavelet thresholding are all classical published algorithms; the implementations here call into SciPy, scikit-image, and OpenCV for the mathematical primitives (FFT convolution, Laplacian, TV solver, NLM, bilateral filter, wavelet transform). Stellar PSF detection uses SEP (Barbary et al.). The tiled multithreaded RL executor, the overlapping vertical-ramp blend, the elliptical super-Gaussian with its kurtosis exponent, the custom bilinear LS polar sampler, the Divide/Subtract LS operator pair with its auto-matched blend modes, the strength blend, and the stamp-stacking PSF normalisation on top of SEP's detections are SASpro's own work.