Blemish Blaster

Three healing tools in one interactive dialog. The Blemish Blaster fixes round defects (dust motes, bad pixels, satellite flashes, residual stars after StarNet) with a one-click median-of-median heal. Linear Blaster paints out straight or bent line defects (satellite trails, cosmic rays, airplane trails) with a draggable path. Banding Blaster suppresses horizontal or vertical row/column banding across the entire frame.

1. Mode selector

Three radio buttons at the top of the controls panel choose the active mode. Switching modes resets any in-progress linear path and hides the controls that don't apply, so the panel stays uncluttered. The preview on the right responds differently in each mode — hover-and-click in Blemish, click-and-drag path building in Linear, pan-only in Banding (because banding operates on the whole frame at once).

2. Shared controls

Zoom

Three toolbuttons at the top of the left pane: Zoom Out, Zoom In, Fit to Preview. Mouse-wheel and trackpad gestures also zoom with the cursor as the anchor — small trackpad swipes give fine control, click-wheel detents give coarse, hold Ctrl to zoom faster in either case. Zoom range is 0.05× to 4×. The first time the dialog opens the preview is auto-fit to its viewport; after that your zoom and pan state is preserved across mode switches.

Radius / Feather / Opacity

These three sliders are shared by Blemish and Linear modes (Banding uses its own set). Each has a live numeric spinbox alongside the slider that stays in sync.

Auto-stretch preview

Below the mode-specific controls. When on (default), a statistical stretch is applied to the preview only — the underlying image data is never modified by this control. Essential when working on linear data where a dust mote would otherwise be invisible.

Undo / Redo / Apply

The in-dialog undo/redo ring holds the last 20 operations. Each blemish heal snapshots only its affected patch; linear and banding operations snapshot the whole image (since a line can span the frame and banding touches every pixel). Ctrl+Z and Ctrl+Y are intercepted inside the dialog so they don't trigger the main window's own undo — the dialog wins whenever it has focus.

Apply to Document commits the current working image back to the originating document as a single undoable edit named “Blemish Blaster” (whichever modes were used during the session), then closes the dialog. Close discards everything. The dialog's window geometry is saved per-user and restored on next open.

Keyboard shortcuts

[ and ] decrease or increase the brush radius by 2 pixels at a time, and the preview circle updates immediately under the current cursor position. Standard for digital-brush tools; indispensable for working through a frame with mixed defect sizes without reaching for the slider.

3. Blemish Blaster mode

Default mode. The classical SetiAstro one-click heal.

Interactions

Workflow

Set the radius just slightly larger than the defect itself (not larger than the defect plus its surroundings — the healer samples the surroundings, so leave them visible). Feather around 50% hides the transition on most backgrounds. Click once; if the result looks off, undo and try at a different radius or offset the click a few pixels to pick a different hex arrangement of sampling patches. The algorithm itself is described in § 6.1.

Tip: for a tiny defect against complex background (nebula filaments, galaxy spiral arms), reduce opacity to 50–70% so the real structure around the defect bleeds through, then click twice — each click re-runs the median-of-median selection so you get a different hex arrangement if you've nudged slightly.

4. Linear Blaster mode

Draws a path that gets stroked at the current Thickness with rounded caps, and healed by sampling parallel offsets on either side. The path can be straight or bent as a quadratic Bézier, and can optionally extend all the way to the image borders.

Building the path

Thickness and extent

Overlays

Blast Line button

The ⚡ Blast Line button is enabled once both endpoints are placed. Pressing it dispatches the linear worker (algorithm in § 6.2) and clears the path once the heal completes. The undo ring records the entire pre-blast image so a full undo always works.

5. Banding Blaster mode

Row- or column-banding reducer for Canon DSLRs (horizontal) and some CMOS sensors with column banding (vertical). This mode does not use the preview-click interface — the correction runs on the entire frame at once with a single button press. The preview switches to scroll-hand drag so you can pan to inspect the result, but clicking does nothing until you switch back to Blemish or Linear.

Direction

Horizontal (default) or Vertical. Horizontal targets rows — the common Canon DSLR pattern. Vertical targets columns — some CMOS astro cameras exhibit column banding especially on long exposures. Internally, vertical is implemented as a transpose + horizontal + un-transpose, so the algorithm is bit-identical in either direction.

Amount

0.00–4.00 spinbox, default 1.00. The strength of the correction, as a multiplier on the per-row median offset.

Protect highlights / Sigma

Protect highlights (checkbox, default on) excludes bright pixels from the per-row median so stars and DSO cores don't bias the correction. Essential for astronomical images and the only reason this tool works at all on a Milky Way frame or a nebula field. When off, the sigma control is disabled.

Sigma (0.01–5.00, default 1.00) is the rejection threshold above the global median, in robust-sigma units (§ 6.3). Lower is more aggressive. 1.0 keeps roughly 80% of a smooth background in the row-median estimate; drop to 0.3–0.5 on very star-dense fields.

Blast Banding button

The ⚡ Blast Banding button runs the correction and pushes the result into the preview and the dialog's undo stack as a whole-image patch. Undoable like any other operation.

6. Technical details

All processing on float32 [0, 1]. The dialog displays a 3-channel copy of the working image regardless of source depth; on Apply, mono originals are collapsed back to a single plane.

6.1 Blemish healing algorithm

For a click at pixel p = (x, y) and brush radius r, six candidate neighbour centres are placed on a hexagon at 1.5× the radius:

\mathbf{c}_k \;=\; \mathbf{p} \;+\; 1.5\,r\,(\cos\theta_k,\;\sin\theta_k)\,,\quad \theta_k = 60^{\circ}k\,,\;k=0,\dots,5

The median brightness is computed inside the circular mask of radius r at the target position and at each of the six candidates. The three candidates whose medians are closest to the target's are selected:

\{\Delta_1,\Delta_2,\Delta_3\} \;=\; \mathrm{argmin}_{\{3\}\subset K}\;|\mathrm{median}(I\|_{\mathrm{patch}_k}) - \mathrm{median}(I\|_{\mathrm{target}})|

For every pixel q inside the brush, the healed value is the median of three samples at the matching offsets:

\mathrm{healed}(\mathbf{q}) \;=\; \mathrm{median}\!\left(I(\mathbf{q}+\Delta_1),\;I(\mathbf{q}+\Delta_2),\;I(\mathbf{q}+\Delta_3)\right)

This two-level median (medians across patches to pick, then median across the three samples to synthesise) is what gives the heal its resistance to local features. A bright star in one neighbour patch would be flagged by its high median and excluded at the pick step; and even if a bright pixel slips through, the per-pixel median across three samples throws it out. The “match the median” pick is also what makes the heal look seamless on gradients — it automatically prefers neighbours of the same local brightness level as the defect's own surroundings.

The feathered mask is built from the Euclidean distance d from the brush centre, inside the circular support d ≤ r:

w(\mathbf{q}) \;=\; \mathrm{clip}\!\left(\frac{r - d}{r\,f},\;0,\;1\right)

where f is the feather fraction. At f = 0 the mask is a hard disk; at f = 1 it ramps linearly from the centre. The final blend uses the opacity slider as a scaling factor on this weight:

\alpha \;=\; \mathrm{opacity}\,\cdot\,w\,,\qquad \mathrm{out} \;=\; (1 - \alpha)\,\mathrm{orig} \;+\; \alpha\,\mathrm{healed}

Sample positions that would fall outside the image are REPLICATE-clipped to the nearest in-bounds pixel — a blemish near the edge still gets corrected, using the border rather than being skipped.

Per-click the operation is tiled to the bounding box [x ± r, y ± r] and runs on a background thread so the GUI stays responsive during larger brushes.

6.2 Linear healing algorithm

The user's path (two endpoints and an optional bend handle) is converted into a sequence of straight sub-segments. When bent, the path is a quadratic Bézier through endpoints A, B and a control point computed so the curve passes through the user's handle m at t = 0.5:

\mathbf{M} \;=\; 2\,\mathbf{m} \;-\; \frac{1}{2}(\mathbf{A} + \mathbf{B})
\mathbf{B}(t) \;=\; (1-t)^{2}\mathbf{A} \;+\; 2(1-t)t\,\mathbf{M} \;+\; t^{2}\mathbf{B}

The curve is discretised into n = clip(arclen/24, 4, 64) vertices; each pair of consecutive vertices becomes a straight sub-segment with its own local tangent. Full-line mode adds two more sub-segments: a ray from A along −end-tangent to the image border, and a ray from B along the far end-tangent to the border.

Each sub-segment is healed in its own local frame of orthonormal coordinates (ŵ, ń) along and perpendicular to the segment:

t = (\mathbf{q} - \mathbf{p}_0)\cdot\hat{\mathbf{u}}\,,\qquad s = (\mathbf{q} - \mathbf{p}_0)\cdot\hat{\mathbf{n}}
\mathrm{band} \;=\; \{\mathbf{q} \;:\; |s(\mathbf{q})| \leq T/2\}

Caps are rounded at true endpoints (first/last sub-segment under Segment mode); joints between internal sub-segments are flat and overlap each other by half the thickness so the stroke doesn't seam visibly along a bent path.

Within each sub-segment, SASpro samples the target band and six offset slabs at ±T, ±2T, ±3T perpendicular to the segment (via cv2.remap with reflect border). For each column t, the three offset slabs whose medians most closely match the target column's median are selected and used as the heal source — the same two-level median idea as the blemish mode, but applied column-wise along the segment so a curved path can heal differently at each t.

\mathrm{offsets} \;=\; \{\pm T,\;\pm 2T,\;\pm 3T\}

The feather/opacity blend is identical to § 6.1 using |s|/T as the distance variable instead of radial d. When sub-segments overlap at joints, a “keep-strongest” rule writes to the output only where the current sub-segment's α exceeds whatever a previous sub-segment already wrote — so the heal doesn't double-blend along the join line.

6.3 Banding reduction algorithm

Vertical banding is handled by transposing the array, running the horizontal algorithm, then transposing back. The horizontal algorithm operates per channel and per row.

Let g be the channel's global median and I the full channel. The robust background sigma uses mean (not median) of absolutes to match Georg's original script bit-for-bit:

\sigma_{\mathrm{rob}} \;=\; 1.4826\,\cdot\,\mathrm{mean}\!\left(|I - g|\right)

With Protect highlights on, the per-row median is computed over only those pixels at or below a threshold:

m_r \;=\; \mathrm{median}\!\left\{ I_{r,c} \;:\; I_{r,c} \leq g + \sigma\,\sigma_{\mathrm{rob}} \right\}

Rows with no pixels at or below the threshold fall back to g (which makes fixr = 0 for those rows). The correction and application are:

\mathrm{fix}_r \;=\; (g - m_r)\,\cdot\,\mathrm{amount}\,,\qquad I'_{r,c} \;=\; \mathrm{clip}\!\left(I_{r,c} + \mathrm{fix}_r,\;0,\;1\right)

Which is to say: each row is additively shifted so its surviving (dim) pixels sit at the global median, scaled by the amount slider. Rows whose medians already match the global sit still; rows that are anomalously dim or bright get pulled back by the same amount end-to-end, which is exactly the right fix for a readout-induced constant offset per row.

Sort-based masked median speedup

The naive implementation of median(row where row ≤ threshold) is np.nanmedian on a masked copy. SASpro ports Georg's JS code to a vectorised numpy formulation that is 4–7× faster on typical astro-image sizes while producing bit-identical results: for each row count the survivors nr = |{c : Ir,c ≤ threshold}|, sort every row ascending (so survivors occupy positions [0, nr)), and index out the median positionally with an odd/even case split.

6.4 Auto-stretch preview

Purely display-side. On every image refresh, if the checkbox is on, the working image is passed through stretch_mono_image (for original mono source) or stretch_color_image (for RGB) with the Target Median and Linked settings. The result goes into a separate display buffer; the working image used by the healers is never touched.

6.5 Undo/redo patch system

Each operation records a tuple (y0, x0, prev) where prev is a copy of the pre-op image bytes in the affected rectangle. For blemish heals this is just the brush bounding box plus the 1.5r sampling padding; for linear and banding it's the whole image. Undo swaps the current pixels in that rectangle with the saved copy and pushes the forward-direction patch into the redo ring; redo is the mirror operation. The ring is bounded at 20 entries to cap peak memory.

Credits. The Banding Blaster algorithm is a port of CanonBandingReduction.js v0.9.1 by Georg Viehoever (PixInsight JavaScript, GPLv3, 2009–2013). All credit for the row-median-subtraction method with robust-sigma highlight rejection goes to Georg; SASpro's port is a straight translation to Python/NumPy with a sort-based masked-median speedup and vertical-direction support added. The port is also GPLv3. The Blemish Blaster (hex-neighbour median-of-median heal with feathered circular blend) and the Linear Blaster (perpendicular-offset band heal with Bézier bending, full-line extension, and column-wise match selection via cv2.remap) are SetiAstro's own designs and implementation, originally for SetiAstro Suite v2 and extended for SASpro.