Star Registration

Batch-register a stack of frames to a reference, write aligned FITS files to disk, and produce an alignment_transforms.sasd sidecar — the standalone counterpart to Stacking Suite's registration stage.

What this tool is for. Where Stellar Alignment aligns two images interactively with a preview and a dialog, Star Registration is the file-based batch pipeline: pick a reference, drop in a folder of frames, point at an output directory, press Start. Every input frame comes out as a *_n_r.fit file on the reference's pixel grid, plus a single alignment_transforms.sasd sidecar that records every frame's transform for later drizzle integration. This is the same engine Stacking Suite uses — just exposed as a standalone step for the cases where you want aligned frames without the integration (feeding another tool, inspecting individual frames post-align, or splitting registration and integration across different sessions).

1. Picking a reference

The reference image defines the pixel grid every input frame will be warped onto. Two source modes:

A reference needs to be either plate-solved (if you'll later use the .sasd file with WCS-aware tools downstream) or at minimum star-rich — the solver matches input frames against it, so it needs enough detectable stars to form robust triangle matches.

2. Adding frames to align

Two ways to populate the file list:

Select Files — the standard file picker, multi-select. Pick 2, 20, or 200 frames; they all land in the tree.
Select Directory — point at a folder; every supported image file in it is added (FITS, XISF, TIFF, PNG, JPEG). No recursion into subfolders.

Both actions deduplicate against what's already in the list, so running Select Files twice on overlapping sets doesn't produce duplicates. Supported extensions: .fits, .fit, .xisf, .tif, .tiff, .png, .jpg, .jpeg.

3. Managing the file list

Files appear in a tree below the file-selection buttons, one row per file (basename only — hover for the full path). Standard tree selection behavior: click to select, Ctrl+click to multi-select, Shift+click for ranges.

Remove Selected — drops the highlighted rows from the list.
Clear All — empties the list entirely.

4. Output directory

Select Output Folder opens a directory picker. All aligned frames and the .sasd sidecar land here. If the folder doesn't exist yet, create it first in your file manager — the dialog doesn't offer a "create new" option.

Pick a dedicated folder, not your source folder. Aligned frames use the suffix _n_r.fit: M31_light_0001.fit → M31_light_0001_n_r.fit. These end in .fit regardless of input format, so a JPEG input becomes a FITS output. Writing them into the source folder mixes originals and aligned versions, which Stacking Suite and other downstream tools will then try to re-register in a subsequent run.

5. Starting registration

Start Registration kicks off the pipeline. The status label updates in real time with per-pass progress, per-frame results, and running counts. The status format tracks across three phases:

  1. Refinement passes — Refinement Pass 1/3…, Astroalign delta for M31_light_0012.fit: dx=1.42, dy=0.87, and so on. Up to three passes by default; stops early on convergence.
  2. Pass summary — Pass 1 delta shifts: [12.4, 8.9, 11.2, ...] and Within tolerance (≤ 0.20px): 42 frame(s). Frames already within tolerance are skipped in subsequent passes.
  3. Finalize — Finalizing aligned outputs with 8 processes…, then per-frame Wrote M31_light_0001_n_r.fit [affine]. This is the single full-resolution read + warp + write per frame.

At the end, a summary box reports Valid frames: X/Y — how many frames produced output versus how many were in the input list. Frames whose alignment failed entirely (astroalign couldn't find enough triangles even after backoff) are reported in-log and skipped rather than written as broken outputs.

6. Reading the status log for problems

Common status-line patterns that mean something's wrong:

Status messageWhat it meansWhat to do
Astroalign failed for X: List of matching triangles exhaustedEven the strictest detection pass couldn't find enough shared triangles between the frame and the reference.Check whether the frame actually overlaps the reference. Common on frames that drifted far during the session or on panels from a different mosaic region.
Astroalign failed for X – skipping (no transform returned)Detection worked but RANSAC couldn't find a consistent transform.Usually indicates a frame with heavy cloud contamination, a satellite trail covering the brightest stars, or a defocused frame.
low coverage src=0.37, ref=0.82 for crop solveControl points on the source cover only 37% of the frame — the solution may be geometrically biased.Usually a warning, not a failure. If downstream stacking shows one-sided bleed or rotation inconsistency, this frame may be the culprit.
Within tolerance (≤ 0.20px): N frame(s)N frames converged in this pass and will be skipped by the next pass.Nothing to do — healthy progress indicator. Convergence after pass 1 is normal for well-tracked sessions.

7. Output files

Two kinds of output land in the chosen directory:

Each FITS output also carries SASDZ* header keywords — a self-description stamp that lets drizzle integration reconstruct the frame's transform even without the .sasd file.

8. Satellite trails and no-data pixels

If your frames went through calibration with satellite trail rejection enabled, trail pixels are baked as NaN in the calibrated frames. Star Registration preserves this: NaN pixels bloom through the Lanczos warp (naturally covering the ring halo around the original trail), and the final file retains them as no-data markers. Integration downstream drops NaN pixels per-lane via isfinite(), so trails don't contaminate the stack.

The star detector handles trail NaN by folding non-finite pixels into the SEP ignore mask on a detection-only copy. The saved frame keeps its NaN markers; only the detection pass sees a filled copy for background estimation.

9. Workflow recipes

Split registration from integration. You want to try two different integration methods (sigma-kappa vs winsorized clip, say) on the same registered stack. Run Star Registration once to produce the aligned frames + .sasd; then run Stacking Suite with "Skip Registration and Integrate" twice, once per method. Each integration reads the same aligned frames with no re-registration cost.
Session-at-a-time pipeline. You image the same target over multiple nights and want each session's frames aligned to a master reference. Pick the master as reference, add just tonight's frames, run. Each night's aligned output accumulates in the same output folder, and the .sasd is overwritten each run — so if you want the per-session .sasd preserved, rename it (alignment_night3.sasd) before the next session.
Pre-aligned frames for a different tool. You want to feed registered frames to a tool that doesn't integrate itself — a star removal step, a per-frame analysis script, a QA inspection pass. Star Registration produces the aligned FITS without integration. The outputs are plain float32 FITS files that any FITS-aware tool can read directly.
Verifying alignment before a long stack. You're about to kick off a big integration and want to confirm registration quality first. Run Star Registration on the frames with a tight shift_tolerance expectation. Watch the per-pass delta shifts in the log: if pass 1 shifts are < 2 px and pass 2 shows most frames converged (within 0.2 px), the registration is healthy. If pass 2 still shows large deltas on many frames, something's wrong (too few stars, dramatically different FOVs, a bad reference choice).

10. Shared engine with Stacking Suite

The internals here are literally the same code Stacking Suite uses — see the Stellar Alignment documentation for the full algorithmic treatment of star detection (SEP with hot-pixel rejection on FWHM, ellipticity, and pixel count), uniform-grid control point allocation (coverage and spread gates), triangle-invariant matching via astroalign, the center-crop solve with coordinate lift, the downsample-and-lift affine math, and the anti-ring Lanczos+bilinear warp.

The relevant preferences live under stacking/align/* in SASpro's settings and are shared across every alignment-using tool: the detection threshold, max control points, downsample factor, RANSAC reprojection threshold, hot-pixel FWHM/ellipticity limits, and finalize worker count. Tune them in one tool, they apply in all of them.

11. Technical details

The batch pipeline's structure, multi-pass refinement, progress accounting, and finalize phase.

11.1 Pipeline structure

Registration runs in two phases: the refinement loop and the finalize pass. The refinement loop iterates on downsampled grids to compute accurate transforms; the finalize pass performs the single full-resolution read + warp + save per frame. Separating them matters because the refinement loop may touch each frame several times (reading full-resolution each time would be wasteful) while the finalize pass must touch each frame exactly once (and the full-res warp is the expensive operation).

11.2 Multi-pass refinement model

Each refinement pass solves for the delta transform between the currently-warped frame and the reference. The accumulated transform is updated by matrix composition:

T_i^{(k)} = T_{\mathrm{delta},i}^{(k)} \cdot T_i^{(k-1)}

Pass 0 starts from identity, so the first delta is also the initial transform. Each subsequent pass warps the original frame with the accumulated transform, solves for whatever residual drift remains, and composes it in. The scalar delta is the translation magnitude:

\|\Delta\mathbf{t}_i\| = \sqrt{(\Delta t_x)^2 + (\Delta t_y)^2}

Convergence is declared frame-by-frame when the delta falls below shift_tolerance (default 0.2 px). Converged frames are added to a skip set and excluded from subsequent passes:

The default maximum is 3 passes. For well-tracked sessions, most frames converge after pass 1; pass 2 handles frames with mild residual drift; pass 3 is rare and usually catches a few outliers with large initial offsets.

11.3 Progress accounting

The progress bar reports unit progress rather than wall-clock percentage. Total units for a run of N frames with P passes:

N_{\mathrm{total}} = N_{\mathrm{frames}} \cdot P_{\mathrm{passes}} + N_{\mathrm{finalize}}

which simplifies to:

\text{total units} = N \cdot P + N

Each refinement worker completion = 1 unit; each finalize worker completion = 1 unit. Convergence shortens the actual time by skipping already-converged frames in later passes, but the unit count stays fixed — "100% complete" means every frame has been attempted in every pass plus finalized once. Progress updates fire every 500ms via a Qt timer to avoid flooding the UI with per-frame updates.

11.4 Why refinement reads original frames

A subtle but important design choice: the refinement pass always reads the original input frame, not the previous pass's intermediate. The accumulated transform is applied to the original each time, giving the solver the pre-warped frame to work against. This has two benefits: no intermediate files are written during refinement (saving I/O and disk space), and no cumulative resampling error builds up across passes (each pass sees the full-resolution source pixels, not a progressively-degraded warp of a warp of a warp).

The finalize pass is where disk writes happen — exactly once per frame, with the final accumulated transform applied at full resolution via the anti-ring warp.

11.5 Finalize parallelism

The finalize pass uses a smaller worker pool than refinement because each worker holds a full-resolution frame plus warp buffers in RAM. To keep CPU cores busy without multiplying memory, each worker allocates multiple OpenCV threads for its warp:

\text{threads}_{\mathrm{cv2}} = \lfloor N_{\mathrm{cores}} / N_{\mathrm{workers}} \rfloor

Default is 8 workers × 1 cv2 thread on 8-core machines, or 4 workers × 2 cv2 threads on 8-core machines when finalize_workers is tuned down for memory reasons. The split is auto-balanced via the SASPRO_FINALIZE_CV2_THREADS environment variable passed to each worker process.

11.6 Process pool vs thread pool fallback

The default executor is ProcessPoolExecutor with spawn-mode contexts (matches both frozen builds and Qt safety requirements). On frozen (PyInstaller) builds, freeze_support() at the top of the entry point catches child re-execs so spawned workers don't launch new SASpro windows.

When process pool initialization fails — common on mixed-environment Windows installations where child workers die on NumPy/SciPy import with BrokenProcessPool — the engine falls back to a ThreadPoolExecutor. The thread pool serializes astroalign calls through the global _AA_LOCK (SEP is not thread-safe at the C level), so parallelism is limited to the warp phase, but registration still completes rather than failing hard.

11.7 Cancellation

The pipeline polls a cancellation event between frame boundaries. In-flight worker calls finish naturally; new jobs are not dispatched after the event fires. This gives clean cancellation without corrupting partially-written output files or leaving zombie worker processes. User-visible cancellation latency is bounded by the longest in-flight frame's solve time, typically 1-3 seconds.

11.8 The alignment_transforms.sasd format

A plain-text file in a line-oriented format, UTF-8:

REF_SHAPE: 4096, 6144 REF_PATH: /path/to/reference.fit MODEL: mixed FILE: /abs/path/to/frame_0001_n_r.fit KIND: affine MATRIX: 0.999987, -0.004521, 12.345 0.004521, 0.999987, -8.763 FILE: /abs/path/to/frame_0002_n_r.fit KIND: homography MATRIX: 0.9998, -0.0045, 11.2 0.0045, 0.9998, -8.1 0.0000012, -0.0000008, 1.0

Fields:

Numbers are formatted via {:.16g} for maximum precision without trailing zeros. Polynomial frames fall back to an affine written as # fallback affine from refinement comment plus the 2×3 matrix, so downstream tools that only understand affine can still process poly-registered stacks (less accurately but functionally).

11.9 FITS header SASDZ stamp

Every aligned FITS carries header keywords that let drizzle integration reconstruct the frame's transform without needing the .sasd file:

This stamp means a drizzle integration can be launched from a folder of aligned FITS files even if the .sasd has been deleted or overwritten by a later run — the drizzle integrator reads the stamp from each file's own header and reconstructs the per-frame geometry. Polynomial-registered frames carry only the kind and reference dimensions (their transforms are dense maps, not small matrices), and drizzle treats them as already-warped — deposited on an identity canvas.

Credits. SASpro Star Registration — the two-phase refinement/finalize split, the pass-skip convergence model with frame-level composition, the progress unit accounting, the .sasd sidecar format, the FITS SASDZ* self-description stamp, and the process-vs-thread executor fallback — is original work by Franklin Marek for SetiAstro Suite Pro. The underlying alignment math (star detection, triangle matching, RANSAC, warp) is the shared engine documented in Stellar Alignment; see that document for citations to astroalign, SEP, OpenCV, scikit-image, scipy, astropy, and the SIP convention.