Star Navigation Model
Overview
NavModelStars is the catalog-driven star
navigation model. For each observation the model reduces the configured catalog set into a
deduplicated star list, gates each star by catalog magnitude against the per-observation
limiting magnitude obs.star_max_usable_vmag(),
flags stars whose predicted positions overlap a body silhouette or
ring annulus, and emits one
STAR NavFeature
per star that is at least as bright as the limiting magnitude and whose conflict flags
allow it.
The orchestrator builds exactly one instance of this model per observation regardless of scene content; star navigation is the universal fallback when other navigation paths fail.
Theory
The star model orchestrates four cooperating steps: catalog reduction, conflict marking, the magnitude detectability gate, and per-star feature emission.
Catalog reduction
Multiple catalogs are merged into a single deduplicated star list, processed in priority order (most-precise first). When two stars from different catalogs match within a configurable angular tolerance and visual-magnitude tolerance, the higher-priority catalog’s entry survives. Pairs of stars whose magnitudes are too close together to disambiguate visually are dropped from both catalogs (the autonomous match would be unable to attribute a detection to one or the other).
Conflict marking
Each predicted star is checked against:
Body silhouettes — when a star’s predicted position lies inside any body’s predicted silhouette mask, the star is flagged
in_body_silhouette.Ring annuli — when a star’s predicted position lies inside any planet’s ring system (defined by the per-planet radial bounds in the YAML config), the star is flagged
in_body_silhouetteas well (a ring conflict occludes the star the same way).
The model does not gate a star on the saturation or cosmic-ray mask at its predicted
position: once a pointing offset is present the predicted position carries no special
significance, and a sharp stellar peak self-triggers the cosmic-ray detector, so consulting
the mask there would suppress good stars. The saturated and
in_saturation_or_cosmic_mask flags therefore stay clear. Star usability is an
occlusion-only gate.
Conflict-flagged stars stay in the model’s list (so the curator surfaces them in the
sidecar) but are excluded from the autonomous matching path by the upstream
usable_stars filter consulted by every star technique.
Magnitude detectability gate
A star is detectable when its catalog visual magnitude is at least as bright as the
per-observation limiting magnitude obs.star_max_usable_vmag(). The gate is purely
magnitude-based: a star with no catalog magnitude, or with
\(V_{\mathrm{mag}} > m_{\mathrm{limit}}\), is skipped, and no per-pixel DN photometry
or DN-to-image-unit scaling enters the decision. The catalog reduction applies the same
magnitude cap, so the gate in the model is mostly a self-contained re-check that also
handles the missing-magnitude case.
Each per-instrument star_max_usable_vmag() follows the form
where anchor is the limiting magnitude at a one-second exposure and each Pogson factor
of exposure time buys one more magnitude of depth.
Magnitude-margin effective SNR
The CRLB covariance and the reliability sigmoid still want an SNR-like quantity, so the model synthesises one from how far below the limiting magnitude the star sits rather than from any photometric DN measurement:
floored at the module constant SNR_FLOOR so it stays strictly positive. A star exactly
at the limit gets SNR_REF (8.0, just below the reliability sigmoid centre); each
magnitude of headroom multiplies the effective SNR by one Pogson ratio (2.512),
matching the flux ratio per magnitude. SNR_FLOOR (0.1) keeps the covariance away
from the zero-SNR huge-variance branch.
When the per-image smear length exceeds the anisotropic-smear threshold the PSF is
replaced by a smear-aware kernel built from the per-image smear vector via
compute_smear_vector_px(); the kernel has elongated
support, and the smear vector also rotates the CRLB covariance along the smear direction.
Stars whose smear length exceeds the per-instrument max_smear cap are dropped from the
emission set, as are stars fainter than the limiting magnitude.
nav.nav_model.stars.predicted_snr retains a separate raw-DN predicted_snr
photometry helper for diagnostics; it is not the gate consulted by the navigator’s star
path.
Per-feature uncertainty
Each emitted STAR feature carries a Cramer-Rao lower bound covariance derived from the
magnitude-margin effective SNR and the per-instrument PSF. For a 2-D Gaussian PSF the CRLB
on each position component reduces to
so stars well above the limiting magnitude have sub-pixel position sigma and stars near
the limit have several-pixel sigma. The per-axis variances populate the diagonal of the
position_cov_px covariance; the off-diagonal is
zero except when the smear-aware kernel is anisotropic, in which case the cross-axis
correlation reflects the smear orientation.
Restrictions and assumptions
The detectability gate depends on the per-instrument limiting-magnitude model. When
star_max_usable_vmag()is miscalibrated for an instrument or exposure, the emission gate may include or exclude the wrong stars.Catalog reduction assumes the per-catalog sky-coverage epoch is recent enough that proper motion brings the star to within the duplicate-detection tolerance of its true epoch position. Stars with very high proper motion may move out of catalog match between catalogs.
Ring-occlusion checks consult the per-planet radial bounds in the YAML; non-listed planets contribute no ring-occlusion flag.
Sources of uncertainty
The per-star CRLB covariance reflects the magnitude-margin centroid uncertainty. It
does not capture systematic biases from a misaligned camera distortion model, from
unmodelled background flux, or from PSF smear that exceeds the per-instrument
max_smear cap (stars above that cap are dropped from the emission set rather than
emitted with unreliable predictions). Star usability is gated only on body / ring occlusion;
the saturation and cosmic-ray masks are not consulted at the predicted position, so the
StarFlags saturation flags stay clear.
Configuration
The model’s runtime knobs live in stars in
src/nav/config_files/config_030_stars.yaml.
catalogs— list[str], default[ucac4, tycho2, ybsc]. Catalog priority order (most-precise first). Catalog files are loaded from the configured catalog roots.body_conflict_margin— int, default5px. Margin around body silhouettes for the body-conflict flag.default_star_class— str, default'G0'. Spectral class assigned to catalog stars with no per-entry class.stellar_aberration— bool, defaulttrue. Apply stellar aberration to predicted positions.proper_motion— bool, defaulttrue. Apply proper motion to predicted positions.max_stars— int, default100. Maximum number of predicted stars retained per observation.max_movement_steps— int, default50. Maximum sub-exposure steps used by the smear-vector computation.label_font— str. Font used for star labels in the summary PNG.label_font_size— int, default18px. Label font size.label_font_color— list[int], default[255, 0, 0](RGB). Label font color.label_star_color— list[int], default[255, 0, 0](RGB). Color of the star marker drawn on the summary PNG.duplicate_ra_dec_threshold_arcsec— float, default5arcsec. Tolerance for the per-catalog duplicate detection step.duplicate_vmag_threshold— float, default3mag. Magnitude tolerance for the duplicate detection step.overlapping_vmag_threshold— float, default2mag. Below this magnitude difference, two visually-overlapping stars are dropped from both catalogs.calibrated_data— bool, defaulttrue. Whether the per-image data is in calibrated I/F units (vs. raw DN).float_psf_sigma— bool, defaultfalse. When true, the per-instrument PSF sigma is treated as a fit parameter; when false, the per-instrument value is used verbatim.search_multipliers— list[float], default[0.25, 0.5, 0.75, 1.0]. Multipliers on the per-instrument SPICE pointing-error envelope used by the star matcher’s coarse search.perform_photometry— bool, defaulttrue. Whether to run per-star photometric validation.try_without_photometry— bool, defaultfalse. Whether to attempt a fallback match path with photometry disabled.min_stars_low_confidence— list (count, confidence), default[3, 0.75]. Minimum star count and confidence level for the low-confidence match path.min_stars_high_confidence— list (count, confidence), default[6, 1.0]. Minimum star count and confidence level for the high-confidence match path.min_confidence— float, default0.9. Minimum confidence level for the match to succeed.psf_gain— list (DN, gain), default[5000, 4]. PSF integrated gain mapping for flux estimation.max_smear— float, default100(dimensionless). Maximum smear length above which a star is dropped from the emission set.min_vmag— float, default5.0mag. Minimum visual magnitude (i.e. brightest) considered.max_vmag— float, default15.0mag. Maximum visual magnitude (i.e. dimmest) considered for prediction.vmag_increment— float, default0.5mag. Magnitude bin width for the per-bin star-list build.max_star_dn— float, default100000.0DN. Above this, stars are too bright to use (saturation regime).min_dn_force_one_star— float, default25000.0DN. Below this DN, the unique-bright single-star path will not fire even on a uniquely bright catalog star.star_body_conflict_margin— int, default3px. Smaller-than-body conflict margin used when the per-star centroid is close to the body silhouette boundary.too_bright_dn— float, default1000DN. Threshold above which a star is “very bright” for the per-star photometric tests.too_bright_factor— float, default1(dimensionless). Multiplier ontoo_bright_dn(reserved tuning slot).ring_occlusion_enabled— bool, defaulttrue. Whether to flag stars whose predicted positions lie inside a planet’s ring system.ring_occlusion_radii_km— dict[str, list[list[float]]]. Per-planet ring annular bounds (km) used by the occlusion check. Saturn entry covers C, B, and A rings; Uranus covers the main ring system; Neptune covers Galle through Adams rings.
Module-level emission constants
The detection-side thresholds and the catalog magnitude-bin grid are Python module-level
constants in the nav.nav_model.stars subpackage and are not exposed as YAML knobs.
Tests and downstream tools read the canonical values via these symbols.
DAOPHOT_DEFAULT_DETECTION_SIGMA— float,4.0(inimage_noise_sigma). Threshold for matched-filter peaks. Below 4 sigma the cosmic-ray-driven false-positive rate dominates real detections; the autonomous detector rejects peaks below this level.DAOPHOT_DEFAULT_SHARPNESS_MIN— float,0.2(dimensionless). Minimum DAOPHOT sharpness for a real star. Sharpness below 0.2 is dominated by single-pixel hot spikes whose wing contribution is too small to be a star.DAOPHOT_DEFAULT_SHARPNESS_MAX— float,1.0(dimensionless). Maximum DAOPHOT sharpness for a real star. Sharpness above 1.0 indicates an extended source (galaxy / blended pair) whose central pixel does not dominate.DAOPHOT_DEFAULT_ROUNDNESS_BOUND— float,1.0(dimensionless). Maximum \(|\mathrm{roundness}|\) for a real star, computed as the per-axis Gaussian-marginal asymmetry; values outside \([-1, 1]\) point at a CCD bloom or one-axis trail rather than a smear-oriented PSF.CATALOG_MAGNITUDE_BINS— tuple[float, …]. The coarse magnitude grid the catalog reducer pulls stars by. Stars are pulled bin by bin until the configuredmax_starsbudget is hit, which avoids the worst case of pulling every dim star in a degree-square chunk of UCAC4.
Per-instrument overrides
Per-instrument YAML files in src/nav/config_files/config_4N0_inst_*.yaml may override
catalog selection or photometric parameters; see the per-instrument source for the full
list.
Implementation
Source files:
src/nav/nav_model/stars/nav_model_stars.py—NavModelStarsplus the per-star CRLB-covariance helper, the smear-resolver, and the per-summary-PNG annotation builder.src/nav/nav_model/stars/catalog.py—nav.nav_model.stars.catalogmulti-catalog reduction helpers andCATALOG_MAGNITUDE_BINS.src/nav/nav_model/stars/conflicts.py—nav.nav_model.stars.conflictsbody / ring conflict marking.src/nav/nav_model/stars/predicted_snr.py—psf_sigma_px()(used by the model and the detection helpers) and theSCLASS_TO_B_MINUS_Vspectral-class-to-colour table.predicted_snr()is a raw-DN photometry diagnostic and is not the model’s detectability gate.src/nav/nav_model/stars/smeared_psf.py—compute_smear_vector_px()andsmear_length_px().src/nav/nav_model/stars/detection.py—nav.nav_model.stars.detectionmatched-filter source detection plus the fourDAOPHOT_DEFAULT_DETECTION_SIGMA,DAOPHOT_DEFAULT_SHARPNESS_MIN,DAOPHOT_DEFAULT_SHARPNESS_MAX, andDAOPHOT_DEFAULT_ROUNDNESS_BOUNDconstants documented above. Consumed by the star techniques rather than by the model itself; documented here because its constants set the detection floor every star technique applies.
Public class NavModelStars, base
NavModel. Self-registers via __init_subclass__.
Public methods (autodocumented at nav.nav_model):
instances_for_obs()— always returns[NavModelStars('stars', obs)](one instance per observation).create_model()— runs catalog reduction, computes the smear vector, marks conflicts, applies the magnitude detectability gate, and populates the per-star CRLB covariance.to_features()— emits oneSTARfeature per surviving catalog star.to_annotations()— emits per-star markers and labels for the summary PNG.
Inherited read-only properties on NavModel:
name,
obs,
metadata.
Annotation helpers
NavModelStars derives directly from
NavModel and carries its own annotation helpers
(unlike the body and ring families, which share helpers via an abstract base).
_build_annotations— builds the per-starAnnotationscollection: a rectangle outline at every predicted star position sized by itspsf_size, plus per-star labels carrying the catalog name and visual magnitude. Stars flagged with a body / ring conflict are skipped (they are surfaced in the per-image metadata for reviewer awareness but not drawn). Consumes thelabel_*andlabel_star_colorkeys documented above._extfov_indices— converts a star’s predicted(u, v)to extfov-frame indices for the rectangle drawer.The per-star label string is built by the module-level
_star_labelhelper, which picks one of the four arrow directions (TEXTINFO_TOP_ARROW/TEXTINFO_BOTTOM_ARROW/TEXTINFO_LEFT_ARROW/TEXTINFO_RIGHT_ARROW) based on the star’s distance from the frame edge.
Per-image metadata
create_model() populates
metadata with the following entries for the
curator to surface in the per-image JSON sidecar:
start_time/end_time/elapsed_time_sec— wall-clock timing for the model build.star_count— int, number of stars that survived catalog reduction, conflict marking, and the magnitude detectability gate.stars— list[dict], one entry per surviving star. Each entry carriescatalog_name,unique_number,pretty_name,ra_deg,dec_deg,vmag,u,v,move_u,move_v,spectral_class, andconflicts(the comma-separated body- / ring-occlusion flag string built from the per-star conflict marking step).
Conflict-flagged stars stay in the metadata so a reviewer can see which stars were
predicted but excluded; the upstream usable_stars filter consulted by every star
technique drops them from the autonomous matching path.
Call path
Call path traced through
create_model():
Open a logged section. Read the per-image observation epoch and configured catalog list.
Compute the per-image motion smear vector via
compute_smear_vector_px(). Stars whose smear length exceedsmax_smearare dropped from the emission set at feature-build time.Reduce the configured catalogs into a deduplicated star list via
reduce_catalogs(). The function returns a list of mutable star records carrying RA/Dec, magnitude, spectral class, parallax, proper motion, and per-catalog provenance.Mark body / ring conflicts on each star via
mark_body_and_ring_conflicts(). The function consults the per-image inventory and the YAML’sring_occlusion_radii_kmto setin_body_silhouetteandin_ring_annulusflags.Resolve the per-observation limiting magnitude from
obs.star_max_usable_vmag()and synthesise the magnitude-margin effective SNR for each star (used by the CRLB covariance and the reliability sigmoid).Drop stars fainter than the limiting magnitude (or with no catalog magnitude) and stars whose smear length exceeds the per-instrument cap; record the survivors on
self._stars.
Call path traced through
to_features():
For each surviving star, build a
StarGeometryfrom the predicted position and the per-feature CRLB covariance.Build a
StarFlagscarrying the magnitude-margin effective SNR, the catalog magnitude, and the body / ring conflict flags. The saturation / cosmic-ray-mask flags are left clear – star usability is an occlusion-only gate.Construct one
STARNavFeatureper star and return the list.
Examples
one_bright_star_no_body(Cassini ISS WAC, imageW1449079117_1)Single bright star (Vega) in an otherwise empty FOV. The star model emits one STAR feature for Vega, which is far brighter than the limiting magnitude and carries no body / ring conflict flags. The pass-1
StarUniqueMatchNavconsumes the feature in its 1-star path and reports the operator-verified offset \((\Delta v, \Delta u) = (3.06, -0.02)\) px. Other catalog stars in the WAC’s extended FOV are below the magnitude floor and produce no STAR feature.star_dominated(Cassini ISS WAC, imageW1580760393_1)Dense star field with no body in FOV. The star model emits dozens of STAR features above the magnitude floor. The pass-1
StarFieldFromCatalogNavconsumes the cohort and reports the operator-verified offset \((\Delta v, \Delta u) = (-2.68, -3.68)\) px via the triplet-hash matcher.below_resolution_body(Cassini ISS NAC, imageN1777325846_1)Mimas in the lower left at ~20 px diameter at phase 72 degrees. The star model emits STAR features for catalog stars in the FOV; stars whose predicted positions fall inside the predicted Mimas silhouette are flagged
in_body_silhouetteand the upstreamusable_starsfilter drops them so the star techniques do not consume them.