Ring Navigation Model
Overview
NavModelRings is the catalog-driven ring navigation
model. For each planet whose ring system has any radius inside the extended FOV the model
renders the per-ring-edge silhouette from the catalog, runs a four-pass
RingFeatureFilter to drop edges that are not separable / detectable on this image, and
emits either a RING_EDGE per surviving edge
(the “edges resolve” path) or a single
RING_ANNULUS per planet (the “edges
compress” path) when individual edges fall below the resolvability threshold.
The orchestrator constructs one model instance per planet whose ring system overlaps the
extfov. A simulated-image sibling
(NavModelRingsSimulated) renders rings
from operator-supplied parameters instead of the catalog; both classes share annotation
helpers on NavModelRingsBase.
Theory
The ring model is a per-planet edge renderer plus a feature-emission gate that decides whether to ship per-edge polylines or a composite annulus template.
Edge rendering
For each catalog-defined ring edge the model:
Builds an oversampled meshgrid around the predicted ring’s projected bounding box.
Queries the per-pixel ring radius backplane and the ring longitude backplane.
Marks the discrete pixel set whose radius lies on the edge’s catalog radius.
Walks the pixel set to produce a polyline of vertices, each with an outward radial normal estimated from the local radius gradient.
Per-edge feature filtering
A four-pass filter removes edges that cannot contribute a useful constraint:
Catalog presence. Edges absent from the catalog or with non-finite radius are dropped.
Visibility. Edges whose predicted bounding box has no overlap with the extfov are dropped.
Resolvability. Edges whose maximum radial pixel extent compresses below
feature_emission.ring_annulus.<planet>.max_radial_pxare flagged for the annulus path.Detectability. Edges whose per-pixel signal-to-noise falls below the per-instrument detection threshold are dropped.
Curvature classification
For each surviving polyline the model fits a best-fit straight line and measures the
maximum perpendicular deviation. An edge whose deviation exceeds
curvature_threshold_pixels (or the configured fraction of the edge’s length) is flagged
is_curved; otherwise the edge is straight-line and the per-edge constraint is rank-1
along the radial direction. The Ring Edge Fit (RingEdgeNav) page describes how
the downstream technique handles the all-straight case.
Per-vertex covariance
Each polyline vertex carries two per-vertex sigma values: a radial sigma along the outward normal (the constrainable axis) and an along-edge sigma along the polyline tangent (the unobservable axis).
The radial sigma is the catalog-side radial uncertainty \(\sigma_{\mathrm{rms,\,km}}\) projected to pixels at the ring’s radial scale:
The numerator is the maximum of the inner-edge and outer-edge rms values supplied by
the per-planet ring catalogue (config_3N0_<planet>_rings.yaml); taking the maximum
rather than the average is conservative — a feature’s overall radial uncertainty is
dominated by its least well-characterised edge. The denominator is the per-image radial
km/px scale at the ring (the mean of the per-pixel ring-radial-resolution backplane). A
single \(\sigma_{\mathrm{radial,\,px}}\) value is broadcast across every vertex of
the polyline; spatial variation of the catalog rms along the ring’s longitude is not
modelled.
The along-edge sigma is the project-wide constant
RING_EDGE_SIGMA_ALONG_PX (0.5 px), reflecting
the polyline-sampling resolution. The DT-based fit treats motion along the polyline as
essentially unobservable by construction, so this axis only sets the scale at which
along-edge displacement is numerically de-weighted relative to the radial axis.
The per-vertex covariance does not absorb the optical PSF sigma, the per-edge photometric
contrast against the background, or any longitude-dependent perturbation; those terms
shift the apparent edge position non-uniformly around the ring rather than enlarging the
per-vertex radial sigma, and the technique-side fitter handles that scatter via the
shared M-estimator robust-weighting machinery in nav.nav_technique.dt_fitting
(see DT Fitting (Shared Polyline-vs-Image Fitter)).
Annulus template
When the per-planet km/px scale exceeds the configured threshold (or any single ring edge
compresses below the per-polyline radial-pixel threshold), the model emits a single
RING_ANNULUS feature carrying a rendered
template image of the entire ring system
(every ring radius painted at the catalog brightness contrast) plus the matching mask.
The template’s bounding box is the union of the per-edge bounding boxes; the template
brightness at each pixel is the sum of the per-ring-edge contributions.
Restrictions and assumptions
The catalog must provide a per-ring-edge radius and an RMS uncertainty. Rings missing either field are dropped silently.
The model assumes the per-image SPICE pose is good enough that the predicted ring geometry is approximately correct. A wrong pose shifts every edge polyline by the same pose error; the downstream DT fit recovers the offset.
The detectability filter assumes the per-instrument calibration converts the catalog surface brightness into a per-pixel signal correctly. When the calibration is wrong (e.g. on a calibrated-IF instrument with a stale CALIB pipeline), the detectability test may include or exclude wrong edges.
Sources of uncertainty
The per-vertex radial sigma is the catalog-side RMS projected to pixels at the ring’s
radial scale. It does not capture the optical PSF blur, the per-edge photometric softness
against the background, a per-image radial bias from a wrong epoch ring solution, or a
longitude-dependent brightness modulation that would shift the apparent edge position
non-uniformly around the ring. Those terms enter the technique-side fit through the
M-estimator’s robust weighting (which down-weights vertices whose DT residual is
inconsistent with the per-vertex sigma) rather than by inflating the sigma itself; see
DT Fitting (Shared Polyline-vs-Image Fitter) for the fitter’s treatment. Edges flagged
is_straight_line are rank-1 along radial only;
edges that pass the curvature classification carry full-rank locally-observable
information.
Configuration
The model’s runtime knobs are split across three locations: the rings block in
src/nav/config_files/config_050_rings.yaml (general per-model knobs and label
rendering), the per-planet ring catalogues in
src/nav/config_files/config_3N0_*_rings.yaml (one file per planet, each carrying a
rings.ring_features mapping), and the per-planet annulus-emission thresholds under
feature_emission.ring_annulus in src/nav/config_files/config_510_techniques.yaml
(consumed by the per-feature emission gate; see Ring Annulus Correlate (RingAnnulusNav)).
Module-level Python constants in nav.nav_model.nav_model_rings set per-vertex
sigma and curvature thresholds that are not exposed to YAML.
rings block
Every key under rings is listed below. Several keys are reserved for ring-feature
filtering work that is not consumed by the active extractor; the second line of each
bullet names the consumer (or “reserved” when no consumer is wired).
model_source— str, defaultephemeris. Selects the ring-feature data source. Reserved; the active extractor reads the per-planetring_featuresmapping unconditionally.fiducial_feature_threshold— int, default3(count). Reserved for the fiducial-feature classifier that promotes a ring edge to fiducial status when at least this many independent edge measurements line up. Not consumed by the active extractor.fiducial_rms_gain— float, default2(dimensionless). Reserved alongsidefiducial_feature_thresholdfor the fiducial classifier’s RMS gain term. Not consumed by the active extractor.fiducial_min_feature_width— int, default2px. Reserved alongsidefiducial_feature_threshold; minimum radial width below which a fiducial promotion is suppressed. Not consumed by the active extractor.one_sided_feature_width— float, default30.0px. Reserved width used by the fiducial classifier for one-sided (gap or step) features. Not consumed by the active extractor.fiducial_ephemeris_width— int, default100px. Reserved tolerance window for matching ephemeris-predicted radii against detected fiducials. Not consumed by the active extractor.min_curvature_low_confidence— list (rad, ratio), default[0.0, 0.5]. Reserved curvature / confidence threshold for the rank-1 degeneracy escape on the all-straight ring fit. Not consumed by the active extractor; the equivalent test lives on the per-feature flags.min_curvature_high_confidence— list (rad, ratio), default[0.17, 1.0]. Reserved alongsidemin_curvature_low_confidence. Not consumed by the active extractor.curvature_to_reduce_features— float, default1.5708rad (= 90 degrees). Reserved threshold above which the extractor would reduce the surviving polyline count by collapsing nearly-orthogonal edges. Not consumed by the active extractor.curvature_reduced_features— int, default1(count). Reserved for the reduce-features path. Not consumed by the active extractor.emission_fiducial_threshold— float, default0.75(dimensionless). Reserved per-edge fiducial-promotion threshold. Not consumed by the active extractor.emission_use_threshold— float, default0.2(dimensionless). Reserved per-edge emission floor. Not consumed by the active extractor.remove_planet_shadow— bool, defaulttrue. When true the model masks pixels inside the per-planet shadow before rendering ring edges; the ring radius is still defined inside the shadow but the brightness is zero, so leaving the shadow pixels in would skew the per-vertex covariance. Consumed byNavModelRings.remove_body_shadows— bool, defaultfalse. Reserved for masking the projected shadow of every body in the FOV. Not consumed by the active extractor.ring_features— dict[str, dict]. Per-planet ring catalogue overlaid from the per-planetconfig_3N0_*_rings.yamlfiles (see below). Consumed byNavModelRings.label_font— str, defaultliberation2/LiberationMono-Bold.ttf. Font used for ring labels. Consumed byNavModelRingsBase.label_font_size— int, default18px. Ring label font size. Consumed byNavModelRingsBase.label_font_color— list[int], default[255, 0, 0](RGB). Ring label font color. Consumed byNavModelRingsBase.label_limb_color— list[int], default[255, 0, 0](RGB). Color of the per-edge polyline drawn on the summary PNG. Consumed byNavModelRingsBase.label_mask_enlarge— int, default10px. Pixels around a ring edge to avoid for label placement. Consumed byNavModelRingsBase.label_horiz_gap— int, default7px. Horizontal gap between the edge and the head of the label arrow. Consumed byNavModelRingsBase.label_vert_gap— int, default5px. Vertical gap between the edge and the head of the label arrow. Consumed byNavModelRingsBase.
Per-planet ring catalogue
Each config_3N0_<planet>_rings.yaml file carries a single
rings.ring_features.<PLANET> mapping keyed by upper-case SPICE planet name (one file
per planet: Jupiter, Saturn, Uranus, Neptune). Each per-planet entry sets:
epoch— str (UTC). Reference epoch for every ring solution under this planet; used to advance precessing edge solutions to the per-image time. A missing or invalid epoch raises at config-load.fade_width_pix— float (px). Per-planet softness of the per-edge brightness profile used by the rendered annulus template; controls how many pixels the catalog brightness fades into the background.min_allowed_fade_width_pix— float (px). Floor on the per-edge fade width applied after per-image scaling; prevents the rendered template from collapsing to a single-pixel ring at high subject distances.min_feature_pixels— float (px). Minimum per-feature pixel extent below which the per-image filter drops the feature.features— dict[str, dict]. Per-edge entries keyed by feature name. Each entry carriesfeature_type(GAP/RINGLET/RING),name(display label), and one or both ofinner_data/outer_datalists. Each*_datalist enumerates the per-mode catalog terms (mode number plusa/rms/ae/long_peri/rate_perifor orbiting features, oramplitude/phase/pattern_speedfor free-mode features). Consumed byRingFeature.
Module-level emission constants
The per-vertex sigma defaults and curvature threshold are Python module-level constants
in nav.nav_model.nav_model_rings and are not exposed as YAML knobs. Tests and
downstream tools read the canonical values via these symbols.
RING_EDGE_DEFAULT_RELIABILITY— float,0.7(dimensionless). Catalog default reliability scaling applied to aRING_EDGEfeature before per-image weighting; the design’s “catalog_default_reliability” term in theRING_EDGEsigmoid.RING_EDGE_SIGMA_ALONG_PX— float,0.5px. Per-vertex sigma along the polyline tangent direction. Reflects polyline-sampling resolution; the technique-side fitter treats this axis as essentially unobservable.FLAT_CURVATURE_THRESHOLD_PX— float,1.0px. Pixel-deviation threshold below which a polyline is flaggedis_straight_line. The technique-side fitter then handles its rank-1 covariance.
Per-instrument overrides
The rings block is global: per-instrument YAML (config_4N0_inst_*.yaml) does not
override any of the keys above. Instrument-specific behaviour enters through the
observation snapshot — the optical PSF sigma read from
star_psf() and the extended-FOV margin set by
InstrumentSettings — rather than through
this config block.
Implementation
Source files:
src/nav/nav_model/nav_model_rings.py—NavModelRings, the four-pass filter, the per-edge sampler, and the annulus-template builder.src/nav/nav_model/nav_model_rings_base.py—NavModelRingsBase, abstract shared base carrying the ring annotation pipeline.src/nav/nav_model/rings/— thenav.nav_model.ringssubpackage with the validation, filtering, and rendering helpers (ring_types,ring_feature,ring_filter,ring_math,ring_render_context,ring_render_result).
Public class NavModelRings, base
NavModelRingsBase. Self-registers via
__init_subclass__.
Public methods (autodocumented at nav.nav_model):
instances_for_obs()— class method that returns one instance per planet whose ring system has any radius inside the extended FOV.create_model()— populates the model state by rendering each per-edge silhouette, running the four-pass filter, and emitting per-vertex polyline data plus an optional annulus template.to_features()— runs the per-edge emission gates and constructs zero or moreNavFeatureinstances (RING_EDGEper surviving edge, orRING_ANNULUSper planet when the annulus path fires).to_annotations()— emits per-edge polylines and per-planet labels for the summary PNG.
Inherited NavModel properties:
name,
obs,
metadata.
Annotation helpers
NavModelRingsBase is the abstract shared
base. One helper lives there:
_create_edge_annotations— builds the per-edge polyline + per-edge labelAnnotationscollection for the summary PNG. Consumes thelabel_*keys documented above. Used by bothNavModelRingsandNavModelRingsSimulated.
The per-edge anti-aliasing math invoked from
RingFeature lives in
ring_math (compute_antialiasing); it is shared between
the real and simulated paths but is not part of the annotation pipeline.
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.planet— upper-case SPICE planet name. Absent when no planet is in the FOV.epoch— UTC epoch string for the ring solution (the per-planetrings.ring_features.<PLANET>.epochvalue advanced precessing solutions are evaluated against).feature_count— int, number of ring features that survived the four-pass filter.features— list[dict[str, str]], one entry per surviving ring feature carryingname(the catalog edge name) andtype(theNavFeatureTypevalue the per-edge emission gate ultimately produced —RING_EDGEorRING_ANNULUS).
Call path
Call path traced through
create_model():
Open a logged section. Look up the per-planet ring catalogue from the configured
ring_featuresmapping. Each entry carries a name, a radius, an RMS, and a per-edge surface-brightness profile.Build an oversampled meshgrid around the predicted ring’s projected bounding box and query the per-pixel ring radius and longitude backplanes.
For each catalog edge, mark the pixel set whose ring radius lies within the per-edge tolerance and run the four-pass filter.
For each surviving edge, walk the pixel set to produce a per-vertex polyline (position, radial normal, per-vertex sigma). Classify the polyline curvature via the best-fit straight-line residual.
Decide the per-planet emission path: when the per-planet km/px scale exceeds the configured threshold, or when any single edge compresses below the per-polyline radial- pixel threshold, render a
RING_ANNULUStemplate; otherwise emit per-edge polylines.
Call path traced through
to_features():
Annulus path. Construct one
RING_ANNULUSfeature per planet carrying the rendered annulus template plus the per-planet bounding box.Per-edge path. For each surviving edge, construct one
RING_EDGEfeature carrying theRingEdgePolyline(vertices, normals, per-vertex sigmas) plus a per-edgeRingEdgeFlagswith the catalog edge name and the curvature classification.
Examples
ring_only_curved(Cassini ISS NAC, imageN1447064164_1)A high-resolution Saturn-ring scene whose individual catalog edges resolve into separable polylines. The ring model emits multiple
RING_EDGEfeatures (the F-ring outer edge, the A-ring outer edge, gaps, ringlets); the per-planet km/px on this scene is well below the annulus threshold so the annulus path does not fire. Curvature classification flags the edges curved (each surviving polyline arcs noticeably across the FOV); the rank of the jointRingEdgeNavfit is full-rank because the curvature lifts the rank-1 degeneracy. See Ring Edge Fit (RingEdgeNav).ring_annulus_unresolved(Cassini ISS WAC, low-resolution approach phase)A low-resolution approach-phase Saturn image whose km/px exceeds the per-planet kmpp threshold. The ring model emits a single
RING_ANNULUSfeature per planet carrying the rendered annulus template; theRingAnnulusNavconsumes it via the shared pyramid-NCC machinery. See Ring Annulus Correlate (RingAnnulusNav).