Planetary Stacker

The main lucky-imaging stacking engine — takes a video or image sequence loaded in the Planetary Stacker Viewer, scores every frame for sharpness, places alignment points automatically, aligns via multi-stage phase correlation and SSD refinement, optionally corrects atmospheric dispersion / field rotation / planetary axial derotation, and produces a drizzle-ready weighted stack of the best frames. This page documents the per-file Stacker dialog; the Batch Planetary Stacker runs this same engine across many files in sequence.

SASpro-exclusive tool. The Planetary Stacker is an original SetiAstro Suite Pro implementation by Franklin Marek. The quality metric (variance of in-mask Laplacian plus feature-richness, log-compressed and half-pedestal corrected), the three-phase bootstrap-to-Welford-style transition between coarse and fine alignment, the SSD-prepared-reference hill-climb refinement with slice-overlap SSD evaluation (no warp per candidate), the reference-locked coarse surface tracking with keyframe-boundary parallel chunking that prevents frame-to-frame drift, the MAD-based AP outlier rejection with dense inverse-distance displacement fields, the two-pass hysteresis-gated field rotation search, the per-channel chromatic dispersion correction, the Export-Aligned-SER workflow for fast re-stacking, the interactive quality graph, and the Blink Keepers manual culling dialog are all built from the ground up for SASpro's planetary pipeline.
Five-step workflow
(1) Analyze — score every frame, build the reference, place alignment points, compute shifts, rotation, dispersion. → (2) Edit APs (optional) — adjust the alignment-point layout manually and re-align. → (3) Blink Keepers (optional) — manually reject individual frames from the keep-% set. → (4) Export Aligned SER (optional) — bake the alignment into a new SER so you can re-stack later without re-analyzing. → (5) Stack Now — quality-weighted integration of the kept frames with optional drizzle, producing the final image.

The quality metric that drives frame ranking, computed per frame on a downsampled mono preview:

Q = 0.70 \cdot \log\!\left(1 + 10^4\,\sigma_{\mathrm{lap}}^2\right) + 0.30 \cdot \frac{\overline{\|\nabla I\|}_M}{\max_M \|\nabla I\|}

— variance of the in-mask Laplacian (log-compressed) for sharpness, plus the mean-over-max of the in-mask gradient magnitude for feature richness. §14.1 derives it. Scores are then half-pedestal-corrected and used both for ranking (which frames to keep) and as weights at stack time.

Contents
1. Dialog layout
2. Stack Settings
3. Drizzle
4. Planet Axial Rotation
5. Field Rotation
6. Calibration (flats)
7. Analyze settings
8. The quality graph
9. AP Editor
10. Blink Keepers
11. Export Aligned SER
12. Stacking
13. Output
14. Technical details
14.1 Quality metric
14.2 Reference frame
14.3 Alignment points
14.4 Planetary centroid tracker
14.5 Surface coarse tracking
14.6 AP phase correlation
14.7 SSD gradient refinement
14.8 Multi-scale AP combination
14.9 MAD outlier rejection
14.10 Dense displacement field
14.11 Field rotation search
14.12 Chromatic dispersion
14.13 Planet axial derotation
14.14 Drizzle
14.15 Quality-weighted stacking
14.16 Flat-field calibration

1. Dialog layout

The Stacker is a two-column dialog. The left column holds all the setup: Stack Settings, four collapsible sections (Drizzle, Planet Axial Rotation, Field Rotation, Calibration), Analyze settings, and the five action buttons at the bottom. The right column shows the live quality graph after Analyze finishes, plus a log window that narrates each phase of processing. A progress bar below the action buttons tracks the current phase.

The dialog is non-modal — it stays usable while open, so you can go back to the Viewer, re-pick the ROI or surface anchor, and the Stacker syncs those changes on window activation.

2. Stack Settings

Tracking mode

Inherits from the Viewer but can be overridden here:

Keep %

What fraction of frames, by quality rank, actually makes it into the stack. The default is 20%. You can set it here (0.1 to 100), by dragging the vertical yellow cutoff line on the quality graph after Analyze, or by typing a different value.

Debayer

Decodes a Bayer mosaic to RGB. For AVI sources opened from the Viewer, this should match whatever the Viewer's Bayer pattern dropdown is set to; for SER files, the pattern comes from the SER header automatically. Off = treat as mono.

Center planet in output

Only enabled in Planetary mode. If ticked, every frame is shifted so the planet's centroid lands at the exact centre of the output image, rather than at the position of the reference frame's centroid. Useful when the reference happens to be an off-centre frame and you'd rather have the final stack centred for crop and framing.

Correct atmospheric dispersion

Only enabled in Planetary mode with Debayer on. Splits each frame into R/G/B channels, finds each channel's centroid independently, and shifts R and B to align with G before the local AP warp — removing the small prismatic colour fringes that atmospheric refraction smears across a disc at low altitude. §14.12 covers the maths. Leave off for sources at the zenith; it's a noticeable win below about 30° altitude.

Surface anchor

Read-only here — shows the anchor you set in the Viewer. In Surface mode it must be set, or Analyze refuses to start. The label shows the anchor in both ROI-space and (if ROI is active) full-frame coordinates for sanity-checking.

3. Drizzle (collapsible)

Drizzle (Fruchter & Hook 2002) upsamples each aligned frame and “drops” each input pixel's value onto a finer output grid, letting a well-sampled stack of many frames recover resolution beyond the native sampling. For planetary work it's useful when your camera's pixel scale under-samples the diffraction limit.

Scale

Pixfrac

The “drop shrink” factor, in [0.30, 1.00], default 0.80. Lower values make each input pixel's drop smaller on the output grid (sharper reconstruction, but can produce gaps and noise if coverage is sparse); higher values spread each drop wider (smoother coverage, slightly softer).

Rule of thumb: 1.5× drizzle pairs well with pixfrac 0.80; 2× drizzle pairs well with pixfrac 0.70.

Kernel & sigma

SASpro's drizzle uses a Gaussian kernel exclusively — square or circular kernels produce visible grid artefacts on planetary data. The Sigma field controls the Gaussian width in output pixels; 0 means “auto”, which sets σ = max(10⁻³, pixfrac × 0.5), which is a sensible default for most situations. Nudge up for smoother coverage, down for slightly sharper response.

4. Planet Axial Rotation (collapsible)

For long clips of fast-rotating planets like Jupiter, the planet actually rotates during the clip, smearing features in the stack. SASpro's axial-derotation unwinds this by projecting each frame back to a geographic (latitude/longitude) map, shifting longitude by the known rotation rate times frame delta, and reprojecting (§14.13).

Enable derotation

Master toggle for the feature.

Preset

Picks a sidereal rotation rate from a built-in table (based on standard values):

PresetPeriodRate (deg/min)
Jupiter (System III)~9.925 h~0.6050
Saturn~10.656 h~0.5631
Mars~24.623 h~0.2437
Mercury~58.646 d~0.004264
Venus (retrograde)~243.025 d~−0.001029
Moon~27.321661 d~0.009155
Uranus (retrograde)~17.24 h~−0.3480
Neptune~16.11 h~0.3725
Custom…—Enter manually

Reverse sign

Camera orientation and sky-side-up conventions can make the default sign of the rotation worse than nothing. Tick Reverse sign if derotation makes smearing worse rather than better; it flips the rate.

Rate (deg/min)

The literal rate used, filled in by the preset or editable with Custom. Negative values are allowed (retrograde).

Set disk…

After Analyze completes, click this to open SASpro's planetary disk-picker on the reference frame. You pick the disk centre, radius, pole position angle and sub-observer latitude ratio (b/a). These parameters feed the longitude-shift projection in §14.13. The status label shows cx, cy, r once set.

5. Field Rotation (collapsible)

On alt-az mounts without a field rotator, the field rotates during a session. Over short clips the rotation per frame is often small enough to be absorbed into translation alignment, but over longer clips (or wider FOV) it compounds. Tick Correct field rotation to add a per-frame small-angle rotation search after the translation stage.

6. Calibration (collapsible)

Divide every raw frame by a normalised master flat before stacking — the same slot you'd use in AutoStakkert for vignetting, dust motes, Newton's rings and per-pixel QE non-uniformity.

Flat file

Accepted formats:

The flat must be at the same sensor resolution, binning and ROI setting as the lights. The pipeline normalises each channel of the flat to a mean of 1.0 before dividing, so you get back frames at roughly the same brightness as uncorrected ones, just with the response flattened.

Apply master flat

The master toggle. If you pick a file via Browse… this auto-enables.

7. Analyze settings

Analyze is the first (and most expensive) stage: score every frame for quality, build a reference frame, autoplace alignment points on the reference, then compute per-frame translation + optional rotation + optional dispersion in parallel.

Reference

AP scale

SSD refine: brute force

By default the SSD refinement uses a hill-climb search over integer offsets with a subpixel quadratic polish (fast). Ticking brute force switches to a full radius-5 window scan at every pixel, which is slower but can rescue tough data where the hill-climb falls into a local minimum. See §14.7.

AP min mean (0..1)

Alignment points are only placed on patches whose mean brightness exceeds this threshold. Default 0.03. Raise it to force APs onto the brighter central disc only; lower it to let APs populate dim limb or terminator regions as well.

AP size (px)

Side length of each alignment patch in reference pixels, 16 to 256, default 64. Larger patches average over more detail (robust, but slow to update when the scene changes); smaller patches track finer detail. For planets at native resolution, 64 is a good start.

AP spacing (px)

Centre-to-centre spacing of the AP grid during autoplace. Default 48 — which means APs overlap (spacing < size), giving good coverage. Reduce for denser grids; increase for sparser ones.

8. The quality graph

After Analyze completes, the right column shows the quality graph: a bright green curve of per-frame quality sorted from best (left) to worst (right). Reference lines:

Click or drag on the graph to move the cutoff and set a new Keep % in one gesture. The Keep % spinbox stays in sync. The log reports every move so you can retrace your choices.

Looking at the curve shape:

9. AP Editor

Click (2) Edit APs… after Analyze to open the AP editor, a dedicated preview with every current alignment point drawn as a bright green outlined square on the reference image.

AP Editor controls:
🖱️ Left click — add an AP centred where you clicked.
🖱️ Right click — delete the AP nearest to the click (within ~60% of AP size).
🖱️ Ctrl+Wheel — zoom in/out.
Zoom toolbar + slider — also sets zoom (10% to 400%).
Fit / 1:1 buttons — snap zoom.
Auto-place — re-run grid autoplace with the current Size/Spacing/Min-mean values.
Clear — remove all APs (so you can start from scratch).

Changing the Size, Spacing or Min-mean controls at the bottom triggers an auto-relayout after a 250 ms debounce. On OK, SASpro automatically re-runs the alignment pass (shifts, rotation, dispersion) using the new AP layout — the quality scores and reference frame are preserved from the original Analyze run, so this is a fast operation.

When to edit APs: autoplace works well for most planetary scenes but can place too few APs on faint subjects, or waste APs on completely featureless areas (uniform background sky, saturated highlights). Manual placement lets you concentrate APs where features actually are — crater rims, limb edges, cloud bands.

10. Blink Keepers

Click (3) Blink Keepers to visually review every frame in your Keep % set and manually reject individual ones. The dialog shows one keeper at a time with its frame index and quality score; rejected frames are excluded from the stack and the final frame count on the quality graph updates.

Blink Keepers keyboard shortcuts:
← / → — previous / next keeper.
↑ / ↓ — same.
PgUp / PgDn — same.
R or Space — toggle reject on the current frame and advance.
Backspace — toggle reject on the current frame and step back.
Esc — cancel without changes.
Enter — accept and close.

The preview applies the same translation + field-rotation transforms that stacking will apply, so what you see is roughly what each frame will contribute to the stack. A Brightness slider at the bottom helps you see detail in faint frames. Rejected frames are drawn in red in the info line; the button to the right of the status also toggles reject.

Blink Keepers is strictly a cull step — it never adds frames you already culled with Keep %, only removes ones that look bad despite scoring well (satellite streaks across a bright patch, aircraft lights, dropped-sync frames whose quality metric happens to be high).

11. Export Aligned SER

A powerful workflow trick: click (4) Export Aligned SER… after Analyze to write a new SER file where every frame has already been translated (and optionally rotated and derotated and locally warped) into reference alignment. The exported file is uint16 at the current ROI size.

The dialog lets you choose:

When would you want this?

12. Stacking

Click (5) Stack Now to run the final integration. Each kept frame is loaded, flat-corrected (if a master flat is loaded), globally translated by its analysis (dx, dy), optionally rotated by its analysis angle, optionally derotated, optionally dispersion-corrected per channel, and then locally warped by the dense displacement field derived from the AP shifts. The warped frames are accumulated with quality weights (§14.15) or deposited into a drizzle grid (§14.14). The final result is a float32 image in [0, 1].

Stacking is heavily parallel: frames are warped in a thread pool whose worker count defaults to min(CPU count, 48). In non-drizzle mode the warped frames are accumulated serially on the main thread with their quality weights to avoid locking overhead; in drizzle mode, deposits land into shared buffers one frame at a time (also serially, to avoid Numba GIL contention under high parallelism).

13. Output

On completion, the result is pushed as a new SASpro document named <source-basename>_stack (e.g., jupiter_2026_11_21_stack), with full history metadata including the stacking diagnostics (frames kept vs. total, track mode, drizzle settings, worker count). The document is opened in a sub-window ready for stretch, wavelets, deconvolution, or whatever else your post-processing pipeline needs.

The log window records the full run including every phase's elapsed time and the final kept-of-total count, so you have a durable record of exactly what was done.

14. Technical details

The analysis and stacking pipeline, in order.

14.1 Quality metric

Every frame is downsampled to a mono preview (long axis ≤ 512 px) and scored in two components, both computed only over pixels with signal above a 0.05 floor — so empty sky, black background, and off-planet regions don't dilute the score with their uniform noise. Let:

M = \{(x,y)\;:\;I(x,y) > 0.05\}

Sharpness — variance of the in-mask Laplacian

Compute the Laplacian with a 5×5 kernel (cv2.Laplacian(ksize=5), slightly more noise-resistant than 3), then take the variance over the signal mask:

\sigma_{\mathrm{lap}}^2 = \mathrm{Var}_{M}(\nabla^2 I)

Variance (as opposed to mean absolute value) punishes frames where sharpness is spatially inconsistent — e.g., half the disc in focus while the other half is soft — which a mean-absolute metric would score the same as a uniformly-slightly-sharp frame.

Variance alone has arbitrary units, so SASpro log-compresses:

S = \log\!\left(1 + 10^4\,\sigma_{\mathrm{lap}}^2\right)

which keeps the dynamic range in a comfortable scoring range and prevents one extreme outlier frame from dominating.

Feature richness — mean-over-max gradient magnitude

Compute the gradient magnitude via Sobel operators:

\|\nabla I\| = \sqrt{(\partial_x I)^2 + (\partial_y I)^2}

then take the mean over the signal mask, normalised by the in-mask maximum:

R = \frac{\overline{\|\nabla I\|}_M}{\max_M \|\nabla I\|}

This rewards frames with fine detail distributed across the signal region, as opposed to a single sharp limb edge that would peg the max but leave the rest soft.

Combine, half-pedestal-correct, use as weight

Weighted sum (70% sharpness, 30% richness):

Q = 0.70\,S + 0.30\,R

After scoring every frame, SASpro subtracts half the minimum from all scores and clips at zero:

Q' = \max(Q - 0.5\,Q_{\min},\;0)

This removes the DC pedestal that the log-compression introduces and broadens the relative spread, so differences between good and bad frames are more proportionally meaningful. The adjusted scores are used for both the Keep% ranking and the per-frame weights at stack time (§14.15).

14.2 Reference frame

In Planetary mode, the reference is either the highest-quality single frame (Best frame) or the mean of the top N by quality (Best stack (N)). In Surface mode, the reference is always frame 0 regardless of this setting — because the surface coarse-tracking pass locks every frame to frame 0 as its reference anchor; using a quality-ranked frame instead would make the (0, 0) shift of the reference itself ambiguous.

14.3 Alignment points (autoplace)

APs are placed on a regular grid over the reference frame. For each candidate position (cx, cy):

Grid spacing is ap_spacing, which can be less than ap_size for overlapping tiles (recommended). If no candidate passes the brightness test (e.g., a very dim target), SASpro falls back to a single AP at the image centre.

14.4 Planetary centroid tracker

In Planetary mode, each frame's shift is computed by finding its brightness centroid and matching it to the reference centroid. The centroid formula:

x_c = \frac{\sum_{(x,y)\in M} x\,I(x,y)}{\sum_{(x,y)\in M} I(x,y)}
y_c = \frac{\sum_{(x,y)\in M} y\,I(x,y)}{\sum_{(x,y)\in M} I(x,y)}

with M = {(x,y) : I(x,y) > τ} and τ the Threshold setting from the Viewer (or 0.5 default), optionally after a per-frame normalisation. The pre-threshold image is first smoothed with a Gaussian of σ = 1.5 to suppress single-pixel noise spikes that would otherwise bias the moments.

Shift is then (dx, dy) = (ref_cx − cur_cx, ref_cy − cur_cy), with a confidence from the signal strength. If confidence is ≥ 0.25, the SSD refinement pass (§14.7) tightens it further.

14.5 Surface coarse tracking (reference-locked NCC + phase correlation)

Surface mode uses a two-stage approach. First, a coarse pass finds each frame's offset relative to frame 0 via template matching. Second, a fine SSD refinement polishes it.

Why reference-locked rather than frame-to-frame?

Classical approach: find frame i's shift relative to frame i−1, chain them. Problem: errors accumulate. Over a long clip, the chain drifts and the final frames end up aligned to an entirely different piece of the Moon than frame 0.

SASpro's approach: lock every frame's template search to frame 0's template directly. Drift literally cannot happen because the reference never moves. The cost is a wider search window per frame (because the predicted offset for a late frame can be large), which SASpro handles with a per-frame predictor seeded by the previous frame's result.

The pipeline per frame

  1. Both frame 0 and the current frame are converted to mono, downsampled by 2×, and bandpass-filtered to remove large-scale illumination gradients (the terminator) and tiny high-frequency noise:
    \mathrm{BP}(I) = G_{\sigma_1}(I) - G_{\sigma_2}(I)
    with σ_1 = 1.2 and σ_2 = 6.0, then z-normalised and Hann-windowed.
  2. A 256×256 template is extracted from frame 0 at the surface-anchor centre.
  3. A search window of radius search_radius = 96 pixels is centred on the predicted offset in the bandpassed current frame.
  4. NCC template matching (cv2.matchTemplate with TM_CCOEFF_NORMED) finds the integer-pixel best match in the window:
    (\Delta x,\,\Delta y) = \arg\max_{(x,y)} \mathrm{NCC}(x,y)
    with confidence equal to the normalised cross-correlation score at the peak.
  5. Subpixel refine via cv2.phaseCorrelate on the matched patch:
    R(f_1, f_2) = \mathcal{F}^{-1}\!\left\{\frac{F_1\,F_2^{*}}{|F_1\,F_2^{*}|}\right\}
    gives a subpixel shift adjustment plus a response-peak confidence.
  6. Final confidence: 0.65 × NCC + 0.35 × phase-corr-response.

Parallelisation

The whole clip is split into stride-16 chunks that run in parallel across a thread pool. To seed each chunk's predictor, SASpro first does a sequential keyframe-boundary pass with a wider search radius (2× the normal one), so each chunk starts from a known-good predictor. This gives near-linear speedup without the drift risk of fully-parallel chaining.

14.6 AP phase correlation

For every AP on the reference, SASpro computes the per-AP shift that aligns the current frame's corresponding patch to the reference patch via phase correlation. Each AP:

  1. Extract reference and current patches at the same location.
  2. Downsample both to a common size (long axis ≤ max_dim = 512).
  3. Phase-correlate:
    (\Delta x,\,\Delta y) = \mathrm{phaseCorrelate}(\mathrm{ref},\,\mathrm{cur})
    returns a subpixel shift and a response value.
  4. Scale the shift back up to full ROI pixels.

The result is per-AP arrays (ap_dx, ap_dy, ap_resp) that get combined into a global shift via the median (for a single global translation) or into a dense displacement field for local warping (§14.10).

14.7 SSD gradient refinement

After the coarse translation estimate (from centroid or AP phase correlation), SASpro polishes it by minimising the sum of squared differences between gradient images:

\mathrm{SSD}(\Delta x,\Delta y) = \sum_{(x,y)} \left(G_{\mathrm{ref}}(x,y) - G_{\mathrm{cur}}(x+\Delta x,\,y+\Delta y)\right)^2

where G is the Sobel gradient magnitude (illumination-robust compared to raw intensity). The optimisation hinges on an aggressive precompute to make per-candidate SSD evaluation nearly free:

Prepare the reference once

The reference gradient is computed once, cropped to the central 80% to avoid edge artefacts, and cached.

Shift the current image once

The current image is shifted by the coarse (dx_0, dy_0) once, its gradient is computed once, and that cropped gradient is cached.

Evaluate candidates by slice-overlap SSD

For each candidate integer offset (i, j) in [−r, +r], SASpro computes SSD directly between slices of the two cached cropped gradients — no warps, no FFTs, no new gradients. Just two array views and a subtraction. This is dramatically faster than the typical “warp-then-correlate” approach that most planetary stackers use.

Hill climb or brute force

By default SASpro hill-climbs from (0, 0) through the 8 integer neighbours, moving to whichever lowers SSD most, until no improvement is found or 6 steps have been taken. This finds the global minimum when the SSD surface is smooth (the usual case). If SSD refine: brute force is ticked, the full (2r+1)² window is scanned instead, which is slower but robust to multiple local minima.

Subpixel polish

Once the best integer offset (i*, j*) is found, a separable quadratic fit at (i*±1, j*) and (i*, j*±1) gives subpixel offsets:

t = \frac{v_{-1} - v_{+1}}{2\,(v_{-1} - 2v_0 + v_{+1})}

clipped to [−0.75, +0.75]. No additional warps are needed — the four extra SSD evaluations are the same slice-overlap arithmetic as the integer search.

Confidence from the SSD surface

Confidence is derived from how sharp the minimum is relative to the surrounding surface:

c = \exp\!\left(-\mathrm{SSD}_{\min}\,/\,\lambda\right)

with scale λ = 0.002, clipped to [0, 1]. A single, very deep minimum gives high confidence; a flat surface or multiple comparable minima give low confidence, which gates downstream stages like field rotation.

14.8 Multi-scale AP combination

When AP scale = Multi-scale, each AP's shift is computed at three sizes: 2× the base AP size, 1× (the base), and ½×. Each produces its own (dx_k, dy_k, c_k). These are combined into a single AP shift via confidence-weighted average with a slight bias toward the larger scales (which are more stable):

w_k = \max(c_k,\,10^{-3})\,\alpha_k
\alpha_{2\times},\,\alpha_{1\times},\,\alpha_{0.5\times} = 1.25,\,1.00,\,0.85
\Delta x = \frac{\sum_k w_k\,\Delta x_k}{\sum_k w_k}

Combined confidence is the same weighted average over c_k. The 1.25/1.00/0.85 weighting is deliberate: large APs give more stable estimates but miss fine detail; small APs catch fine detail but can lock onto noise; the weighted combination gets you the best of both.

14.9 MAD outlier rejection

Not every AP produces a usable shift — some may land on featureless patches, saturated highlights, or just drift. SASpro rejects outlier APs via a Median Absolute Deviation (MAD) test, which is robust to the exact kind of extreme outliers that break mean/std-based rejection.

For each component (dx, dy), among APs with confidence > 0.15:

\mathrm{MAD}_x = \mathrm{median}(|x_i - \mathrm{median}(x)|)
z_i = \frac{|x_i - \mathrm{median}(x)|}{\mathrm{MAD}}

APs with z_x > 3.5 OR z_y > 3.5 are rejected. The surviving set drives the dense displacement field in §14.10.

14.10 Dense displacement field (inverse-distance interpolation)

After rejection, the surviving per-AP shifts need to be turned into a smooth per-pixel displacement field so the current frame can be warped into alignment with the reference. SASpro uses inverse-distance-weighted (IDW) interpolation on a coarse grid, then upsamples with cubic interpolation.

At each coarse-grid point p, the weight contributed by AP i at position p_i is:

w_i(p) = \frac{c_i}{\|p - p_i\|^q}

with power q = 2, confidence c_i, and an optional cutoff radius (3× AP size by default) beyond which an AP's weight is zero. The displacement at p is the weighted average:

\mathbf{d}(p) = \frac{\sum_i w_i(p)\,\mathbf{d}_i}{\sum_i w_i(p)}

The coarse grid is 32×32 (adjusted for aspect ratio); the result is upsampled to full resolution with cv2.resize(INTER_CUBIC). Warping is then cv2.remap with linear interpolation and replicate-border behaviour. This correctly handles differential seeing across the disc — one AP may say shift up-left, another may say shift down-right, and the field smoothly transitions between them without any discontinuity artefacts.

14.11 Field rotation search

With field rotation correction enabled, each frame's post-translation residual is checked for a small-angle rotation that would further reduce SSD against the reference. The search is two-pass with a hysteresis quality gate.

Pass 1 — coarse scan

Sweep angles from −max_deg to +max_deg in steps of coarse_step (default 2°), with a small hysteresis extension so the fine pass can refine against neighbours that fall outside the initial range. For each angle, apply cv2.getRotationMatrix2D + cv2.warpAffine about the rotation centre (planet centroid or surface anchor), compute the gradient, compute SSD against the cached cropped reference gradient. Pick the coarse minimum.

Pass 2 — fine scan

Sweep angles around the coarse minimum in steps of step_deg (default 0.5°). Keep the running minimum.

Centre crop for speed

Both reference and current are centre-cropped around the rotation centre to at most 640×640 pixels — this bounds the warpAffine cost without sacrificing accuracy (rotation signal is strongest near the centre anyway).

Hysteresis quality gate

A frame's rotation is only accepted if the SSD at the best angle is lower than both the SSD at best_angle − hysteresis_steps × step and best_angle + hysteresis_steps × step, by a minimum lift of 0.2% of the best SSD. If the hysteresis check fails, the rotation is set to 0° (no correction applied) and the frame's quality score is set to 0 so it falls out of the Keep set.

Subpixel polish

Same quadratic fit as the translation refinement, giving sub-degree angle precision.

14.12 Chromatic dispersion correction

Atmospheric refraction depends on wavelength, so at low altitudes a planetary disc shows R and B offset from G — the classic “chromatic fringe”. In Planetary mode with Debayer and dispersion correction both on, SASpro corrects this before the local AP warp.

For each frame (after global translation):

  1. Find the G-channel centroid independently.
  2. Find the R-channel centroid independently.
  3. Find the B-channel centroid independently.
  4. Compute the per-channel shift needed to align R and B centroids to G:
    \Delta_c = (c_G - c_c),\;\; c \in \{R,\,B\}
  5. Apply those shifts to the R and B planes of the frame; G is unchanged.

This runs at analysis time, so the correction is per-frame rather than a single global offset — which matters because dispersion varies with altitude over the course of a session.

14.13 Planet axial derotation

For long clips of Jupiter and similar fast rotators, the planet rotates measurably across the clip, smearing features in a naive stack. SASpro's derotation maps each frame to geographic coordinates on an oblate-spheroid model, shifts longitude by the known rotation rate times frame delta, and reprojects.

Given disk centre (cx, cy), radius r, pole position angle θ_p, and axis-tilt ratio b/a (semi-minor over semi-major), SASpro builds latitude/longitude grids for the current frame. The sub-observer latitude follows from the axis-tilt:

\varphi_{\mathrm{obs}} = \arccos(b/a)

For derotation, each pixel's longitude is shifted by:

\lambda'(x,y) = \lambda(x,y) + \omega\,\Delta t

with ω the rotation rate (deg/min) and Δt the frame delta from the reference. The shifted lat/lon is then back-projected to pixel coordinates with the same oblate-spheroid model, and the frame is resampled via cv2.remap. Everything outside the disc is left as-is (no derotation applied).

14.14 Drizzle

Drizzle (Fruchter & Hook 2002) upsamples each input frame onto a finer output grid and deposits each input pixel's value with a kernel-weighted contribution. For planetary data, SASpro uses a Gaussian kernel exclusively:

K(r) = \exp\!\left(-\frac{r^2}{2\,\sigma^2}\right)

with σ in output-pixel units. The output sigma is scaled by the drizzle factor to maintain consistent spatial smoothing:

\sigma_{\mathrm{out}} = \sigma_{\mathrm{in}}\,\cdot\,s

and defaults to max(10⁻³, pixfrac × 0.5) when the user's sigma is zero. Deposits accumulate into two shared buffers: a weighted data buffer D and a weight buffer W. On finalisation:

D(X,Y) = \frac{\sum_i w_i\,K(\|X - X_i\|)\,I_i}{\sum_i w_i\,K(\|X - X_i\|)}

Each input pixel's weight w_i is its frame's quality score — so better frames contribute more, exactly as in the non-drizzle weighted stack.

The implementation uses Numba JIT-compiled kernels (drizzle_deposit_kernel_mono_fast / drizzle_deposit_kernel_color_fast) shared with SASpro's Stacking Suite. A pre-allocated scratch tile is reused per frame to avoid allocation overhead at thousands-of-frames scale.

14.15 Quality-weighted stacking

Without drizzle, the warped frames are accumulated as:

S(x,y) = \frac{\sum_i w_i\,I_i(x,y)}{\sum_i w_i}

with w_i the half-pedestal-corrected quality score from §14.1. The accumulation happens serially on the main thread (while frames are warped in parallel), which avoids the overhead of per-pixel locks and keeps the memory footprint to one accumulator plus one weight scalar.

14.16 Flat-field calibration

If a master flat is loaded, every frame is divided by the flat before anything else:

I'(x,y) = \frac{I(x,y)}{F(x,y)}

The flat is loaded once at full sensor size (via load_flat_for_run), then cropped to the current ROI per get_frame call via view-slicing (no copy). For SER-format flats with multiple frames, they're combined to a single master using the chosen method (median by default — robust to cosmic rays and satellite streaks in the flat sequence; mean for very clean runs). The resulting master is normalised to per-channel mean 1.0 before use, so the frames come out at the same brightness scale, just with the sensor's spatial response removed.

Values are clipped to [0, 1] after division to guard against the occasional negative pixel from a very dim master flat.

Credits. The Planetary Stacker — the quality metric (variance of in-mask Laplacian plus mean-over-max gradient richness, log-compressed and half-pedestal corrected), the SSD-prepared-reference hill-climb with slice-overlap SSD evaluation (no warp per candidate), the reference-locked parallel-chunked coarse surface tracking with keyframe seeding, the multi-scale AP combination with 1.25/1.00/0.85 confidence weighting, MAD-based AP outlier rejection, inverse-distance dense displacement field, two-pass hysteresis-gated field-rotation search with centre-crop acceleration, per-channel chromatic dispersion correction, latitude-based longitude-shift planetary derotation, the Analyze↔Edit APs↔Blink Keepers↔Export Aligned SER↔Stack workflow, the interactive quality graph with click-to-set keep cutoff, the AP editor and the Blink Keepers dialog — is original work by Franklin Marek for SetiAstro Suite Pro. Standard components credited to community: drizzle resampling follows Fruchter & Hook (2002, PASP 114:144); phase correlation is Kuglin & Hines (1975); normalised cross-correlation template matching is classical pattern recognition; log-polar Fourier-Mellin rotation estimation follows De Castro & Morandi (1987); sigma-clipping is classical statistical outlier rejection; the MAD-based robust z-score follows the Rousseeuw & Croux convention; sum-of-squared-differences image registration and inverse-distance-weighted interpolation are textbook methods. OpenCV provides phase correlation (cv2.phaseCorrelate), template matching (cv2.matchTemplate), log-polar warp (cv2.warpPolar), affine warp (cv2.warpAffine), Sobel and Laplacian operators, Gaussian blur, resampling (cv2.resize, cv2.remap) and demosaic kernels. The drizzle deposit kernels are SASpro's own Numba JIT-compiled implementations (shared with the Stacking Suite). Astropy provides FITS I/O for flats; the SER reader is SASpro's implementation of the Lumenera SER v3 specification; FFmpeg (via SASpro's platform decoder) handles AVI/MP4/MOV/MKV frames. NumPy provides the array engine, PyQtGraph is not used here (the quality graph is custom QPainter-drawn), and PyQt6 provides the UI framework. The Planetary Stacker Viewer and Batch Planetary Stacker, which feed this engine, are documented separately.