Star Field Pattern Match (StarFieldFromCatalogNav)

Overview

StarFieldFromCatalogNav is the pass-1 multi-star pattern matcher. It hashes catalog and detection triplets into a translation- and rotation-invariant feature space, finds correspondences via a KD-tree match in that space, and runs a RANSAC similarity-transform fit to recover the translation (plus optional in-plane rotation) that maps the predicted catalog cohort onto the observed detections. The technique runs without a prior — it is the ensemble’s primary star path on scenes where the SPICE pointing error is too large for StarUniqueMatchNav’s search window to contain the brightest predictable star.

Feasibility passes when the predictable-star cohort has at least three usable STAR features (the matcher cannot form a single triplet below that count); feasibility fails otherwise.

Theory

The technique solves the global star-pattern-matching problem from scratch — without a prior offset — by reducing translation- and rotation-invariant pattern matching to a high-dimensional nearest-neighbour search.

Triplet hashing

For every triplet of three predictable catalog stars the matcher computes a translation- and rotation-invariant hash: the two ratios of side lengths (sorted longest-to-shortest), plus the included angle at the longest-side vertex. The same hash is computed for every triplet of three detected sources in the image. Triplets with matching hashes (within a small tolerance) are candidate correspondences.

Source detection

Detections come from a matched-filter search over the extfov image: the image is correlated with a Gaussian PSF kernel of the configured psf_sigma_px, peaks above detection_sigma * image_noise_sigma are kept, and a brightness-weighted moment around each peak gives the sub-pixel centroid. Up to max_sources brightest detections feed the hash-space search; the catalog side is similarly capped.

KD-tree match

The catalog and detection triplet hashes are loaded into a KD-tree in (ratio_short_to_long, ratio_mid_to_long, angle_radians) space, weighted per axis (hash_ratio_weight, hash_angle_weight); a query of every catalog hash against the detection KD-tree returns the nearest detection triplet within hash_match_tolerance. Each match implies a per-star correspondence between three catalog vertices and three detection vertices.

RANSAC inlier validation

Each candidate correspondence proposes a similarity transform (rotation, translation, and an implied unit scale). The matcher applies the proposed transform to every catalog star and counts how many predicted positions land within inlier_tolerance_px of a detection. The transform with the most inliers wins; below pattern_match_min_inliers the technique reports spurious.

PSF-fit inlier refinement

The detection centroid that pins each source is a brightness-weighted moment: unbiased, but only noise-limited, so on a faint field its per-star scatter dominates the field offset. Once the inlier correspondences are fixed, each matched inlier is re-centroided to drive that scatter down. Two estimators are available per star and they trade off with brightness:

  • The brightness-weighted moment is unbiased; its error falls roughly as \(1/\mathrm{SNR}\) as the star brightens.

  • A maximum-likelihood PSF fit (obs.star_psf().find_position against the instrument’s modelled point-spread function) reaches the minimum variance and so wins decisively when the star is faint. An undersampled PSF, however, carries a fixed sub-pixel-phase bias floor (~0.08 px for the COISS NAC star PSF, sigma ~0.54 px) that does not improve with brightness.

The two error curves cross near an integrated SNR of ~30 at the field level (the per-star crossover is a little higher, ~40, but the field fit averages the moment’s noise down faster than the PSF fit’s partly-correlated bias). The technique therefore refines a matched inlier with the PSF fit only while its box SNR is below the configurable ceiling psf_refine_snr_max (default 30), and keeps the moment above it. The box SNR is \(\sum (\text{box} - \text{median}) / \sqrt{\text{signal} + n_{\text{pix}}\,\sigma^{2}}\) over the fit box. The PSF fit reports its position in the eval_rect convention (offset measured from a pixel’s lower edge), so the technique subtracts the half-pixel to land in the pixel-centre convention shared by the detection moment and the catalog prediction. Any inlier whose fit fails (too close to the image edge, too few good pixels, no convergence) silently falls back to its moment centroid. The whole step is gated by psf_refine_enabled and the obs supplying a star_psf(); without either it is a no-op and the moment centroids stand.

The observation that drives this is brightness-dependent: a dim field (vmag 3-4) improves its recovered-offset median from ~0.052 px (moment-only) to ~0.023 px, while a bright field (vmag 0-0.8) recovers to ~0.005 px on the moment alone – where forcing the PSF fit would instead raise the error to ~0.056 px by exposing the bias floor. See the simulator performance report’s star-field centroiding section for the dim-vs-bright sweep.

Similarity-transform refit

Once the inlier set is known, the matcher runs a weighted Procrustes / Kabsch fit on the inlier correspondences (using the per-star inverse-trace covariance weights) to refine the similarity transform. The translation read off the fit is the reported offset; the rotation, when present, is the converged angle about the catalog-side weighted centroid.

Per-axis covariance

The reported translation covariance is derived from the inlier residual scatter and the catalog-side centroid spread, per the same precision-weighted-mean form StarRefineNav uses; see Star Refinement (StarRefineNav) for the algebra. When the per-instrument camera-rotation flag is on and the inlier count supports it, a 3x3 covariance with the rotation diagonal is reported; otherwise the 2x2 translation block is reported on its own or embedded in the rank-deficient 3x3.

Restrictions and assumptions

  • The matcher needs at least three usable stars to form one triplet. Below that count the technique reports infeasible without running.

  • The hash-space tolerance is empirically tuned to a typical centroid sigma of ~0.1 px; significantly noisier centroids may cause the KD-tree match to miss correct correspondences.

  • The matcher is translation- and rotation-invariant by construction but not scale-invariant; a Procrustes refit explicitly forces unit scale, which is correct for navigation but means the fit will not produce a useful result when the per-instrument plate scale is wrong.

  • Triplet hashing scales as \(O(M^{3})\) per side where \(M\) is the per-side cap (max_sources). At the default max_sources = 30 the cost is ~27,000 triplets per side.

Sources of uncertainty

The reported covariance is the precision-weighted-mean form derived from inlier residual scatter; it does not capture systematic biases (a per-star centroid offset that affects every detection equally moves the global solution by the same offset and is invisible to the inlier scatter), and it does not capture catalog-side errors (a star with a wrong predicted position bumps the inlier count by zero or one and the bulk of the fit is unaffected). When the converged offset sits within the at-edge tolerance of any axis bound, or when the rotation parameter is at the configured fraction of its cap, the result is flagged at_edge and the hard-zero gate forces confidence to zero. When the inlier count falls below pattern_match_min_inliers the result is flagged spurious.

Configuration

All numeric tunables for this technique live in techniques.StarFieldFromCatalogNav.tuning in src/nav/config_files/config_510_techniques.yaml.

  • max_sources — int, default 30 (count). Maximum number of brightest detected sources / brightest catalog stars per side feeding the matcher. Triplet count is \(O(M^{3})\), so 30 keeps the candidate list bounded at ~27,000 triplets per side.

  • detection_sigma — float, default 4.0 (dimensionless). Detection-threshold multiplier on the per-image noise sigma; matched-filter peaks above this threshold are candidate detections.

  • psf_sigma_px — float, default 1.0 px. Gaussian PSF sigma for the matched-filter kernel. Matches the typical ungroomed star PSF; per-instrument overrides may tighten or loosen this once the integration library exercises the full instrument set.

  • centroid_box_half_px — int, default 3 px. Half-width of the brightness-weighted moment box around each accepted peak.

  • hash_match_tolerance — float, default 0.05 (dimensionless). KD-tree match radius in the (ratio, ratio, angle_rad) hash space; weighted Euclidean. Empirical default for centroid sigma ~0.1 px against typical triplet scales.

  • hash_ratio_weight — float, default 1.0 (dimensionless). Per-axis weight on the ratio dimensions in the hash distance metric.

  • hash_angle_weight — float, default 1.0 (dimensionless). Per-axis weight on the angle dimension in the hash distance metric.

  • inlier_tolerance_px — float, default 2.0 px. Maximum residual for a detection-to-catalog correspondence to count as an inlier under a candidate similarity transform.

  • pattern_match_min_inliers — int, default 6 (count). Minimum inlier count for the matcher to accept a transform; below this the technique returns spurious. Must be at least 3 (the matcher needs at least one triplet per side).

  • at_edge_tolerance_px — float, default 1.0 px. A converged offset whose absolute distance from any search-window axis bound falls within this tolerance is flagged at_edge.

  • rotation_at_edge_fraction — float, default 0.95 (dimensionless). When fit_camera_rotation is true, the converged rotation magnitude trips at_edge once it crosses this fraction of the per-image max_rotation_deg cap.

  • psf_refine_enabled — int flag, default 1. 1 enables the PSF-fit re-centroiding of matched inliers; 0 keeps the brightness-weighted moment centroid everywhere.

  • psf_refine_box_px — int, default 11 px (odd). Square box side for the PSF fit and the integrated-SNR estimate around each inlier.

  • psf_refine_search_limit_px — float, default 2.0 px. Maximum search distance from the moment centroid for the PSF fit; the moment is already within a pixel of truth.

  • psf_refine_snr_max — float, default 30.0 (dimensionless). Integrated-SNR ceiling for the moment/PSF crossover: an inlier whose box SNR exceeds this keeps its moment centroid (its noise has already fallen below the PSF fit’s sub-pixel-phase bias floor); fainter inliers are refined with the PSF fit. The default is tuned to the field-level crossover on a nominal background; elevated read noise or a stray-light gradient pulls the optimum down toward ~16-21 (see the simulator performance report).

Per-instrument overrides

Per-instrument YAML files in src/nav/config_files/config_4N0_inst_*.yaml do not override any of these knobs.

Confidence formula

The technique reports a calibrated confidence in \([0, 1]\) produced by the shared sigmoid combination; see Confidence Calibration (Shared Sigmoid-of-Linear Combination). Spec is techniques.StarFieldFromCatalogNav; consumes attributes off StarFieldDiagnostics plus at_edge and spurious.

  • n_inliers — alpha = 1.0, offset = 6.0, divisor = 6.0, cap at 1.0. Number of detection-to-catalog inliers after RANSAC. Saturates at 12 inliers (offset = 6 plus full cap).

  • median_residual_px — alpha = -1.0, offset = 0.0, divisor = 1.0, no cap. Median position residual on inliers. Larger residuals pull confidence down.

  • n_detected_sources — alpha = 0.0, offset = 0.0, divisor = 30.0, cap at 1.0. Number of bright sources detected in the image. Carries no weight in the current confidence formula; the wiring is in place so a downstream recalibration can tune the alpha.

  • n_catalog_predicted — alpha = 0.0, offset = 0.0, divisor = 30.0, cap at 1.0. Number of catalog stars in the extfov. Same posture as the detected-sources term.

Hard-zero gate: at_edge and spurious either firing forces confidence to zero before the sigmoid evaluates. The constant baseline is \(\alpha_{0} = -2.0\). No post-sigmoid hard_cap is applied.

Implementation

Source files:

Public class StarFieldFromCatalogNav, base NavTechnique. Self-registers via __init_subclass__.

Class attributes:

Public methods (autodocumented at nav.nav_technique): is_feasible() and navigate().

Diagnostics

StarFieldDiagnostics:

  • n_inliers — number of detection-to-catalog inliers. Consumed by the confidence formula and the spurious- detection gate.

  • median_residual_px — median position residual on inliers. Consumed by the confidence formula.

  • n_detected_sources — number of bright sources detected in the image.

  • n_catalog_predicted — number of catalog stars in the extfov.

  • n_triplets_evaluated — number of triplet candidates considered by RANSAC.

Call path

Call path traced through navigate():

  1. Open a logged section. Filter the offered features down to usable_stars (predictable and not occluded by a body silhouette or ring annulus) and pull the predicted catalog positions / SNR off the per-feature geometry and flags.

  2. Run the matched-filter detection over image_ext to find the brightest sources. Cap the catalog and detection cohorts at max_sources each.

  3. Compute every catalog and detection triplet’s (ratio, ratio, angle) hash. Load both sets into a KD-tree and find correspondences within hash_match_tolerance.

  4. RANSAC: for each candidate correspondence triplet, propose a similarity transform and count inlier matches across the full catalog cohort using inlier_tolerance_px. Keep the transform with the most inliers.

    • Below min_inliers. Return a spurious zero-confidence result with the diagnostic fields populated for the JSON sidecar.

  5. Refine the inlier detection positions: for each matched inlier below the psf_refine_snr_max integrated-SNR ceiling, replace its moment centroid with a maximum-likelihood PSF fit (obs.star_psf().find_position, half-pixel-corrected); brighter inliers and failed fits keep the moment. Skipped entirely when psf_refine_enabled is 0 or the obs exposes no star_psf().

  6. Run a weighted Procrustes / Kabsch fit on the (refined) inlier correspondences via similarity_transform_fit. The fit returns the rotation and translation; the translation is the reported offset.

  7. Result-shape branches on fit_camera_rotation and the inlier count:

    • Translation only (fit_camera_rotation false). The (2, 2) covariance comes from the inlier residual scatter scaled by the catalog-side centroid spread. rotation_rad and sigma_rotation_rad are None.

    • Rotation fit, two or more inliers. The (3, 3) covariance has the per-axis translation variances and the rotation variance derived from the inlier residual scatter against the catalog-side spread (same algebra as StarRefineNav documented at Star Refinement (StarRefineNav)).

    • Rotation fit, one inlier. The (2, 2) translation block is embedded in a rank-deficient (3, 3) via embed_rotation_unobservable(); rotation_rad is 0.0 and the sigma is the rotation-unobservable sentinel.

  8. Apply the at-edge tests against the search-window axis bounds and the rotation cap.

  9. Build a StarFieldDiagnostics, evaluate the confidence spec via evaluate_sigmoid_combination(), log the breakdown via log_confidence_breakdown(), and assemble the NavTechniqueResult.

The feature_ids field preserves every consumed feature_id so the orchestrator’s curator can attribute each per-star contribution at audit time.

Examples

star_dominated (Cassini ISS WAC, image W1580760393_1)

Dense star field with no body in FOV. The stars model emits one STAR feature per predictable catalog star; StarFieldFromCatalogNav runs the triplet hash against the detected sources, lands on a similarity transform with several inliers, and reports a translation against the operator-verified offset \((\Delta v, \Delta u) = (-2.68, -3.68)\) px. The pass-2 StarRefineNav consumes this prior and polishes the offset using the full predictable cohort; see Star Refinement (StarRefineNav) for that walk-through.

stars_plus_body (Cassini long-exposure background-stars scene class)

One body and at least three usable catalog stars in the same FOV. The stars model emits one STAR feature per predictable catalog star whose predicted position lies outside the body silhouette and any ring annulus; the body model emits a LIMB_ARC (or BODY_BLOB) for the body. On pass 1, the BodyLimbNav consumes the body’s feature first and the orchestrator’s ensemble combine populates the per-image prior from the limb-derived offset. StarFieldFromCatalogNav runs in parallel on the star cohort: the triplet hash matches the predicted catalog stars against detected sources, the RANSAC inlier set lands on the same translation as the body fit, and the ensemble combine tightens the per-image covariance. On pass 2 the StarRefineNav consumes the cohort and polishes the offset further.

faint_stars (Galileo SSI / Voyager outer-leg scene class)

Every catalog star in the FOV is fainter than the per-observation limiting magnitude obs.star_max_usable_vmag(). The stars model emits no STAR features that clear the magnitude gate. The technique’s is_feasible() fails with reason no_usable_stars and the technique skips its navigate pass entirely. The orchestrator falls back to whichever body- or ring-derived technique is feasible on the scene and surfaces the per-technique infeasibility on the per-image NavResult so the curator records which gate fired.