Rings
NavModelRings and
NavModelRingsSimulated build
ring feature models from configuration (or simulation parameters). The
nav.nav_model.rings subpackage holds the domain model: validation,
filtering, and rendering are separated so each concern can be tested in
isolation.
Ring domain model
RingFeatureType is a two-value enum
(RINGLET, GAP) that controls how the shaded region is oriented relative
to the known edge when only one edge is present.
RingBaseOrbitMode and
RingPerturbationMode are frozen
dataclasses that hold the orbital parameters for an edge.
RingPerturbationMode exposes an
is_inclination_mode property (mode number > 90) that the rendering path uses
to skip inclination modes that require a different backplane not yet
supported.
RingEdgeData bundles the base orbit
and zero or more perturbation modes for a single ring edge. Its base_radius
and rms properties extract the canonical radius and uncertainty from the
base orbit. parsed_modes_for_backplane() returns the list of (mode,
amplitude, phase, pattern_speed) tuples that the oops backplane API
consumes, omitting inclination modes.
RingFeature is the core domain
object—a frozen dataclass that owns a single ring feature (ringlet or gap) with
optional inner and outer edges. It is constructed via from_config(key, data)
which validates the YAML dictionary immediately and raises ValueError on
any malformed input. Derived cached fields (_start_et, _end_et) are
computed in __post_init__ using object.__setattr__() because the
dataclass is frozen. Query methods (is_visible_at, is_in_radius_range,
uncertainty, all_base_radii, uses_fade_for_edge) allow the filter
to make decisions without coupling to the rendering path. The render(context)
method dispatches to either _render_full_ringlet() (for two-edge features)
or _render_single_edge() (for one-edge features) and returns one or two
RingRenderResult instances.
validate_no_date_overlaps(features) is a module-level function that
enforces authoring invariants: if two features with explicit date ranges share
overlapping radii, their date ranges must not overlap. This is checked as a hard
error at YAML load time to catch mistakes before any rendering occurs.
Ring filtering pipeline
RingFeatureFilter applies a
four-pass decision pipeline to a list of RingFeature objects and returns
only the features that should be rendered:
Pass |
Name |
Decision |
|---|---|---|
1 |
Date |
Exclude the feature if the observation time is outside its |
2 |
Radius |
Exclude the feature if neither edge falls within |
3 |
Resolvability |
For two-edge features (RINGLETs and GAPs where both edges are in the
FOV), exclude the feature if the gap width |
4 |
Fade conflict |
For each edge that uses a fade, check whether a neighboring edge’s
|
The filter logs every exclusion decision at DEBUG level with the feature
key, pass number, and reason, allowing detailed inspection without polluting
INFO output. Exclusion and trimming are kept in the filter rather than in
the renderer so that each concern can be tested independently.
Ring YAML configuration
Top-level ring model parameters
These keys sit directly under the rings: section (not under
rings.ring_features.<PLANET>). They are shared across all planets and
control post-render processing that is applied to every feature before the
NavModelResult is constructed.
Parameter |
Default |
Description |
|---|---|---|
|
|
When |
|
|
Reserved for future implementation. When set, it will zero out ring model pixels that lie in the shadows cast by moons. The flag is accepted by the configuration parser but has no effect in the current release. |
Shadow removal is applied after rendering and before
NavModelResult construction, so the model_img and model_mask
stored in each result already reflect the masking. At INFO log level the
orchestrator reports the shadow pixel count:
Planet shadow removal: 1284 pixel(s) inside SATURN shadow will be masked
Set general.log_level_model_rings to DEBUG for the full backplane call
trace.
Planetary ring features are defined in separate YAML files under
src/nav/config_files/. The default Saturn configuration is in
config_21_saturn_rings.yaml.
YAML structure
rings:
ring_features:
SATURN: # Planet name (must match obs.closest_planet)
epoch: '2004-01-01 12:00:00' # Reference epoch for precessing modes
fade_width_pix: 100.0 # Nominal fade width in pixels for each edge
min_allowed_fade_width_pix: 2.0 # Minimum fade width before edge is excluded
min_feature_pixels: 2.0 # Minimum resolvable gap width (pass-3 filter)
features: # Dict of named ring features
colombo_gap:
feature_type: GAP
outer_data: # Edge data list (mode 1 = base orbit)
- mode: 1
a: 77870.0 # Semi-major axis in km
ae: 100.0 # Amplitude of eccentricity (km)
long_peri: 195.0 # Longitude of periapsis (degrees)
rate_peri: 0.0 # Precession rate (degrees/day)
rms: 2.0 # Edge uncertainty (km, 1-sigma RMS)
titan_ringlet:
feature_type: RINGLET
start_date: '2004-01-01' # Optional: feature active from this UTC date
end_date: '2017-09-15' # Optional: feature active until this UTC date (exclusive)
inner_data:
- mode: 1
a: 77517.0
ae: 3.0
long_peri: 0.0
rate_peri: 0.0
rms: 1.0
outer_data:
- mode: 1
a: 77871.0
ae: 5.0
long_peri: 0.0
rate_peri: 0.0
rms: 2.0
- mode: 2 # Optional perturbation mode
amplitude: 1.5
phase: 30.0
pattern_speed: 0.5
Planet-level parameters
Parameter |
Description |
|---|---|
|
Reference UTC date-time string for evaluating precessing orbital modes.
All |
|
Desired fade width in pixels for each rendered edge. The fade spans this many pixels everywhere in the image: at the ansae (high resolution) the fade covers fewer kilometres; near the ansa edges (low resolution) it covers more. Required; must be positive. |
|
Minimum fade width in pixels. If a neighboring edge would force the conflict-adjusted fade below this threshold (at the best resolution along the edge), the edge is excluded by the filter. Required; must be positive. |
|
Minimum resolvable width in pixels for two-edge features (RINGLETs and
GAPs where both edges fall within the FOV). Features narrower than
|
Feature-level parameters
Parameter |
Description |
|---|---|
|
|
|
List of mode dicts describing the edge orbit. At least one of these must be present. Mode 1 is the base orbit (required in the list); higher modes are perturbations. See edge mode parameters below. |
|
Optional UTC date string. The feature is active only for observations at or after this date. |
|
Optional UTC date string. The feature is active only for observations strictly before this date. |
Edge mode parameters (mode 1 — base orbit)
Field |
Description |
|---|---|
|
Must be |
|
Semi-major axis in km. Must be positive. |
|
Eccentricity amplitude in km (half of peak-to-peak radial variation). |
|
Longitude of periapsis at the reference epoch, in degrees. |
|
Precession rate of periapsis, in degrees per day. |
|
Edge position uncertainty, in km (1-sigma RMS). Used for
|
Edge mode parameters (mode > 1 — perturbation modes)
Field |
Description |
|---|---|
|
Mode number. Values 2-90 are radial perturbations (supported). Values > 90 are inclination modes (stored but silently skipped during rendering because the current backplane API does not support them). |
|
Perturbation amplitude in km. |
|
Phase angle at the reference epoch, in degrees. |
|
Pattern speed in degrees per day. |
Validation and loading
RingFeature.from_config() validates every feature dictionary immediately when it
is read. Errors raise ValueError with the feature key in the message. Checks
include:
feature_typemust be"GAP"or"RINGLET".At least one of
inner_data/outer_datamust be present.Each mode list must contain exactly one mode-1 entry.
amust be positive;rmsmust be non-negative.Date strings must be parseable by
utc_to_et.
After all features are loaded, validate_no_date_overlaps() performs a
cross-feature pass. If two features share overlapping radial extents and both have
explicit [start_date, end_date) windows that overlap in time, a ValueError is
raised. This catches authoring mistakes where a curator accidentally activates two
conflicting features simultaneously.
Adding a new planet
To configure rings for a new planet (e.g., Uranus):
Create
src/nav/config_files/config_XX_uranus_rings.yamlwith the structure shown above, replacingSATURNwithURANUS.Add a
!includedirective (or equivalent) in the main config so that the new file is loaded alongside the Saturn file:rings: ring_features: !include config_XX_uranus_rings.yaml
Populate
fade_width_pix,min_allowed_fade_width_pix,min_feature_pixels, andepochfor the new planet.Add individual features under
features:using the same format as Saturn.
No code changes are required. The orchestrator (NavModelRings) reads whichever
planet name appears in obs.closest_planet and looks it up in
rings.ring_features at runtime.