Science-grade optical diagnostics for your telescope and sensor — grid-mosaic star inspection, physical FWHM² tilt paraboloid, focal-plane heatmaps, SIP distortion grid.
Image Peeker opens on the active document and shows a grid-mosaic preview: it samples a grid × grid array of equally-spaced panels across the full frame and tiles them in the preview with a configurable separation gap. This single view lets you look at corners, edges, and center at full native resolution without panning around a multi-megapixel frame — the fastest way to spot asymmetric star elongation that signals tilt or decentring.
From the top of the preview you can run one of three analyses (described in §4-§6), each of which pops a dedicated results dialog without closing the Peeker. Below the preview, a parameters panel controls both the mosaic layout and the physical constants (pixel size, focal length, aperture) that the analyses need in order to report real-world numbers.
The preview tiles a square grid of equally-spaced panels pulled from across the frame. Three controls shape it:
The AutoStretch button operates on the stored high-bit-depth source (not on the 8-bit display buffer), so when you toggle it on you see a correctly-stretched preview of the full linear data range, not a stretch of an already-quantized version. The underlying measurement path for the analyses runs on the linear float32 image regardless of the stretch state.
Three values let the analyses report real-world units. Each lives in the parameters panel and is remembered across sessions.
Pick Focal Plane Analysis from the Analysis dropdown and press Analyze. The analysis runs on a background thread (the status line reports progress) and produces three heatmap windows covering the whole frame:
Each window shows the heatmap under a colormap (viridis for FWHM, magma for ellipticity, hsv for orientation) with a vertical colorbar and a span readout showing the real min and max values on the frame. All three windows persist until you close them, so you can tile them for side-by-side inspection.
Pick Tilt Analysis and press Analyze. The tool fits a physical defocus model to the per-star FWHM across the frame and pops a results dialog showing the fitted sensor plane with:
If the paraboloid model turns out ill-conditioned on your frame (not enough stars, or the fit's quadratic coefficients come out negative, which is physically impossible for a real paraboloid), the dialog falls back to a linear FWHM-vs-position plane fit and tells you clearly in an orange note: "paraboloid model is ill-conditioned, showing linear fallback, consider a longer sub." You still get a tilt estimate; it's just less reliable than the paraboloid version.
Pick Astrometric Distortion Analysis and press Analyze. This analysis has three stages:
The math and algorithm choices behind every number the tool reports.
Every analysis starts with a star catalog. The tool runs SEP's background estimator once (that's the expensive step) and then re-extracts with progressively lower sigma thresholds — 100 → 50 → 25 → 12 → 6 → 3 — stopping as soon as the quality-filtered catalog has at least 1000 well-shaped stars.
The quality filter is strict: a detection is kept only if (a) SEP's blend/truncation/saturation flag is zero, (b) the pixel count is above a minimum, and (c) the shape ellipse is well-formed with major axis a > 0, minor axis b > 0, and b ≤ a. The surviving catalog is used across all surface fits so one SEP call powers the whole analysis.
The waterfall idea is a performance choice. On a rich field, SEP at 3σ can produce 30,000+ detections, most of which fail the shape quality filter anyway (they're noise, hot pixels, or faint stars whose ellipse measurements are too uncertain to constrain a smooth surface). Starting high and descending stops as soon as there are enough high-SNR stars to constrain the fit, which is usually after 2-3 extractions on a well-populated frame.
Each detection's shape is characterized by its second moments: semi-major axis a, semi-minor axis b, and orientation θ, all in pixels. The physically-correct Gaussian FWHM in microns is:
where ps is the pixel size in µm. The numerical factor evaluates to:
The geometric mean √(a·b) is used instead of just a because an elongated star's FWHM in the equivalent circular-gaussian sense is set by the product of its axes, not the longer one alone. The older convention of FWHM ≈ 2·a that appears in some astronomical tools systematically underestimates the physical FWHM for round stars (which have a = b) by a factor of 1.18, and distorts the comparison between round and elongated stars. The tool uses the correct Gaussian conversion everywhere.
The reported ellipticity is the PixInsight / SExtractor convention:
which is zero for a circular star and approaches 1 for extremely elongated ones. The alternative definition e = √(1 - b²/a²) (astronomical eccentricity) produces larger numbers for the same shape and is not what this tool reports; "0.1" from this tool means the star's minor axis is 90% of the major axis, which is a mild elongation.
Fitting a smooth orientation surface θ(x,y) directly from per-star θ values runs into the ambiguity that θ and θ + π describe the same ellipse — a star elongated east-west is the same shape whether you call its angle 0 or π. Averaging raw angles across stars near that wraparound would produce nonsense (two stars at angles slightly above and below π average to π/2, pointing north-south instead).
The fix is to fit the double-angle sine and cosine as independent smooth surfaces (both continuous across the wraparound because sin(2θ) and sin(2θ + 2π) are identical), then recover the orientation via atan2:
The resulting θ wraps correctly over [-π/2, π/2], which maps directly to a hue over [0, 1] for display. The hsv colormap used for the orientation map is cyclic, so stars whose fitted angle lies near either edge of the range appear in similar colors, preserving the natural wraparound.
The core improvement in the current tilt pipeline is the move from an older "fit a plane to blur" approach to a physical paraboloid model. Here's why the old approach was broken and the new one isn't.
The physics. For a sensor tilted relative to the focal plane, the distance-from-best-focus at pixel (x,y) is a plane:
where p and q are the tilt slopes in µm per pixel and r is the overall defocus at the frame origin. The defocus blur caused by this at f-number N is |dz|/N, so the measured FWHM is the quadrature sum of a baseline (seeing + residual optics) FWHM₀ and the geometric defocus blur:
where:
is your focal ratio. This is a true paraboloid in (x, y): a bowl whose minimum lies along the line px + qy + r = 0 (the "focus ridge") and that rises symmetrically on either side.
Why the old plane-fit-to-blur was broken. The old pipeline treated the measured FWHM itself as approximately linear in position and fit a plane. The problem: blur depends on |dz| not dz, so on a tilted sensor where the focus ridge crosses the frame, one side has positive dz and the other negative, but both sides have positive blur. The plane-fit then has to compromise between the positive-blur-on-the-left and positive-blur-on-the-right and collapses toward zero — reporting almost no tilt even when the real tilt is substantial. The old approach only recovered tilt correctly for the extreme case where the whole frame was defocused with the same sign (focus ridge off the sensor).
The paraboloid fit. The new pipeline expands the paraboloid as a general 2D quadratic in (x, y):
and fits the six coefficients by iterated-sigma-clipped linear least squares on z = FWHM² treated as the dependent variable. Because dz enters squared, this is a linear problem in the cij even though the underlying model is quadratic in position.
Once the coefficients are known, the physical defocus parameters come back directly:
The sign of q is pinned by the cross-term c11 (negative c11 means p and q have opposite signs). The constant r comes out of the linear terms c10 or c01, and the baseline FWHM is extracted from the constant c00:
Sign ambiguity. The paraboloid is symmetric under the sign flip (p, q, r) → (-p, -q, -r) — physically, you cannot distinguish "sensor tipped so top-left is high" from "sensor tipped so top-left is low" from the blur pattern alone. By convention the tool pins r ≥ 0, so corner Δ µm values are reported consistently relative to the center but there's a global sign flip that only through-focus data can resolve.
Well-defined vs ill-conditioned. A valid paraboloid has both c20 > 0 and c02 > 0 (FWHM² is bounded below and grows away from best focus). When the fit returns negative values — because of noise, insufficient stars, or a frame that just doesn't have a clean paraboloidal pattern — the model is "ill-conditioned" and the tool falls back to a linear FWHM-vs-position plane fit, flagging the fallback in the dialog with an orange note so the user knows the number is less reliable.
Once (p, q, r) are known, the defocus at any pixel relative to the center (x₀, y₀) = (W/2, H/2) is:
Evaluated at each of the four corners, these are the numbers shown in the corner Δ µm table. The peak-to-peak span (max minus min) is the single-number tilt magnitude.
The focal-plane analysis needs smooth surfaces for FWHM, ellipticity, and sin/cos(2θ) over the full image, from scattered per-star measurements. The tool fits a 2D polynomial of degree d:
by iterated 3σ-clipped linear least squares. The default degree is 3 for FWHM and ellipticity (enough shape to capture bowl, saddle, and radial-dominant patterns) and 1-2 for orientation (which varies smoothly across the frame for pure tilt or decentre).
The number of free parameters is (d+1)(d+2)/2 — 10 for degree 3 — so with 1000 well-shaped stars the fit is heavily overdetermined and robust against outliers. If a frame produces fewer than that many detections, the tool automatically reduces the fit degree so the polynomial remains well-posed.
Fast rendering. Full-resolution evaluation of a degree-3 polynomial on a multi-megapixel grid is wasted work: the fitted surface is smooth by construction, so values an integer pixel apart are nearly identical. The tool evaluates the polynomial on a coarse 128×128 grid and bilinearly upsamples to the full (H, W) size with scipy's ndimage.zoom. The result is visually identical to a full-resolution evaluation and roughly 1000× faster on a 24-megapixel frame — the difference between an interactive tool and a two-minute wait.
The Simple Imaging Polynomial (SIP) convention stores astrometric distortion as two forward polynomials A and B, defined so that the distortion correction in pixels at image pixel (x, y) is:
where o is the polynomial order (typically 2-5) and (u, v) are pixel offsets from the SIP reference pixel CRPIX. The distortion grid dialog evaluates (Δx, Δy) at each node of a 10×10 line grid, amplifies by the user's slider setting for visibility, and draws the warped grid lines; the per-star residuals shown in the histogram are |Δx, Δy| converted to arcseconds using the WCS pixel scale.
The pixel scale is extracted from the solved WCS by taking the determinant of the CD matrix:
which gives arcseconds per pixel directly. If the WCS uses the modern PC+CDELT representation instead, the CD matrix is reconstructed as CD = PC × diag(CDELT) first. If no WCS exists at all, the tool falls back to the user-supplied pixel size and focal length via s = 206.265 × (ps / fmm).
A WCS without A_i_j / B_i_j terms describes only the linear astrometric transformation (position, scale, rotation) and says nothing about optical distortion. The tool detects this case and tells you to re-solve with distortion fitting enabled in ASTAP rather than silently returning a flat grid.
Focal Plane and Tilt analyses both run in a background QThread (_AnalysisWorker) that posts progress updates and results back to the GUI thread via signals. SEP's extract call plus the paraboloid or polynomial fit takes a few seconds on a typical multi-megapixel frame; running it on the GUI thread would freeze the whole app during that time. The background worker emits the "Detecting stars..." status, then the result dictionary (or None on failure, with a specific message in the error signal). The GUI thread picks up the result and opens the result dialogs.
The Astrometric Distortion analysis is not threaded because it may need to open a modal plate-solve dialog and mutate the document's metadata, both of which require main-thread execution. On a frame that already has a SIP solve, it's effectively instantaneous.
stretch_mono_image / stretch_color_image pipeline. Surface rendering uses scipy's ndimage.zoom for bilinear upsampling.