nav.nav_model
- class NavModel(name: str, obs: ObsSnapshot, *, config: Config | None = None)[source]
-
Base class for navigation models used to generate synthetic images.
- abstractmethod create_model(*, always_create_model: bool = False, never_create_model: bool = False, create_annotations: bool = True) → None[source]
Creates the internal model representation for a navigation model.
- Parameters:
always_create_model – If True, creates a model even if won’t have useful contents.
never_create_model – If True, only creates metadata without generating a model or annotations.
create_annotations – If True, creates text annotations for the model.
- property models: list[NavModelResult]
Returns the list of results produced by this model.
- property obs: ObsSnapshot
Returns the observation snapshot associated with this model.
Dataclass encapsulating the result of a navigation model computation.
- class NavModelResult(model_img: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, model_mask: ndarray[tuple[Any, ...], dtype[bool]] | None = None, weighted_mask: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, range: ndarray[tuple[Any, ...], dtype[floating[Any]]] | float | None = None, blur_amount: ndarray[tuple[Any, ...], dtype[floating[Any]]] | float | None = None, uncertainty: ndarray[tuple[Any, ...], dtype[floating[Any]]] | float | None = None, confidence: float | None = None, stretch_regions: list[ndarray[tuple[Any, ...], dtype[uint8]]] | None = None, annotations: Annotations | None = None)[source]
Bases:
objectEncapsulates a single result from a navigation model.
A model may produce one or more results (e.g., one per body or one combined). Each result holds the model image, mask, range, and related per-result data.
- annotations: Annotations | None = None
- class NavModelStars(name: str, obs: Observation, *, star_list: list[MutableStar] | None = None, config: Config | None = None)[source]
Bases:
NavModel- create_model(*, always_create_model: bool = False, never_create_model: bool = False, create_annotations: bool = True) → None[source]
Create a model containing nothing but stars.
Individual stars are modeled using the PSF specified by the associated Inst class.
- Parameters:
ignore_conflicts – True to include stars that have a conflict with a body or rings.
- Returns:
A tuple containing the model, metadata, and annotations.
- property star_list: list[MutableStar]
Return the list of stars in the model.
- stars_list_for_obs(radec_movement: tuple[float, float] | None = None, **kwargs: Any) → list[MutableStar][source]
Return a list of stars in the FOV of the obs.
- Parameters:
radec_movement – A tuple (dra,ddec) that gives the movement of the camera in each half of the exposure. None if no movement is available.
**kwargs – Passed to find_stars to restrict the types of stars returned.
- Returns:
A list of Star objects in the FOV. The following attributes are added to the Star object:
catalog_name: The name of the catalog the star was found in.pretty_name: A pretty name for the star.uandv: The U,V coordinate (including stellar aberration if applicable).move_uandmove_v: The movement of the star in U,V coordinates the entireexposure.
ra_pmanddec_pm: The proper motion of the star.temperature_faked: A bool indicating if the temperature and spectral classhad to be faked.
johnson_mag_faked: A bool indicating if the Johnson magnitudes had to be faked.dn: The estimated integrated DN count given the star’s class, magnitude, andthe filters being used. This is just a relative value based on the star’s magnitude.
conflicts: A string indicating if the star has a conflict with another staror body.
- class NavModelBodyBase(name: str, obs: ObsSnapshot, *, config: Config | None = None)[source]
Bases:
NavModelBase class for body navigation models.
Provides shared helpers to compute a limb mask and to create annotations consistent with the standard body model implementation.
- class NavModelBody(name: str, obs: Observation, body_name: str, *, inventory: dict[str, Any] | None = None, config: Config | None = None)[source]
Bases:
NavModelBodyBase- create_model(*, always_create_model: bool = False, never_create_model: bool = False, create_annotations: bool = True) → None[source]
Creates a navigation model for a planetary body with optional text overlay.
- Parameters:
always_create_model – If True, creates a model even if the body is too small or has poor limb definition.
never_create_model – If True, creates metadata but doesn’t generate an actual model or annotations.
create_annotations – If True, creates text annotations for the model.
- class NavModelBodySimulated(name: str, obs: Observation, body_name: str, sim_params: dict[str, Any], *, config: Config | None = None)[source]
Bases:
NavModelBodyBase
Base class for ring navigation models.
This module provides the annotation creation helper shared by the real ring model
(NavModelRings) and the simulated ring model (NavModelRingsSimulated).
Anti-aliasing is implemented in
nav.nav_model.rings.ring_math.compute_antialiasing and is invoked from
RingFeature._render_full_ringlet(). Annotation helpers live here because they
need observation metadata (image shape, config font settings) that belongs with
NavModel, unlike the pure math in ring_math.
- class NavModelRingsBase(name: str, obs: ObsSnapshot, *, config: Config | None = None)[source]
Bases:
NavModelBase class for ring navigation models.
Provides the
_create_edge_annotationshelper for creating annotations consistent between the real and simulated ring model implementations. Anti-aliasing math is innav.nav_model.rings.ring_math.
Navigation model for planetary rings.
This module implements the orchestrator for the planetary ring navigation model. It is a thin coordinator that:
Reads the planet block from merged config (
rings.ring_features.<PLANET>), including required planet-level parameters (epoch, fade settings, etc.).Requires a non-empty
featuresdict under that block (each entry is validated when passed toRingFeature.from_config()).Checks ring visibility via the
ring_radiusbackplane before callingRingFeature.from_config(), so feature parsing is skipped when no ring radii appear in the FOV.Constructs typed
RingFeatureobjects viaRingFeature.from_config(), raisingValueErroron malformed feature entries.Validates no two features have overlapping date ranges over the same radial region (
validate_no_date_overlaps).Performs a fast extent check: if the minimum radius in the FOV exceeds the maximum
a + aeacross all features, no ring can be visible and the model returns before the expensive resolutions backplane and filter pipeline.Filters through the four-pass
RingFeatureFilterpipeline.Renders each surviving feature via
feature.render(context).Optionally removes planet-shadow pixels from each rendered result when
rings.remove_planet_shadowisTrue(shadow computed once viaobs.ext_bp.where_inside_shadow).Wraps each result in a
NavModelResultwith annotations.
Design principle: This module contains no physics, no math, and no rendering
logic. All of that lives in the rings subpackage (ring_feature,
ring_math, ring_filter). The orchestrator’s only job is to wire together
configuration retrieval, backplane access, and NavModelResult construction.
Top-level rings keys (under rings):
remove_planet_shadow: Boolean (defaultFalse). WhenTrue, pixels that lie inside the nearest planet’s own shadow are zeroed out of each rendered feature’s model image and excluded from its model mask before theNavModelResultis constructed. A warning is logged and shadow removal is skipped if the backplane call fails.
Planet ring block keys (under rings.ring_features.<PLANET>):
general.log_level_model_rings: Log level for theCREATE RINGS MODELPdsLogger.open()block. Ring filtering, feature rendering, and fade math log through the samePdsLoggeras this model (debugfor internals,infofor summaries on the orchestrator).epoch: UTC epoch string for radial mode calculations (required).fade_width_pix: Desired fade extent in pixels for single-edge features (required; no default).min_allowed_fade_width_pix: Minimum allowed fade after conflict reduction (required; no default).min_feature_pixels: Minimum resolvable feature width in pixels (required; no default).features: Dict of feature key -> feature dict (parsed viaRingFeature.from_config).
- class NavModelRings(name: str, obs: Observation, *, config: Config | None = None)[source]
Bases:
NavModelRingsBaseNavigation model for planetary rings based on ephemeris data.
Retrieves ring feature definitions from the merged configuration, filters them for the current observation (date, visibility, resolvability, fade conflicts), renders each surviving feature via
RingFeature.render(), and appends the results toself._modelsasNavModelResultinstances.Each rendered edge becomes a separate
NavModelResultso the navigator can independently offset-correct individual ring features.- create_model(*, always_create_model: bool = False, never_create_model: bool = False, create_annotations: bool = True) → None[source]
Create the internal model representation for planetary rings.
- Parameters:
always_create_model – If True, creates a model even if it won’t have useful contents.
never_create_model – If True, only creates metadata without generating a model or annotations.
create_annotations – If True, creates text annotations for the model.
Simulated ring navigation model.
This module provides a navigation model for simulated rings created in the GUI.
The simulated ring model uses a different rendering path from the real ring model
(NavModelRings): instead of computing backplane-based ring radii and applying
RingFeature.render(), it delegates image generation to
nav.sim.sim_ring.render_ring(), which operates entirely in pixel space.
The data model types (RingFeature, RingEdgeData) are shared with the real
model for two purposes:
Validation:
RingFeature.from_config()validatessim_paramsstructure at construction time, catching authoring errors in the same way as the YAML config path.Annotations:
_create_edge_annotationsrequires anedge_info_list; the feature’suncertaintyfield is wired toNavModelResult.uncertainty.
The rendering path itself is NOT shared because simulated rings use pixel-space geometry rather than backplane geometry.
- class NavModelRingsSimulated(name: str, obs: Observation, ring_name: str, sim_params: dict[str, Any], *, config: Config | None = None)[source]
Bases:
NavModelRingsBaseNavigation model for simulated rings created in the GUI.
Uses
render_ring()for image generation (pixel-space) andRingFeature/RingEdgeDatafor annotation data and uncertainty.- create_model(*, always_create_model: bool = False, never_create_model: bool = False, create_annotations: bool = True) → None[source]
Create the internal model representation for simulated rings.
- Parameters:
always_create_model – If True, creates a model even if it won’t have useful contents.
never_create_model – If True, only creates metadata without generating a model or annotations.
create_annotations – If True, creates text annotations for the model.
nav.nav_model.rings
The nav.nav_model.rings subpackage provides types, features, filters, and
rendering support for planetary ring models.
Ring feature domain model for planetary navigation.
This subpackage defines typed domain objects for ring feature data, rendering, and
filtering: immutable dataclasses with validation at construction time and
rendering behavior on RingFeature.
Architecture overview:
ring_types: Pure frozen dataclasses for orbital parameters. No rendering dependencies; safe for lightweight import.ring_render_context: Immutable bundle of rendering dependencies passed toRingFeature.render().ring_render_result: Lightweight result object returned by rendering.ring_feature: Core domain object. Owns backplane-based rendering and cross-feature date-overlap validation.ring_filter: Four-pass filter pipeline deciding which features to render.ring_math: Pure mathematical functions for fade and anti-aliasing.
- class RingBaseOrbitMode(a: float, ae: float, long_peri: float, rate_peri: float, rms: float)[source]
Bases:
objectMode-1 orbital parameters defining the base orbit of a ring edge.
This represents the fundamental circular (or nearly circular) orbit of a ring edge before any higher-order perturbation modes are applied. It is always present in the data; higher modes are optional perturbations.
- Parameters:
a – Semi-major axis in km. Must be > 0.
ae – Eccentricity amplitude in km. Zero means circular.
long_peri – Longitude of pericenter in degrees.
rate_peri – Precession rate of pericenter in degrees/day.
rms – RMS residual of the orbit fit in km. Must be >= 0. Used as the uncertainty measure for navigation.
- Raises:
TypeError – If any numeric field is not a finite real number (
boolis rejected because it is a subclass ofint).ValueError – If
a<= 0,ae< 0, orrms< 0.
- class RingEdgeData(base_orbit: RingBaseOrbitMode, perturbations: tuple[RingPerturbationMode, ...])[source]
Bases:
objectAll orbital mode data for one edge of a ring feature.
Combines the base orbit (
RingBaseOrbitMode) with zero or more higher-order perturbations (RingPerturbationMode). This is the complete description needed to compute the radius of one ring edge at any point in the image backplane.- Parameters:
base_orbit – The mode-1 base orbit parameters.
perturbations – Tuple of higher-order perturbation modes. May be empty. Inclination modes (mode_num > 90) are accepted here but excluded from backplane computation – see
radial_perturbations(). A mutable sequence (e.g.list) is accepted at construction and stored as an immutabletuple.
- Raises:
ValueError – If
base_orbitorperturbationsisNone.TypeError – If
base_orbitis not aRingBaseOrbitMode, ifperturbationsis not a non-string sequence, or if any sequence element is not aRingPerturbationMode.
- base_orbit: RingBaseOrbitMode
- property base_radius: float
Semi-major axis of the base orbit in km.
This is the nominal radius used for spatial filtering and conflict detection. It is the mean radius of the edge, not the instantaneous (perturbed) radius at any given longitude.
- parsed_modes_for_backplane() → list[tuple[Any, ...]][source]
Convert edge data to tuples for oops radial_mode computation.
Returns a list of tuples in the format expected by
oops.ext_bp.radial_mode():Mode 1 (base orbit):
(1, a, ae, long_peri_rad, rate_peri_rad_per_sec)Other modes:
(mode_num, amplitude, phase_rad, speed_rad_per_sec)
Inclination modes (mode_num > 90) are excluded because
oops.ext_bp.radial_modeonly supports in-plane perturbations. The base orbit always comes first, followed by perturbations in their original order.- Returns:
List of mode tuples. Always contains at least the base orbit tuple.
- perturbations: tuple[RingPerturbationMode, ...]
- radial_perturbations() → tuple[RingPerturbationMode, ...][source]
Return only radial (non-inclination) perturbation modes.
Inclination modes (mode_num > 90) require out-of-plane backplane support that is not yet implemented in oops. This method filters them out so callers do not need to check individually.
- Returns:
Tuple of perturbation modes with mode_num <= 90.
- class RingFeature(key: str, name: str | None, feature_type: RingFeatureType, inner_edge: RingEdgeData | None, outer_edge: RingEdgeData | None, start_date: str | None = None, end_date: str | None = None)[source]
Bases:
objectA single ring feature (gap or ringlet) with backplane rendering capability.
Frozen dataclass: all attributes are set at construction and never mutated.
render()takes context and returns results without modifying feature state. This immutability guarantees that feature data loaded from YAML config remains consistent throughout the pipeline: multiple filter passes, render calls, and annotation creation all see the same data.Owns backplane-based rendering for real observations. For simulated observations, data access and annotations are used but rendering is delegated to
sim_ring.render_ring()inNavModelRingsSimulated.- __post_init__() → None[source]
Validate and cache date conversions.
Uses
object.__setattr__()to set derived fields on a frozen dataclass. This is the standard Python pattern for frozen dataclasses that need computed cached values –__post_init__is the only place where this bypass is appropriate. After construction completes, all fields are truly frozen.- Raises:
ValueError – If both inner_edge and outer_edge are None, or if both dates convert to ET and
start_datedoes not strictly precedeend_date.
- all_base_radii() → list[tuple[float, str]][source]
Return (radius_km, edge_label) pairs for all present edges.
Edge labels follow the convention: - RINGLET inner edge: ‘IER’ (Inner Edge Ringlet) - RINGLET outer edge: ‘OER’ (Outer Edge Ringlet) - GAP inner edge: ‘IEG’ (Inner Edge Gap) - GAP outer edge: ‘OEG’ (Outer Edge Gap)
- Returns:
List of (radius_km, label) tuples for present edges.
- property edge_labels: dict[str, str]
Map of ‘inner’/’outer’ to edge label string.
- Returns:
Dict with keys ‘inner’ and ‘outer’ mapping to label strings (‘IER’/’OER’ for ringlets, ‘IEG’/’OEG’ for gaps).
- feature_type: RingFeatureType
- classmethod from_config(key: str, data: dict[str, Any]) → RingFeature[source]
Construct a RingFeature from a YAML feature dictionary.
Validates all fields at construction time. This follows the principle that bad config is an authoring error that should fail loudly and immediately, not silently degrade at render time.
- Parameters:
key – Feature key (YAML dict key used as identifier).
data – Feature dictionary with keys: -
feature_type: ‘GAP’ or ‘RINGLET’ (required) -name: Human-readable name (optional) -inner_data: List of mode dicts (optional) -outer_data: List of mode dicts (optional) -start_date: ISO date string (optional) -end_date: ISO date string (optional)
- Returns:
Constructed
RingFeatureinstance.- Raises:
TypeError – If
keyis not astrordatais not adict.ValueError – On any structural or value error: - feature_type not ‘GAP’ or ‘RINGLET’ - neither inner_data nor outer_data present - mode data is not a non-empty list - mode-1 data missing or has non-positive ‘a’ - rms < 0 - perturbation mode missing required fields
- inner_edge: RingEdgeData | None
- is_in_radius_range(min_r: float, max_r: float) → bool[source]
Return True if at least one edge is within the given radius range.
A feature is kept if ANY of its edges falls in
[min_r, max_r]. This enables partial visibility: a ringlet with one edge in range and one out of range is rendered as a single-edge feature byrender().- Parameters:
min_r – Minimum ring radius in km (inclusive).
max_r – Maximum ring radius in km (inclusive).
- Returns:
True if at least one edge base radius is in
[min_r, max_r].
- is_visible_at(obs_time_et: float) → bool[source]
Return True if this feature is valid at the given observation time.
A feature with no date range is always visible. If only
start_dateis set, the feature is visible at and after that date. If onlyend_dateis set, the feature is visible before that date. The range is half-open: [start_et, end_et).- Parameters:
obs_time_et – Observation time in TDB seconds (from
utc_to_et).- Returns:
True if the feature is active at
obs_time_et.
- property max_extent_radius: float
Maximum possible radius (a + ae) across all present edges.
Returns the outermost radius the feature could occupy at any longitude, accounting for eccentricity. Used for a fast pre-filter check: if the minimum observed ring radius in the FOV exceeds this value, none of the feature can appear in the image regardless of orientation.
- Returns:
Maximum of (base_orbit.a + base_orbit.ae) for all present edges.
- Raises:
ValueError – If both
inner_edgeandouter_edgeareNone, somax_extent_radiuscannot be computed.
- outer_edge: RingEdgeData | None
- render(context: RingsRenderContext) → list[RingRenderResult][source]
Render this feature using backplane data.
Dispatch logic:
RINGLET with both edges visible ->
_render_full_ringlet()-> one resultGAP with any edges, or single-edge RINGLET ->
_render_single_edge()per edge -> one result per edge
For RINGLETs with one edge out of the visible radius range (partial visibility),
RingFeatureFiltertrims the out-of-range edge toNonebefore this method is called, sorender()naturally takes the single-edge path for the remaining in-range edge (fade rendering).- Parameters:
context – Immutable rendering context with obs, ring_target, epoch, per-pixel resolutions, fade config, and all_edge_radii.
- Returns:
List of
RingRenderResultobjects. Typically one result for full ringlets and one per edge for gaps and single-edge features.
- property uncertainty: float
Maximum RMS across all present edges (km).
Used to populate
NavModelResult.uncertainty. The maximum (rather than minimum or average) is conservative: the overall uncertainty of a feature is dominated by its least well-characterized edge.- Returns:
Max of inner and outer edge RMS values, or the single edge RMS if only one edge is present.
- uses_fade_for_edge(edge_type: str) → bool[source]
Return True if the given edge uses fade rendering by structure.
Structural check based on feature configuration:
GAP edges always use fade (gaps render a fading gradient from each known edge; they do not fill solid between edges).
RINGLET edges use fade only when the feature has a single edge (the other is None).
This is a structural check. The
RingFeatureFilteraugments this with partial-visibility awareness: if a RINGLET has both edges but one is out of the visible radius range, the filter treats the in-range edge as fade-using even though this method returns False.- Parameters:
edge_type – ‘inner’ or ‘outer’.
- Returns:
True if this edge uses fade rendering by structure.
- Raises:
ValueError – If
edge_typeis not exactly'inner'or'outer'.
- class RingFeatureFilter(*, obs_time_et: float, min_radius: float, max_radius: float, min_res_at_radius: Callable[[float], float | None], fade_width_pix: float, min_allowed_fade_width_pix: float, min_feature_pixels: float, logger: Any)[source]
Bases:
objectFour-pass filter deciding which ring features and edges to include in a render.
Instantiate with observation-specific parameters, then call
filter()with the full feature list. The filter is stateless between calls; the same instance can be reused for the same observation parameters.- Pipeline passes:
Date: exclude features not valid at
obs_time_et.Radius: exclude features with no edge inside
[min_radius, max_radius].Resolvability: exclude two-edge features narrower than
min_feature_pixels * min_reskm.Fade conflict: exclude or trim individual fade-using edges whose conflict-adjusted fade width falls below
min_allowed_fade_width_pix * min_res.
- filter(features: Sequence[RingFeature]) → list[RingFeature][source]
Run the four-pass filter and return surviving features.
Features that fail a pass are excluded entirely. Features that partially fail pass 4 (one edge of a GAP excluded) are returned with the failing edge set to None.
- Parameters:
features – Ring features retrieved from configuration for the planet.
- Returns:
Filtered list of features, possibly with some edges trimmed to None.
- class RingFeatureType(*values)[source]
Bases:
EnumClassification of a ring feature as a gap or ringlet.
Determines the rendering polarity: - RINGLET: image is brightened between the two edges (fill between). - GAP: image is darkened between the two edges (clear between). Single-edge features of either type use fading instead of solid fill.
- GAP = 'GAP'
- RINGLET = 'RINGLET'
- class RingPerturbationMode(mode_num: int, amplitude: float, phase: float, pattern_speed: float)[source]
Bases:
objectA single radial or inclination perturbation mode for a ring edge.
Higher-order perturbations are superimposed on the base orbit defined by
RingBaseOrbitMode. They represent resonance-driven distortions.Modes with
mode_num > 90are inclination (out-of-plane) perturbations. These are stored in the data model because they appear in real YAML config files (e.g. Cassini Division features). However, inclination modes are not supported for radial backplane rendering becauseoops.ext_bp.radial_modeonly handles in-plane distortions. Useis_inclination_modeorRingEdgeData.radial_perturbations()to filter them out.- Parameters:
mode_num – Perturbation mode number passed to
oops.ext_bp.radial_mode. Values > 90 indicate inclination modes. Mission ring tables also use non-positive indices (e.g.0or negative modes in Saturn YAML).amplitude – Perturbation amplitude in km.
phase – Perturbation phase in degrees.
pattern_speed – Pattern speed in degrees/day.
- Raises:
ValueError – If
mode_numis not an integer (boolis rejected), ifamplitudeis not anint/float(boolrejected), is not finite, or is negative, or ifphaseorpattern_speedis not a finiteint/float.
- class RingRenderResult(model_img: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.floating[~typing.Any]]], model_mask: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.bool]], uncertainty: float, edge_info_list: list[tuple[~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.bool]], str, str]] = <factory>)[source]
Bases:
objectResult of rendering a single ring feature edge or band.
Returned by
RingFeature.render(). Contains the rendered model image and mask, the feature uncertainty (forNavModelResult), and pre-computed annotation edge data.edge_info_listcontains(edge_mask, label_text, edge_label)tuples for annotation creation.render()computes these during rendering to avoid recomputing the edge radius backplanes a second time. The orchestrator passes this list toNavModelRingsBase._create_edge_annotations().- Parameters:
model_img – Float64 array of rendered ring brightness values. Shape matches the extended FOV.
model_mask – Boolean mask array where True indicates pixels with non-zero ring model contribution.
uncertainty – Maximum RMS across all rendered edges (km). Sourced from
RingEdgeData.rmsviaRingFeature.uncertainty.edge_info_list – Pre-computed annotation data: list of
(edge_mask, label_text, edge_label)tuples.edge_maskis a boolean array in extended FOV coordinates.
- class RingsRenderContext(obs: Any, ring_target: str, epoch: float, resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], fade_width_pix: float, all_edge_radii: tuple[tuple[float, str], ...], logger: Any)[source]
Bases:
objectImmutable context for backplane-based ring feature rendering.
Constructed by the orchestrator (
NavModelRings) once per observation and passed unchanged to everyRingFeature.render()call. Contains observation data, computed backplane arrays, fade configuration, and the sorted list of all surviving edge radii for conflict-based fade reduction.The
all_edge_radiituple is built from features that survived all four filter passes. It is used bycompute_edge_fadeto reduce fade width when a neighboring feature’s edge is within the fade zone, preserving the current behavior of halving the fade extent at a conflict boundary rather than rendering with full width. This is a width reduction, not exclusion – exclusion is handled byRingFeatureFilterbefore rendering.- Parameters:
obs – The observation object (
oops.Observation). Provides access to all backplane computation methods.ring_target – Ring target string used for backplane calls, e.g.
'saturn:ring'.epoch – TDB epoch time in seconds used as the reference time for multi-mode orbital perturbation calculations.
resolutions – 2-D array of per-pixel radial resolution in km/pixel. Shape matches the extended FOV. Used to compute per-pixel fade widths:
fade_width_km = fade_width_pix * resolutions.fade_width_pix – Fade extent in pixels as configured in the YAML (
fade_width_pixkey). Must be finite and strictly positive; per-pixel km extent is computed at render time from this value andresolutions.all_edge_radii – Sorted tuple of
(radius_km, edge_label)pairs for all edges of all features that survived filtering. Used bycompute_edge_fadefor conflict detection and width reduction.logger –
PdsLoggerfrom the ringNavModel(same instance asNavModelRings._logger).
- compute_antialiasing(*, radii: ndarray[tuple[Any, ...], dtype[floating[Any]]], edge_radius: float, shade_above: bool, resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], max_value: float = 1.0) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute anti-aliasing shade at pixel boundaries near a ring edge.
Creates smooth sub-pixel transitions at the pixel boundary where the ring edge crosses. The shade value represents the fraction of the pixel that is covered by the ring, linearly interpolated between 0.0 and
max_valueas the edge moves from one side of the pixel to the other.When the pixel center is exactly at the edge, shade = 0.5 * max_value. When the edge is half a resolution unit past the pixel center (in the shade direction), shade = max_value (full coverage). When the edge is half a resolution unit in the opposite direction, shade = 0.0.
- Parameters:
radii – Array of ring radii at pixel centers (km).
edge_radius – Target edge radius (km).
shade_above – If True, shading is applied on the low-radius side of the edge (the object is above the edge, anti-aliasing goes below). If False, shading is applied on the high-radius side.
resolutions – Array of radial resolutions at each pixel (km/pixel).
max_value – Maximum shade value (default 1.0). Use values < 1.0 for partial-opacity rendering.
- Returns:
Array of shade values in [0, max_value], same shape as
radii. Results are clipped to [0,max_value].- Raises:
ValueError – If
radiiandresolutionsdiffer in shape, contain non-finite values, or any resolution is not strictly positive; or ifmax_valueis not finite and non-negative.
- compute_edge_fade(*, model: ndarray[tuple[Any, ...], dtype[floating[Any]]], radii: ndarray[tuple[Any, ...], dtype[floating[Any]]], edge_radius: float, shade_above: bool, fade_width_pix: float, resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], all_edge_radii: Sequence[tuple[float, str]], logger: Any) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute a linear fade from a single ring edge with per-pixel fade width.
This function produces a linear gradient from full brightness at a known ring edge to zero over a configurable distance. The fade is necessary when a ring feature has only one known edge – without it, the model image would show a false sharp boundary where the ring ceases to be defined. The gradient provides a smooth signal that works well for correlation-based navigation.
Per-pixel fade width: The fade width in km varies per pixel:
fade_width_km = fade_width_pix * resolutions. This ensures the fade always spans exactlyfade_width_pixpixels at every location in the image, regardless of the local radial resolution. At the ansae (fine resolution) the fade covers fewer km; at foreshortened regions (coarse resolution) it covers more km.Conflict detection and width reduction: When a neighboring feature’s edge falls within the fade zone, the fade width is reduced per pixel to half the distance to the neighbor. The
RingFeatureFilterhas already excluded edges where this reduction falls belowmin_allowed_fade_width_pix, so this function always produces a result.Shade direction:
shade_abovemaps to an internalshade_sign(+1 or -1) used bycompute_fade_integral.The integration uses four cases for pixel coverage:
Case 1: Both edge and fade end within the pixel.
Case 2: Edge within pixel, fade end extends beyond.
Case 3: Edge before pixel, fade end within pixel.
Case 4: Full coverage (edge before pixel, fade end after pixel).
- Parameters:
model – Current model image array. The fade is added to this.
radii – Per-pixel ring radius array from the backplane (km).
edge_radius – Nominal radius of the ring edge (km).
shade_above – If True, shade toward larger radii (away from planet); if False, shade toward smaller radii (toward planet).
fade_width_pix – Desired fade extent in pixels (from config); must be strictly positive (zero would yield zero per-pixel width and break integration).
resolutions – Per-pixel radial resolution (km/pixel).
all_edge_radii – Sorted sequence of (radius, label) pairs for all surviving feature edges. Used to detect conflict and reduce fade width when a neighboring edge falls within the fade zone.
logger –
PdsLoggerfrom the ringNavModel(same asRingsRenderContext.logger) for optional debug output when fade width is narrowed by neighbor edges.
- Returns:
per-pixel fade contribution is clipped to
[0, 1], then added to the inputmodel. The result may exceed1.0if the model already had large values.- Return type:
Updated model image
- Raises:
ValueError – If array shapes differ, values are non-finite, any resolution is not strictly positive,
fade_width_pixis not finite or is not strictly positive, oredge_radiusis not finite.TypeError – If
fade_width_pixhas an invalid type.
- compute_fade_integral(a0: ndarray[tuple[Any, ...], dtype[floating[Any]]], a1: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, edge_radius: float, width: ndarray[tuple[Any, ...], dtype[floating[Any]]], resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], shade_sign: float) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute the definite integral of the linear fade function over a pixel.
The fade function is a linear gradient from 1.0 at the edge to 0.0 at
edge_radius + shade_sign * width. The integral gives the average shade value for the portion of the pixel that overlaps the fade zone, which is what a properly anti-aliased renderer should compute.shade_signis +1.0 or -1.0 according to fade direction. The closed form depends onshade_signonly through the sign of two terms:result = ((1 + shade_sign * edge_radius / width) * (a1 - a0) + shade_sign * (a0^2 - a1^2) / (2 * width)) / resolutions- Parameters:
a0 – Lower integration bounds per pixel (km).
a1 – Upper integration bounds per pixel (km).
edge_radius – Fixed edge radius (km).
width – Per-pixel fade width in km. Varies per pixel because
fade_width_km = fade_width_pix * resolutions.resolutions – Per-pixel radial resolution (km/pixel).
shade_sign – +1.0 for shade_above, -1.0 for shade_below.
- Returns:
Per-pixel integral values, same shape as
a0.- Raises:
ValueError – If
a0ora1contain a non-finite value, if array shapes differ, or ifwidthorresolutionscontain a non-finite value or any element that is not strictly positive.
- validate_no_date_overlaps(features: Sequence[RingFeature]) → None[source]
Cross-feature validation: detect date-range overlaps in the same radial region.
Two features “overlap” if their date ranges intersect AND their radial extents intersect. This catches authoring errors in the YAML config where the same ring edge is defined twice with overlapping validity periods.
This function runs after all features are loaded via
from_config()and before the runtime filter. It is a hard error (ValueError) because overlapping dates for the same radial region is a config authoring mistake, not an observation-dependent condition. The filter handles valid features that are not relevant for a particular observation.- Parameters:
features – All features loaded from one planet’s config.
- Raises:
ValueError – If any pair of features has overlapping dates AND overlapping radial extents.
Immutable data types for ring feature orbital parameters.
This module defines the core value objects that represent ring edge orbital
data: RingFeatureType, RingBaseOrbitMode, RingPerturbationMode,
and RingEdgeData. These are frozen dataclasses (immutable) because orbital
parameters from YAML config are physical constants that should never be modified
after loading. Validation occurs at construction time so that downstream
rendering code can trust the data without defensive checks.
Separating these types into their own module keeps them free of rendering dependencies (oops, numpy), enabling lightweight import for testing and for the simulated model which uses the types for data validation but does not use backplane rendering.
Design notes:
RingBaseOrbitModeandRingPerturbationModeare distinct types to resolve the ambiguity in the YAML config wheremode: 1can appear with either base-orbit fields (a,ae, …) or perturbation fields (amplitude,phase, …). Having separate types makes the dispatch explicit inRingFeature.from_config().Inclination modes (
mode_num > 90) are stored inRingEdgeData.perturbationsbecause they are valid YAML data. However,RingEdgeData.radial_perturbations()andparsed_modes_for_backplane()exclude them, because theoopsbackplaneradial_mode()function only handles radial (in-plane) perturbations. Making the limitation visible here rather than silently skipping them in rendering code surfaces the issue for future implementors.
- class RingBaseOrbitMode(a: float, ae: float, long_peri: float, rate_peri: float, rms: float)[source]
Bases:
objectMode-1 orbital parameters defining the base orbit of a ring edge.
This represents the fundamental circular (or nearly circular) orbit of a ring edge before any higher-order perturbation modes are applied. It is always present in the data; higher modes are optional perturbations.
- Parameters:
a – Semi-major axis in km. Must be > 0.
ae – Eccentricity amplitude in km. Zero means circular.
long_peri – Longitude of pericenter in degrees.
rate_peri – Precession rate of pericenter in degrees/day.
rms – RMS residual of the orbit fit in km. Must be >= 0. Used as the uncertainty measure for navigation.
- Raises:
TypeError – If any numeric field is not a finite real number (
boolis rejected because it is a subclass ofint).ValueError – If
a<= 0,ae< 0, orrms< 0.
- class RingEdgeData(base_orbit: RingBaseOrbitMode, perturbations: tuple[RingPerturbationMode, ...])[source]
Bases:
objectAll orbital mode data for one edge of a ring feature.
Combines the base orbit (
RingBaseOrbitMode) with zero or more higher-order perturbations (RingPerturbationMode). This is the complete description needed to compute the radius of one ring edge at any point in the image backplane.- Parameters:
base_orbit – The mode-1 base orbit parameters.
perturbations – Tuple of higher-order perturbation modes. May be empty. Inclination modes (mode_num > 90) are accepted here but excluded from backplane computation – see
radial_perturbations(). A mutable sequence (e.g.list) is accepted at construction and stored as an immutabletuple.
- Raises:
ValueError – If
base_orbitorperturbationsisNone.TypeError – If
base_orbitis not aRingBaseOrbitMode, ifperturbationsis not a non-string sequence, or if any sequence element is not aRingPerturbationMode.
- base_orbit: RingBaseOrbitMode
- property base_radius: float
Semi-major axis of the base orbit in km.
This is the nominal radius used for spatial filtering and conflict detection. It is the mean radius of the edge, not the instantaneous (perturbed) radius at any given longitude.
- parsed_modes_for_backplane() → list[tuple[Any, ...]][source]
Convert edge data to tuples for oops radial_mode computation.
Returns a list of tuples in the format expected by
oops.ext_bp.radial_mode():Mode 1 (base orbit):
(1, a, ae, long_peri_rad, rate_peri_rad_per_sec)Other modes:
(mode_num, amplitude, phase_rad, speed_rad_per_sec)
Inclination modes (mode_num > 90) are excluded because
oops.ext_bp.radial_modeonly supports in-plane perturbations. The base orbit always comes first, followed by perturbations in their original order.- Returns:
List of mode tuples. Always contains at least the base orbit tuple.
- perturbations: tuple[RingPerturbationMode, ...]
- radial_perturbations() → tuple[RingPerturbationMode, ...][source]
Return only radial (non-inclination) perturbation modes.
Inclination modes (mode_num > 90) require out-of-plane backplane support that is not yet implemented in oops. This method filters them out so callers do not need to check individually.
- Returns:
Tuple of perturbation modes with mode_num <= 90.
- class RingFeatureType(*values)[source]
Bases:
EnumClassification of a ring feature as a gap or ringlet.
Determines the rendering polarity: - RINGLET: image is brightened between the two edges (fill between). - GAP: image is darkened between the two edges (clear between). Single-edge features of either type use fading instead of solid fill.
- GAP = 'GAP'
- RINGLET = 'RINGLET'
- class RingPerturbationMode(mode_num: int, amplitude: float, phase: float, pattern_speed: float)[source]
Bases:
objectA single radial or inclination perturbation mode for a ring edge.
Higher-order perturbations are superimposed on the base orbit defined by
RingBaseOrbitMode. They represent resonance-driven distortions.Modes with
mode_num > 90are inclination (out-of-plane) perturbations. These are stored in the data model because they appear in real YAML config files (e.g. Cassini Division features). However, inclination modes are not supported for radial backplane rendering becauseoops.ext_bp.radial_modeonly handles in-plane distortions. Useis_inclination_modeorRingEdgeData.radial_perturbations()to filter them out.- Parameters:
mode_num – Perturbation mode number passed to
oops.ext_bp.radial_mode. Values > 90 indicate inclination modes. Mission ring tables also use non-positive indices (e.g.0or negative modes in Saturn YAML).amplitude – Perturbation amplitude in km.
phase – Perturbation phase in degrees.
pattern_speed – Pattern speed in degrees/day.
- Raises:
ValueError – If
mode_numis not an integer (boolis rejected), ifamplitudeis not anint/float(boolrejected), is not finite, or is negative, or ifphaseorpattern_speedis not a finiteint/float.
Ring feature domain object and cross-feature date-overlap validation.
This module is the core of the ring domain model. It defines:
RingFeature: An immutable domain object representing a single ring gap or ringlet. It owns backplane-based rendering viarender(context)and provides query methods used by the filter and orchestrator.validate_no_date_overlaps(): A cross-feature validation function that detects authoring errors in the YAML config where two features cover the same radial region with overlapping date ranges.
Validation philosophy: from_config() raises ValueError on any
malformed per-feature data (bad types, missing fields, out-of-range values).
validate_no_date_overlaps() raises ValueError on cross-feature date
conflicts. Both are hard errors because bad config is an authoring mistake that
should be caught immediately, not silently degraded at render time. The
RingFeatureFilter handles valid features that are not relevant to a
particular observation.
Immutability: RingFeature is a frozen dataclass. All attributes are set
at construction and never mutated. Derived cached fields (_start_et,
_end_et) are set in __post_init__ using object.__setattr__(), the
standard Python pattern for frozen dataclasses that need computed cached
values – __post_init__ is the only place this bypass is appropriate. After
construction completes, all fields are truly frozen.
Rendering dispatch: render() calls _compute_edge_radii() which uses
RingEdgeData.parsed_modes_for_backplane() to get the mode tuples for the
oops.ext_bp.radial_mode() backplane calls. The rendering path branches based
on feature type and edge availability:
RINGLET with both edges ->
_render_full_ringlet()-> oneRingRenderResultGAP with either or both edges, or single-edge RINGLET ->
_render_single_edge()per present edge -> oneRingRenderResultper edge
- class RingFeature(key: str, name: str | None, feature_type: RingFeatureType, inner_edge: RingEdgeData | None, outer_edge: RingEdgeData | None, start_date: str | None = None, end_date: str | None = None)[source]
Bases:
objectA single ring feature (gap or ringlet) with backplane rendering capability.
Frozen dataclass: all attributes are set at construction and never mutated.
render()takes context and returns results without modifying feature state. This immutability guarantees that feature data loaded from YAML config remains consistent throughout the pipeline: multiple filter passes, render calls, and annotation creation all see the same data.Owns backplane-based rendering for real observations. For simulated observations, data access and annotations are used but rendering is delegated to
sim_ring.render_ring()inNavModelRingsSimulated.- __post_init__() → None[source]
Validate and cache date conversions.
Uses
object.__setattr__()to set derived fields on a frozen dataclass. This is the standard Python pattern for frozen dataclasses that need computed cached values –__post_init__is the only place where this bypass is appropriate. After construction completes, all fields are truly frozen.- Raises:
ValueError – If both inner_edge and outer_edge are None, or if both dates convert to ET and
start_datedoes not strictly precedeend_date.
- all_base_radii() → list[tuple[float, str]][source]
Return (radius_km, edge_label) pairs for all present edges.
Edge labels follow the convention: - RINGLET inner edge: ‘IER’ (Inner Edge Ringlet) - RINGLET outer edge: ‘OER’ (Outer Edge Ringlet) - GAP inner edge: ‘IEG’ (Inner Edge Gap) - GAP outer edge: ‘OEG’ (Outer Edge Gap)
- Returns:
List of (radius_km, label) tuples for present edges.
- property edge_labels: dict[str, str]
Map of ‘inner’/’outer’ to edge label string.
- Returns:
Dict with keys ‘inner’ and ‘outer’ mapping to label strings (‘IER’/’OER’ for ringlets, ‘IEG’/’OEG’ for gaps).
- feature_type: RingFeatureType
- classmethod from_config(key: str, data: dict[str, Any]) → RingFeature[source]
Construct a RingFeature from a YAML feature dictionary.
Validates all fields at construction time. This follows the principle that bad config is an authoring error that should fail loudly and immediately, not silently degrade at render time.
- Parameters:
key – Feature key (YAML dict key used as identifier).
data – Feature dictionary with keys: -
feature_type: ‘GAP’ or ‘RINGLET’ (required) -name: Human-readable name (optional) -inner_data: List of mode dicts (optional) -outer_data: List of mode dicts (optional) -start_date: ISO date string (optional) -end_date: ISO date string (optional)
- Returns:
Constructed
RingFeatureinstance.- Raises:
TypeError – If
keyis not astrordatais not adict.ValueError – On any structural or value error: - feature_type not ‘GAP’ or ‘RINGLET’ - neither inner_data nor outer_data present - mode data is not a non-empty list - mode-1 data missing or has non-positive ‘a’ - rms < 0 - perturbation mode missing required fields
- inner_edge: RingEdgeData | None
- is_in_radius_range(min_r: float, max_r: float) → bool[source]
Return True if at least one edge is within the given radius range.
A feature is kept if ANY of its edges falls in
[min_r, max_r]. This enables partial visibility: a ringlet with one edge in range and one out of range is rendered as a single-edge feature byrender().- Parameters:
min_r – Minimum ring radius in km (inclusive).
max_r – Maximum ring radius in km (inclusive).
- Returns:
True if at least one edge base radius is in
[min_r, max_r].
- is_visible_at(obs_time_et: float) → bool[source]
Return True if this feature is valid at the given observation time.
A feature with no date range is always visible. If only
start_dateis set, the feature is visible at and after that date. If onlyend_dateis set, the feature is visible before that date. The range is half-open: [start_et, end_et).- Parameters:
obs_time_et – Observation time in TDB seconds (from
utc_to_et).- Returns:
True if the feature is active at
obs_time_et.
- property max_extent_radius: float
Maximum possible radius (a + ae) across all present edges.
Returns the outermost radius the feature could occupy at any longitude, accounting for eccentricity. Used for a fast pre-filter check: if the minimum observed ring radius in the FOV exceeds this value, none of the feature can appear in the image regardless of orientation.
- Returns:
Maximum of (base_orbit.a + base_orbit.ae) for all present edges.
- Raises:
ValueError – If both
inner_edgeandouter_edgeareNone, somax_extent_radiuscannot be computed.
- outer_edge: RingEdgeData | None
- render(context: RingsRenderContext) → list[RingRenderResult][source]
Render this feature using backplane data.
Dispatch logic:
RINGLET with both edges visible ->
_render_full_ringlet()-> one resultGAP with any edges, or single-edge RINGLET ->
_render_single_edge()per edge -> one result per edge
For RINGLETs with one edge out of the visible radius range (partial visibility),
RingFeatureFiltertrims the out-of-range edge toNonebefore this method is called, sorender()naturally takes the single-edge path for the remaining in-range edge (fade rendering).- Parameters:
context – Immutable rendering context with obs, ring_target, epoch, per-pixel resolutions, fade config, and all_edge_radii.
- Returns:
List of
RingRenderResultobjects. Typically one result for full ringlets and one per edge for gaps and single-edge features.
- property uncertainty: float
Maximum RMS across all present edges (km).
Used to populate
NavModelResult.uncertainty. The maximum (rather than minimum or average) is conservative: the overall uncertainty of a feature is dominated by its least well-characterized edge.- Returns:
Max of inner and outer edge RMS values, or the single edge RMS if only one edge is present.
- uses_fade_for_edge(edge_type: str) → bool[source]
Return True if the given edge uses fade rendering by structure.
Structural check based on feature configuration:
GAP edges always use fade (gaps render a fading gradient from each known edge; they do not fill solid between edges).
RINGLET edges use fade only when the feature has a single edge (the other is None).
This is a structural check. The
RingFeatureFilteraugments this with partial-visibility awareness: if a RINGLET has both edges but one is out of the visible radius range, the filter treats the in-range edge as fade-using even though this method returns False.- Parameters:
edge_type – ‘inner’ or ‘outer’.
- Returns:
True if this edge uses fade rendering by structure.
- Raises:
ValueError – If
edge_typeis not exactly'inner'or'outer'.
- validate_no_date_overlaps(features: Sequence[RingFeature]) → None[source]
Cross-feature validation: detect date-range overlaps in the same radial region.
Two features “overlap” if their date ranges intersect AND their radial extents intersect. This catches authoring errors in the YAML config where the same ring edge is defined twice with overlapping validity periods.
This function runs after all features are loaded via
from_config()and before the runtime filter. It is a hard error (ValueError) because overlapping dates for the same radial region is a config authoring mistake, not an observation-dependent condition. The filter handles valid features that are not relevant for a particular observation.- Parameters:
features – All features loaded from one planet’s config.
- Raises:
ValueError – If any pair of features has overlapping dates AND overlapping radial extents.
Ring feature filter pipeline.
This module implements a four-pass filter that decides which ring features (and which
edges within each feature) are included in the final render. It is deliberately
separated from the rendering logic in ring_feature.py for two reasons:
Single responsibility: A feature object knows how to render itself; the filter knows which features are worth rendering for a given observation. These are distinct concerns – a feature’s physics do not change based on whether it is currently visible.
Testability: The filter is a pure function of its inputs (features + observation parameters). It can be tested independently of backplane computation. Rendering involves complex oops backplane calls that require mocking; the filter only needs simple numeric comparisons.
Pass 4 (fade conflict) checks individual edges, not whole features, because:
A GAP feature has two fade edges that shade independently in opposite directions. The outer edge shading outward may be clear of conflicts while the inner edge shading inward is blocked – the outer edge is still useful for navigation.
Excluding the whole feature because one edge is blocked would silently remove valid navigation signals.
Pipeline order rationale:
Date (cheapest: pure arithmetic) eliminates features not valid for this image.
Radius eliminates features outside the current field of view.
Resolvability eliminates two-edge features too narrow to see as a width.
Fade conflict (most expensive: needs all surviving edge radii) eliminates or trims individual fade-using edges that are squeezed by a neighbor.
Processing in this order means expensive operations run only on the smaller set of features that survive the cheaper passes.
- class RingFeatureFilter(*, obs_time_et: float, min_radius: float, max_radius: float, min_res_at_radius: Callable[[float], float | None], fade_width_pix: float, min_allowed_fade_width_pix: float, min_feature_pixels: float, logger: Any)[source]
Bases:
objectFour-pass filter deciding which ring features and edges to include in a render.
Instantiate with observation-specific parameters, then call
filter()with the full feature list. The filter is stateless between calls; the same instance can be reused for the same observation parameters.- Pipeline passes:
Date: exclude features not valid at
obs_time_et.Radius: exclude features with no edge inside
[min_radius, max_radius].Resolvability: exclude two-edge features narrower than
min_feature_pixels * min_reskm.Fade conflict: exclude or trim individual fade-using edges whose conflict-adjusted fade width falls below
min_allowed_fade_width_pix * min_res.
- filter(features: Sequence[RingFeature]) → list[RingFeature][source]
Run the four-pass filter and return surviving features.
Features that fail a pass are excluded entirely. Features that partially fail pass 4 (one edge of a GAP excluded) are returned with the failing edge set to None.
- Parameters:
features – Ring features retrieved from configuration for the planet.
- Returns:
Filtered list of features, possibly with some edges trimmed to None.
Pure mathematical functions for ring edge rendering.
This module provides standalone functions for ring edge fade gradients and anti-aliasing. Keeping the numerics here separate from orchestration gives:
Testability: Pure functions are exercised with numpy arrays without backplane-heavy integration tests.
Reuse: Backplane-based ring rendering and
nav.sim.sim_ringboth rely on the same anti-aliasing and fade logic defined here.Single responsibility:
RingFeature.render()chooses what to render and assembles results; this module performs the mathematical work.
Design notes
Per-pixel fade width: compute_edge_fade accepts fade_width_pix (a scalar
pixel count) and a per-pixel resolutions array, computing
fade_width_km = fade_width_pix * resolutions element-wise. This ensures the
fade spans exactly fade_width_pix pixels everywhere in the image, regardless
of the local radial resolution. The integration bounds therefore vary per pixel.
Shade direction: shade_above=True and shade_above=False share one
implementation through an internal shade_sign (+1 or -1) in
compute_fade_integral. The two directions differ only in the sign of two
terms in the closed form.
Conflict detection vs exclusion: compute_edge_fade handles width reduction
when a neighboring feature’s edge falls within the fade zone (halving the fade at
the conflict boundary). Exclusion of edges whose adjusted width falls below the
minimum is handled upstream by RingFeatureFilter before rendering. This
function therefore always produces a valid result.
- compute_antialiasing(*, radii: ndarray[tuple[Any, ...], dtype[floating[Any]]], edge_radius: float, shade_above: bool, resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], max_value: float = 1.0) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute anti-aliasing shade at pixel boundaries near a ring edge.
Creates smooth sub-pixel transitions at the pixel boundary where the ring edge crosses. The shade value represents the fraction of the pixel that is covered by the ring, linearly interpolated between 0.0 and
max_valueas the edge moves from one side of the pixel to the other.When the pixel center is exactly at the edge, shade = 0.5 * max_value. When the edge is half a resolution unit past the pixel center (in the shade direction), shade = max_value (full coverage). When the edge is half a resolution unit in the opposite direction, shade = 0.0.
- Parameters:
radii – Array of ring radii at pixel centers (km).
edge_radius – Target edge radius (km).
shade_above – If True, shading is applied on the low-radius side of the edge (the object is above the edge, anti-aliasing goes below). If False, shading is applied on the high-radius side.
resolutions – Array of radial resolutions at each pixel (km/pixel).
max_value – Maximum shade value (default 1.0). Use values < 1.0 for partial-opacity rendering.
- Returns:
Array of shade values in [0, max_value], same shape as
radii. Results are clipped to [0,max_value].- Raises:
ValueError – If
radiiandresolutionsdiffer in shape, contain non-finite values, or any resolution is not strictly positive; or ifmax_valueis not finite and non-negative.
- compute_edge_fade(*, model: ndarray[tuple[Any, ...], dtype[floating[Any]]], radii: ndarray[tuple[Any, ...], dtype[floating[Any]]], edge_radius: float, shade_above: bool, fade_width_pix: float, resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], all_edge_radii: Sequence[tuple[float, str]], logger: Any) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute a linear fade from a single ring edge with per-pixel fade width.
This function produces a linear gradient from full brightness at a known ring edge to zero over a configurable distance. The fade is necessary when a ring feature has only one known edge – without it, the model image would show a false sharp boundary where the ring ceases to be defined. The gradient provides a smooth signal that works well for correlation-based navigation.
Per-pixel fade width: The fade width in km varies per pixel:
fade_width_km = fade_width_pix * resolutions. This ensures the fade always spans exactlyfade_width_pixpixels at every location in the image, regardless of the local radial resolution. At the ansae (fine resolution) the fade covers fewer km; at foreshortened regions (coarse resolution) it covers more km.Conflict detection and width reduction: When a neighboring feature’s edge falls within the fade zone, the fade width is reduced per pixel to half the distance to the neighbor. The
RingFeatureFilterhas already excluded edges where this reduction falls belowmin_allowed_fade_width_pix, so this function always produces a result.Shade direction:
shade_abovemaps to an internalshade_sign(+1 or -1) used bycompute_fade_integral.The integration uses four cases for pixel coverage:
Case 1: Both edge and fade end within the pixel.
Case 2: Edge within pixel, fade end extends beyond.
Case 3: Edge before pixel, fade end within pixel.
Case 4: Full coverage (edge before pixel, fade end after pixel).
- Parameters:
model – Current model image array. The fade is added to this.
radii – Per-pixel ring radius array from the backplane (km).
edge_radius – Nominal radius of the ring edge (km).
shade_above – If True, shade toward larger radii (away from planet); if False, shade toward smaller radii (toward planet).
fade_width_pix – Desired fade extent in pixels (from config); must be strictly positive (zero would yield zero per-pixel width and break integration).
resolutions – Per-pixel radial resolution (km/pixel).
all_edge_radii – Sorted sequence of (radius, label) pairs for all surviving feature edges. Used to detect conflict and reduce fade width when a neighboring edge falls within the fade zone.
logger –
PdsLoggerfrom the ringNavModel(same asRingsRenderContext.logger) for optional debug output when fade width is narrowed by neighbor edges.
- Returns:
per-pixel fade contribution is clipped to
[0, 1], then added to the inputmodel. The result may exceed1.0if the model already had large values.- Return type:
Updated model image
- Raises:
ValueError – If array shapes differ, values are non-finite, any resolution is not strictly positive,
fade_width_pixis not finite or is not strictly positive, oredge_radiusis not finite.TypeError – If
fade_width_pixhas an invalid type.
- compute_fade_integral(a0: ndarray[tuple[Any, ...], dtype[floating[Any]]], a1: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, edge_radius: float, width: ndarray[tuple[Any, ...], dtype[floating[Any]]], resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], shade_sign: float) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute the definite integral of the linear fade function over a pixel.
The fade function is a linear gradient from 1.0 at the edge to 0.0 at
edge_radius + shade_sign * width. The integral gives the average shade value for the portion of the pixel that overlaps the fade zone, which is what a properly anti-aliased renderer should compute.shade_signis +1.0 or -1.0 according to fade direction. The closed form depends onshade_signonly through the sign of two terms:result = ((1 + shade_sign * edge_radius / width) * (a1 - a0) + shade_sign * (a0^2 - a1^2) / (2 * width)) / resolutions- Parameters:
a0 – Lower integration bounds per pixel (km).
a1 – Upper integration bounds per pixel (km).
edge_radius – Fixed edge radius (km).
width – Per-pixel fade width in km. Varies per pixel because
fade_width_km = fade_width_pix * resolutions.resolutions – Per-pixel radial resolution (km/pixel).
shade_sign – +1.0 for shade_above, -1.0 for shade_below.
- Returns:
Per-pixel integral values, same shape as
a0.- Raises:
ValueError – If
a0ora1contain a non-finite value, if array shapes differ, or ifwidthorresolutionscontain a non-finite value or any element that is not strictly positive.
Immutable rendering context for ring feature backplane rendering.
This module defines RingsRenderContext, a frozen dataclass that bundles
all dependencies needed to render one ring feature. Passing a single context
object instead of many individual parameters achieves two goals:
Clean method signatures:
RingFeature.render(context)takes one argument instead of six. Adding a new rendering parameter only requires updatingRingsRenderContext, not every call site.Immutability contract: Because the context is frozen, each call to
render()receives the same data. Features cannot accidentally modify shared rendering state.
RingsRenderContext carries all_edge_radii – the sorted sequence of
(radius, label) pairs for all features that survived filtering. This is needed
at render time by compute_edge_fade to reduce the fade width when a
neighboring edge falls within the fade zone (halving the fade at the conflict
boundary). The filter has already handled exclusion (edges whose adjusted
fade would be too narrow); compute_edge_fade handles reduction (edges
whose adjusted fade is still acceptable but narrower than the requested
fade_width_pix).
- class RingsRenderContext(obs: Any, ring_target: str, epoch: float, resolutions: ndarray[tuple[Any, ...], dtype[floating[Any]]], fade_width_pix: float, all_edge_radii: tuple[tuple[float, str], ...], logger: Any)[source]
Bases:
objectImmutable context for backplane-based ring feature rendering.
Constructed by the orchestrator (
NavModelRings) once per observation and passed unchanged to everyRingFeature.render()call. Contains observation data, computed backplane arrays, fade configuration, and the sorted list of all surviving edge radii for conflict-based fade reduction.The
all_edge_radiituple is built from features that survived all four filter passes. It is used bycompute_edge_fadeto reduce fade width when a neighboring feature’s edge is within the fade zone, preserving the current behavior of halving the fade extent at a conflict boundary rather than rendering with full width. This is a width reduction, not exclusion – exclusion is handled byRingFeatureFilterbefore rendering.- Parameters:
obs – The observation object (
oops.Observation). Provides access to all backplane computation methods.ring_target – Ring target string used for backplane calls, e.g.
'saturn:ring'.epoch – TDB epoch time in seconds used as the reference time for multi-mode orbital perturbation calculations.
resolutions – 2-D array of per-pixel radial resolution in km/pixel. Shape matches the extended FOV. Used to compute per-pixel fade widths:
fade_width_km = fade_width_pix * resolutions.fade_width_pix – Fade extent in pixels as configured in the YAML (
fade_width_pixkey). Must be finite and strictly positive; per-pixel km extent is computed at render time from this value andresolutions.all_edge_radii – Sorted tuple of
(radius_km, edge_label)pairs for all edges of all features that survived filtering. Used bycompute_edge_fadefor conflict detection and width reduction.logger –
PdsLoggerfrom the ringNavModel(same instance asNavModelRings._logger).
Result object for ring feature backplane rendering.
This module defines RingRenderResult, the structured output of
RingFeature.render(). Returning a typed dataclass instead of a tuple of
arrays makes the return value self-documenting and lets the orchestrator access
the uncertainty and annotation data without relying on positional unpacking.
The edge_info_list is computed during rendering rather than in a separate
annotation pass. This avoids recomputing the edge radius backplanes a second
time: the render method already has the computed backplane results in scope
when it creates the edge masks for border_atop.
- class RingRenderResult(model_img: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.floating[~typing.Any]]], model_mask: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.bool]], uncertainty: float, edge_info_list: list[tuple[~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.bool]], str, str]] = <factory>)[source]
Bases:
objectResult of rendering a single ring feature edge or band.
Returned by
RingFeature.render(). Contains the rendered model image and mask, the feature uncertainty (forNavModelResult), and pre-computed annotation edge data.edge_info_listcontains(edge_mask, label_text, edge_label)tuples for annotation creation.render()computes these during rendering to avoid recomputing the edge radius backplanes a second time. The orchestrator passes this list toNavModelRingsBase._create_edge_annotations().- Parameters:
model_img – Float64 array of rendered ring brightness values. Shape matches the extended FOV.
model_mask – Boolean mask array where True indicates pixels with non-zero ring model contribution.
uncertainty – Maximum RMS across all rendered edges (km). Sourced from
RingEdgeData.rmsviaRingFeature.uncertainty.edge_info_list – Pre-computed annotation data: list of
(edge_mask, label_text, edge_label)tuples.edge_maskis a boolean array in extended FOV coordinates.
- class NavModelTitan(name: str, obs: Observation, *, config: Config | None = None)[source]
Bases:
NavModel
- class NavModelCombined(name: str, obs: ObsSnapshot, models: list[NavModel], *, config: Config | None = None)[source]
Bases:
NavModelA NavModel representing a combination of multiple other models.