nav.nav_orchestrator
Orchestrator layer — turns observations into navigation results.
The orchestrator extracts features, gates them by reliability, runs each
feasible technique, and reconciles the per-technique results into one
NavResult.
Modules:
nav_context
NavContext— per-image global state.feature_summary
NavFeatureSummary— per-feature post-mortem entry.image_classifier
NavImageClassifier— quick-fail image-quality classifier.image_classifier_result
NavImageClassifierResult— output of the classifier.image_derivatives
build_image_edge_dt,compute_image_gradient_vu,compute_all_image_derivatives, andImageDerivativesConfig— shared gradient / edge-DT / gradient-vector computation. The combined entry point (compute_all_image_derivatives) shares the heavy gaussian + sobel pass between all three products.provenance
Provenance— reproducibility metadata.ensemble
ensemble— free function that reconciles per-technique results.nav_result
NavResult— final output dataclass.curator
build_metadata_dict— JSON output curation.status_reason_info
STATUS_REASON_INFO_TEMPLATE— per-status_reasonoperator log templates.
NavOrchestrator — top-level driver for autonomous navigation.
The orchestrator turns one observation into one NavResult:
Build a
NavContext(image, masks, classifier verdict, provenance).Iterate registered
NavModelinstances and gather features and annotations from each.Apply the
FeatureReliabilityGateto drop bad-data features.Run every feasible prior-free technique on the surviving features.
Combine pass-1 results via the
ensemblefunction to derive a prior.Run prior-required techniques against the derived prior.
Combine the union of pass-1 and pass-2 results via
ensemble.
Glob-pattern filters at construction time let an operator restrict which
models or techniques run for debugging (only_models='body:MIMAS',
only_techniques='!StarFieldFromCatalogNav').
- class NavOrchestrator(models: list[NavModel], *, config: Config | None = None, only_models: str | list[str] = '*', only_techniques: str | list[str] = '*', ensemble_config: EnsembleConfig | None = None, image_quality_thresholds: ImageQualityThresholds | None = None, image_derivatives_config: ImageDerivativesConfig | None = None, rms_nav_version: str = '0.0.0')[source]
Bases:
NavBaseTop-level driver for autonomous navigation.
- Parameters:
models – List of constructed NavModel instances for one observation. Caller builds these per-image (since each NavModel binds to an
obs).config – Optional
Configoverride.only_models – Glob-pattern string or list selecting which models run. Default
'*'runs every supplied model.only_techniques – Glob-pattern string or list selecting which techniques run. Default
'*'runs every registered technique.ensemble_config – Optional
EnsembleConfigoverride.image_quality_thresholds – Optional thresholds for the image-quality classifier.
rms_nav_version – Version string written into provenance.
- navigate(obs: ObsSnapshotInst) → NavResult[source]
Run the full pipeline on one observation.
- Parameters:
obs – The observation snapshot to navigate.
- Returns:
A single
NavResultsummarizing the navigation outcome.
- prepare(obs: ObsSnapshotInst, *, apply_gate: bool = True) → OrchestratorPrep[source]
Build every per-image artifact a downstream caller might use.
Runs the same pre-technique pipeline as
navigate(): builds the provenance envelope and theNavContext, calls every registered NavModel’screate_model, extracts features, builds the feature inventory, snapshots per-model metadata, and merges annotations. Hard-failure image classes are logged but not short-circuited — the caller (manual-nav dialog, debugger) decides what to do on a blank or saturated frame.- Parameters:
obs – The observation snapshot to prepare.
apply_gate – When
True(the default) the reliability gate runs;OrchestratorPrep.featurescarries only the kept features andfeature_inventoryrecords the gate verdict for both kept and gated entries. WhenFalseevery emitted feature is returned (withfeature_inventorymarking each as un-gated) — used by the manual-nav dialog, where the operator overrides the autonomous reliability decision.
- class OrchestratorPrep(context: NavContext, provenance: Provenance, image_classifier: NavImageClassifierResult, features: list[NavFeature], feature_inventory: list[NavFeatureSummary], model_metadata: dict[str, dict[str, Any]], annotations: Annotations)[source]
Bases:
objectPer-image artifacts produced by
NavOrchestrator.prepare().Bundles every prep-phase output a downstream caller might want. The autonomous
NavOrchestrator.navigate()does not use this struct (it inlines the prep so it can short-circuit on hard-failure images), but the manual-navigation driver does: it wraps the operator’s chosen offset in a fullNavResultpopulated fromprovenance/image_classifier/feature_inventory/model_metadata/annotationsso the manual pipeline writes the same_metadata.jsonand_summary.pngfiles the autonomous pipeline does.- Parameters:
context – NavContext built from the observation.
provenance – Reproducibility envelope shared with the autonomous pipeline.
image_classifier – Image-quality classifier verdict.
features – Either the gated-kept feature list (when
prepare(..., apply_gate=True)) or every emitted feature (whenapply_gate=False). The manual driver always requests the un-gated list because the operator visually overrides gate decisions.feature_inventory – Per-feature summary entries. When the gate ran, both kept and gated features are included with their
gatedflag set accordingly; when the gate was skipped, every entry hasgated=False.model_metadata – Per-NavModel diagnostic dicts keyed by model name.
annotations – Merged annotation collection assembled from every built NavModel’s
to_annotations.
- annotations: Annotations
- context: NavContext
- feature_inventory: list[NavFeatureSummary]
- features: list[NavFeature]
- image_classifier: NavImageClassifierResult
- provenance: Provenance
NavContext — per-image global state shared across extractors and techniques.
Created once per navigation by the orchestrator. Every member is computed without knowing where any feature lives in the image: global statistics, sensor-vs-extfov masks, shared image-side derivatives, and provenance.
The context is frozen. Pass-2 techniques receive a copy with the pass-1
ensemble’s prior offset and covariance attached via with_prior.
- class NavContext(obs: object, image_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]], sensor_mask_ext: ndarray[tuple[Any, ...], dtype[bool]], image_noise_sigma: float, saturation_mask_ext: ndarray[tuple[Any, ...], dtype[bool]], cosmic_ray_mask_ext: ndarray[tuple[Any, ...], dtype[bool]], image_classifier: NavImageClassifierResult, provenance: Provenance, image_gradient_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, image_gradient_vu_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, image_edge_dt_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, prior_offset_px: tuple[float, float] | None = None, prior_covariance_px2: ndarray[tuple[Any, ...], dtype[floating[Any]]] | None = None, pre_filter_applied: NavFilterSpec | None = None, fit_camera_rotation: bool = False, max_rotation_deg: float = 5.0)[source]
Bases:
objectPer-image global state shared across feature extraction and techniques.
The context is frozen;
with_priorreturns a new instance viadataclasses.replacerather than mutating in place.- Parameters:
obs – The observation snapshot under navigation. Typed loosely as
objectto avoid an import cycle; concrete value is anObsSnapshotInstsubclass.image_ext – The extended-FOV image array (post source-image filter).
sensor_mask_ext –
Truewhere the pixel is real sensor data,Falsefor extfov padding.image_noise_sigma – Robust MAD-based noise sigma in the image’s native units (DN for
raw_dninstruments, I/F forcalibrated_if), computed over the entire sensor area. Pixel-threshold consumers (cosmic-ray mask, body-blob noise floor, star detection) use this value directly because they compare against pixel intensities in the same units.saturation_mask_ext –
Truewhere pixels at or above the instrument’s full-well DN.cosmic_ray_mask_ext –
Truewhere single-pixel cosmic-ray spikes were detected.image_classifier – The image-quality classifier’s verdict.
image_gradient_ext – Optional shared Sobel-of-Gaussian magnitude (computed once, reused by every DT-based technique).
image_gradient_vu_ext – Optional
(H, W, 2)per-pixel gradient-vector image ([..., 0]isg_v,[..., 1]isg_u). Sampled by the polarity filter to compare each model vertex’s outward normal against the image’s edge direction.image_edge_dt_ext – Optional shared signed distance transform of the thresholded gradient image.
prior_offset_px – Prior offset from pass 1,
Noneon pass 1.prior_covariance_px2 – Prior offset covariance from pass 1.
pre_filter_applied – NavFilterSpec applied to the source image (for diagnostic provenance),
Noneif none.provenance – Provenance metadata; populated at context creation.
fit_camera_rotation – Per-instrument flag enabling 3-DoF technique fits. When True every technique adds in-plane camera rotation as a third parameter and reports a 3x3 covariance; when False (the default) techniques produce 2-DoF results.
max_rotation_deg – Maximum allowed rotation magnitude (degrees) when
fit_camera_rotationis True; rotation outside the bound triggersat_edge=True. Ignored whenfit_camera_rotationis False.
- image_classifier: NavImageClassifierResult
- pre_filter_applied: NavFilterSpec | None = None
- provenance: Provenance
- with_prior(*, offset_px: tuple[float, float], covariance_px2: ndarray[tuple[Any, ...], dtype[floating[Any]]]) → NavContext[source]
Return a new NavContext with pass-1 prior attached.
with_prioris non-mutating; the existing instance is unchanged.- Parameters:
offset_px –
(dv, du)offset to install as the pass-2 prior.covariance_px2 – 2x2 covariance of that offset, or 3x3 when the pass-1 ensemble produced a rotation-aware result; the top-left 2x2 block is consumed.
- Returns:
New
NavContextwithprior_offset_pxandprior_covariance_px2populated. Only the 2x2 translation block is kept; pass-2 techniques re-derive any rotation prior from the per-instrument flag.- Raises:
TypeError – if
offset_pxentries are non-numeric (cannot be coerced tofloat).ValueError – if
offset_pxis not a length-2 sequence, contains non-finite entries, orcovariance_px2is not square 2x2 / 3x3 or contains non-finite entries.
NavResult — full in-memory output of a single navigation.
Carries the headline (offset ± uncertainty + simple rank) plus full diagnostic information about every technique that ran, every feature that was extracted, and provenance. Not intended to be JSON-serialized directly; the curator builds a curated JSON-friendly subset.
- class NavResult(status: ~typing.Literal['success', 'failed', 'conflicted'], offset_px: tuple[float, float] | None, sigma_px: tuple[float, float] | None, sigma_along_unobservable_px: float | None, confidence_rank: ~typing.Literal['high', 'medium', 'low', 'conflicted', 'failed'], confidence: float, status_reason: ~nav.support.status_reason.NavStatusReason, covariance_px2: ~numpy.ndarray[tuple[~typing.Any, ...], ~numpy.dtype[~numpy.floating[~typing.Any]]] | None, per_technique: list[~nav.nav_technique.technique_result.NavTechniqueResult], feature_inventory: list[~nav.nav_orchestrator.feature_summary.NavFeatureSummary], image_classifier: ~nav.nav_orchestrator.image_classifier_result.NavImageClassifierResult, provenance: ~nav.nav_orchestrator.provenance.Provenance, model_metadata: dict[str, dict[str, ~typing.Any]] = <factory>, annotations: ~nav.annotation.annotations.Annotations = <factory>, rotation_rad: float | None = None, sigma_rotation_rad: float | None = None)[source]
Bases:
objectFull in-memory navigation output for one image.
Constructors
NavResult.success,NavResult.failed, andNavResult.conflictedare the canonical entry points; direct instantiation is also supported.- Parameters:
status – One of
'success','failed','conflicted'.offset_px –
(dv, du)offset;Noneon failure.sigma_px – Per-axis 1sigma marginal uncertainty;
Noneon failure.sigma_along_unobservable_px – Set when covariance is rank-1 (e.g. flat-ring-only scenes);
Nonefor full-rank results.confidence_rank – Five-bucket rank derived from confidence + status.
confidence – Underlying calibrated confidence score in
[0, 1].status_reason – NavStatusReason value explaining the outcome.
covariance_px2 – Full 2x2 covariance (or 3x3 with rotation);
Noneon failure.per_technique – List of every technique’s result (whether kept or dropped by the ensemble).
feature_inventory – Per-feature summary entries — what was extracted, what survived the gate, and why.
image_classifier – The image-quality classifier’s verdict.
model_metadata – Per-NavModel diagnostic dicts keyed by model name.
annotations – Composite annotation collection assembled from every registered NavModel’s
to_annotationsplus orchestrator additions. Empty by default; intended for the summary-PNG renderer.provenance – Reproducibility envelope.
rotation_rad – Optional fitted camera rotation (radians);
Nonewhenfit_camera_rotationis False.sigma_rotation_rad – Optional 1-sigma rotation uncertainty.
- annotations: Annotations
- classmethod conflicted(*, offset_px: tuple[float, float], covariance_px2: ndarray[tuple[Any, ...], dtype[floating[Any]]], confidence: float, per_technique: list[NavTechniqueResult], feature_inventory: list[NavFeatureSummary], image_classifier: NavImageClassifierResult, provenance: Provenance, model_metadata: dict[str, dict[str, Any]] | None = None, annotations: Annotations | None = None) → NavResult[source]
Construct a NavResult for a conflicted (best-group reported) navigation.
confidence_rankis hard-set to'conflicted'; downstream consumers refuse to use these results without explicit opt-in.
- classmethod failed(*, status_reason: NavStatusReason, image_classifier: NavImageClassifierResult, provenance: Provenance, per_technique: list[NavTechniqueResult] | None = None, feature_inventory: list[NavFeatureSummary] | None = None, model_metadata: dict[str, dict[str, Any]] | None = None, annotations: Annotations | None = None) → NavResult[source]
Construct a NavResult for a failed navigation.
- Parameters:
status_reason – Discrete reason from
NavStatusReason.image_classifier – Image-quality classifier verdict.
provenance – Reproducibility envelope.
per_technique – Optional list of technique results (e.g. when every technique returned spurious).
feature_inventory – Optional list of feature summaries.
model_metadata – Optional model metadata dict.
annotations – Optional pre-built annotation collection (typically empty on failure).
- Returns:
NavResult with
status='failed',confidence=0.0,confidence_rank='failed', and no offset.
- feature_inventory: list[NavFeatureSummary]
- image_classifier: NavImageClassifierResult
- per_technique: list[NavTechniqueResult]
- provenance: Provenance
- status_reason: NavStatusReason
- classmethod success(*, offset_px: tuple[float, float], covariance_px2: ndarray[tuple[Any, ...], dtype[floating[Any]]], confidence: float, confidence_rank: Literal['high', 'medium', 'low', 'conflicted', 'failed'], status_reason: NavStatusReason, per_technique: list[NavTechniqueResult], feature_inventory: list[NavFeatureSummary], image_classifier: NavImageClassifierResult, provenance: Provenance, sigma_along_unobservable_px: float | None = None, model_metadata: dict[str, dict[str, Any]] | None = None, annotations: Annotations | None = None, rotation_rad: float | None = None, sigma_rotation_rad: float | None = None) → NavResult[source]
Construct a NavResult for a successful navigation.
Parameters: see dataclass field docs above.
sigma_pxis derived from the diagonal ofcovariance_px2.
NavFeatureSummary — one entry per emitted feature in NavResult.feature_inventory.
Carries enough information about each NavFeature for the curator to write a post-mortem entry into the per-image JSON metadata, including the gate decision (kept vs dropped, reason). It does not carry the heavy bits of a full NavFeature (templates, polylines, covariance) because those would bloat the metadata.
- class NavFeatureSummary(feature_id: str, feature_type: NavFeatureType, source_model: str, reliability: float, gated: bool, gate_reason: str | None, bbox_extfov_vu: tuple[int, int, int, int])[source]
Bases:
objectPer-feature post-mortem entry consumed by the curator.
- Parameters:
feature_id – Unique identifier matching
NavFeature.feature_id. Must be a non-empty string.feature_type – One of the
NavFeatureTypevalues.source_model – Name of the producing NavModel (
'stars','body:MIMAS','rings:SATURN'). Must be non-empty.reliability – Self-assessed reliability score in
[0, 1].gated – True if the reliability gate dropped this feature.
gate_reason – Human-readable reason when
gatedis True;Noneotherwise. Whengatedis True the reason must be non-empty.bbox_extfov_vu – Half-open
(v_min, u_min, v_max, u_max)bounding box in extfov coordinates; a 4-tuple of ints.
- Raises:
TypeError – if any field has the wrong type.
ValueError – if invariants on field values are violated.
- feature_type: NavFeatureType
NavImageClassifier — quick-fail classifier for incoming images.
Operates on the entire sensor area and assigns an image to one of a small set of classes. Most “bad” classes never invoke an extractor — corrupted images fail in milliseconds with a clear reason.
The classifier is global: no predicted feature positions are used. Three cheap statistics drive the decision:
saturation_frac = fraction of pixels at saturation_threshold_dn or above missing_frac = fraction of pixels equal to missing_data_marker_dn noise_sigma = MAD-based image noise sigma
Per-instrument thresholds live in config_4N0_inst_*.yaml; this module
takes them as constructor parameters so it stays pure-Python and unit-
testable without loading config.
- class ImageQualityThresholds(saturation_threshold_dn: float = 4095.0, missing_data_marker_dn: float = 0.0, max_saturation_frac_clean: float = 0.8, max_missing_frac_clean: float = 0.3, partial_dropout_min_frac: float = 0.05, blank_max_dn: float = 5.0, noisy_threshold: float = 10.0)[source]
Bases:
objectPer-instrument configuration for the image-quality classifier.
- Parameters:
saturation_threshold_dn – Pixels at or above this DN are flagged saturated.
missing_data_marker_dn – Pixels exactly equal to this value are treated as missing data (instrument-specific dropout marker).
max_saturation_frac_clean – Above this fraction of saturated pixels the image is
fully_overexposed.max_missing_frac_clean – Above this fraction of missing pixels the image is
mostly_missing_data.partial_dropout_min_frac – Below this missing fraction, no
partial_dropoutflag is raised. At or above this fraction (and belowmax_missing_frac_clean) thepartial_dropoutadvisory flag is set on the result.blank_max_dn – If the image’s max DN is below this, the image is
blank.noisy_threshold – Above this MAD-noise sigma, the
noisyflag is raised (image staysclean).
- class NavImageClassifier(thresholds: ImageQualityThresholds = <factory>)[source]
Bases:
objectQuick-fail image classifier consumed by the orchestrator.
- Parameters:
thresholds – Per-instrument thresholds (see
ImageQualityThresholds).
- classify(image: ndarray[tuple[Any, ...], dtype[floating[Any]]], sensor_mask: ndarray[tuple[Any, ...], dtype[bool]] | None = None, *, missing_frac: float | None = None) → NavImageClassifierResult[source]
Run the classifier and return its verdict.
- Parameters:
image – 2-D float image array (sensor + extfov padding).
sensor_mask – Optional boolean mask selecting sensor pixels; if
None, every pixel is treated as sensor data.missing_frac – Optional pre-computed missing-data fraction over the sensor pixels. The orchestrator supplies this from the true missing mask (which handles the calibrated-IF
NaNsentinel before the array is sanitised for the finite-only derivative path); whenNonethe classifier derives the fraction itself frommissing_data_marker_dn(usingnp.isnanwhen the marker is itselfNaN).
- Returns:
NavImageClassifierResult.
- Raises:
TypeError – if
imageis not 2-D.
- thresholds: ImageQualityThresholds
NavImageClassifierResult — output of the image-quality classifier.
Carried on every NavResult (success or failure) so downstream consumers
can see at a glance which class the input image fell into and which optional
flags were set.
- class NavImageClassifierResult(image_class: ~typing.Literal['clean', 'blank', 'fully_overexposed', 'mostly_missing_data', 'corrupt'], saturation_frac: float, missing_frac: float, noise_sigma: float, max_dn: float, flags: list[~typing.Literal['partial_dropout', 'noisy']] = <factory>)[source]
Bases:
objectThe image-quality classifier’s verdict for one image.
noisyis a flag, not a class — a noisy-but-clean image isimage_class='clean', flags=['noisy']. Images whose file fails to read getimage_class='corrupt'.- Parameters:
image_class – One of the
ImageClassliteral values.saturation_frac – Fraction of pixels at or above the saturation DN.
missing_frac – Fraction of pixels equal to the missing-data marker.
noise_sigma – Per-image MAD-based noise sigma (DN units).
max_dn – Maximum DN observed in the image.
flags – Additional flags caveats (independent of
image_class).
Shared image-side derivatives consumed by every DT-based technique.
The orchestrator computes one gradient-magnitude image, one gradient-vector image, and one signed distance-transform image per navigation; every limb / terminator / ring-edge technique then samples those products at its own model polylines. Computing them once keeps the per-image cost bounded regardless of how many DT techniques run.
Three quantities are produced and attached to the per-image
NavContext:
image_gradient_extGradient magnitude of the source image after a Gaussian blur at
ImageDerivativesConfig.image_gradient_sigma_px. Used by the technique-side polarity / gating logic when only a magnitude readout is needed.image_gradient_vu_extPer-pixel
(g_v, g_u)gradient vector after the same Gaussian blur. Sampled by the polarity filter to compare the image’s edge direction with the model’s outward normal at each polyline vertex.image_edge_dt_extDistance transform of the binarised gradient image, with the threshold chosen as
edge_threshold_k_sigma * image_noise_sigma. The DT is truncated atDEFAULT_DT_HALF_WIDTH_PXso the per-pixel cost is bounded for the DT-based techniques’ Levenberg-Marquardt step.
The thresholding intentionally treats every edge pixel as a candidate; the
per-technique polarity filter rejects matches that disagree on the gradient
direction. A non-empty gradient image always produces a fully-defined DT
because build_image_edge_dt() falls back to a pure-saturation array
when no pixel exceeds the threshold.
- DEFAULT_DT_HALF_WIDTH_PX: float = 64.0
Maximum distance returned by the truncated distance transform.
Pixels farther than this from any thresholded gradient pixel saturate at
DEFAULT_DT_HALF_WIDTH_PX. The cap bounds the LM cost contribution from vertices that fall in completely-empty regions of the image and bounds the DT array’s working range to a documented maximum.
- DEFAULT_EDGE_THRESHOLD_K_SIGMA: float = 4.0
Default gradient-magnitude threshold expressed as multiples of MAD noise.
Pixels whose smoothed-gradient magnitude exceeds
edge_threshold_k_sigma * image_noise_sigmaare kept as edge candidates. A 4-sigma threshold keeps single-pixel noise excursions out of the DT input while letting limb / terminator / ring edges through with margin to spare.
- DEFAULT_IMAGE_GRADIENT_SIGMA_PX: float = 1.2
Default Gaussian sigma (pixels) used to smooth before the gradient operator.
A 1.2 pixel sigma matches the typical instrument PSF; below that the gradient is dominated by single-pixel noise, above it sharp limbs blur out and the DT loses contrast against the background.
- class ImageDerivativesConfig(image_gradient_sigma_px: float = 1.2, edge_threshold_k_sigma: float = 4.0, dt_half_width_px: float = 64.0)[source]
Bases:
objectConfiguration for the shared gradient + DT computation.
- Parameters:
image_gradient_sigma_px – Gaussian sigma (pixels) used before the gradient operator. Both axes share the same value; anisotropic blur is intentionally not exposed here because the image-side computation must be feature-agnostic.
edge_threshold_k_sigma – Multiples of
image_noise_sigmaused to threshold the gradient magnitude into a binary edge mask.dt_half_width_px – Cap on the DT distance. Vertices farther than this from any edge pixel see a constant cost.
- Raises:
ValueError – if any field is not strictly positive.
- build_image_edge_dt(image_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]], image_noise_sigma: float, *, config: ImageDerivativesConfig | None = None) → tuple[ndarray[tuple[Any, ...], dtype[floating[Any]]], ndarray[tuple[Any, ...], dtype[floating[Any]]]][source]
Compute the shared gradient and edge-distance-transform images.
Two independent products are returned, both aligned to
image_ext:The gradient magnitude after a Gaussian smooth at
config.image_gradient_sigma_px.The distance transform of the thresholded gradient image, where the threshold is
config.edge_threshold_k_sigma * image_noise_sigmaand the result is truncated atconfig.dt_half_width_px.
For callers (the orchestrator) that need both this product and the gradient-vector product, prefer
compute_all_image_derivatives()so the heavy smoothing + Sobel pass runs once instead of twice.- Parameters:
image_ext – Extended-FOV image array (post any source-image filter). Must be 2-D.
image_noise_sigma – MAD-derived noise sigma (image-native units: DN or I/F) used to scale the gradient threshold. Must be finite and non-negative.
config – Optional override; when
Nonethe documented defaults apply.
- Returns:
Tuple
(gradient_ext, edge_dt_ext)of float64 arrays, each shaped likeimage_ext.- Raises:
TypeError – if
image_extis not 2-D.ValueError – if
image_extcontains NaN or +/-inf, or ifimage_noise_sigmais negative or non-finite.
- compute_all_image_derivatives(image_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]], image_noise_sigma: float, *, config: ImageDerivativesConfig | None = None) → tuple[ndarray[tuple[Any, ...], dtype[floating[Any]]], ndarray[tuple[Any, ...], dtype[floating[Any]]], ndarray[tuple[Any, ...], dtype[floating[Any]]]][source]
Compute every image-side derivative the orchestrator needs in one pass.
Returns the same products that
build_image_edge_dt()andcompute_image_gradient_vu()produce separately, but shares the single gaussian + sobel pass between them. The orchestrator’s per-image setup uses this entry point so the heavy smoothing only runs once even though three derivative products end up on theNavContext.- Parameters:
image_ext – Extended-FOV image array (post any source-image filter). Must be 2-D.
image_noise_sigma – MAD-derived noise sigma (image-native units: DN or I/F) used to scale the gradient threshold. Must be finite and non-negative.
config – Optional override; when
Nonethe documented defaults apply.
- Returns:
gradient_ext— gradient magnitude shaped(H, W).edge_dt_ext— truncated distance transform of the thresholded edge mask, shaped(H, W).gradient_vu_ext— signed(g_v, g_u)gradient vector image, shaped(H, W, 2).
- Return type:
Tuple
(gradient_ext, edge_dt_ext, gradient_vu_ext)- Raises:
TypeError – if
image_extis not 2-D.ValueError – if
image_extcontains NaN or +/-inf, or ifimage_noise_sigmais negative or non-finite.
- compute_image_gradient_vu(image_ext: ndarray[tuple[Any, ...], dtype[floating[Any]]], *, sigma_px: float = 1.2) → ndarray[tuple[Any, ...], dtype[floating[Any]]][source]
Compute the per-pixel
(g_v, g_u)gradient vector image.The image is first smoothed with an isotropic Gaussian of standard deviation
sigma_px; gradients are then computed by Sobel along each axis. The output preserves the sign of the gradient — required by the polarity filter, which compares the image gradient direction to the model’s outward normal.For callers (the orchestrator) that need this product alongside the gradient magnitude / DT, prefer
compute_all_image_derivatives()so the heavy smoothing + Sobel pass runs once instead of twice.- Parameters:
image_ext – Extended-FOV image array. Must be 2-D.
sigma_px – Gaussian sigma (pixels) used before the Sobel operator. Must be strictly positive.
- Returns:
(H, W, 2)float64 array; the last axis stacks the v-component and the u-component of the gradient (out[..., 0]isg_v,out[..., 1]isg_u).- Raises:
TypeError – if
image_extis not 2-D.ValueError – if
image_extcontains NaN or +/-inf, or ifsigma_pxis not strictly positive.
Provenance — reproducibility metadata attached to every NavResult.
Two navigations with identical inputs produce byte-identical
Provenance except for pipeline_run_iso8601, which is wall-clock
by construction; regression-baseline comparison strips that field before
comparing.
The collect_provenance_metadata() helper produces the per-image
rms_nav_git_sha, loaded-SPICE-kernel list, and the static-data hash
dictionary at navigate time so the orchestrator can populate the
Provenance envelope without each caller re-implementing the lookups.
- class Provenance(rms_nav_version: str, image_et: float, pipeline_run_iso8601: str, rms_nav_git_sha: str | None = None, spice_kernels: tuple[str, ...]=(), static_data_hashes: Mapping[str, str]=<factory>, technique_names: tuple[str, ...]=(), extractor_names: tuple[str, ...]=())[source]
Bases:
objectReproducibility envelope written into every NavResult.
- Parameters:
rms_nav_version –
__version__string (e.g.'0.5.2').rms_nav_git_sha – Short git SHA,
'dirty', orNoneif neither can be determined.spice_kernels – Sorted tuple of SPICE kernel filenames actually loaded (from
spice.ktotal/spice.kdata).static_data_hashes – Mapping
filename -> sha256(raw bytes)for static-data YAMLs (config_220_body_shape.yaml, everyconfig_3N0_*_rings.yaml, everyconfig_4N0_inst_*.yaml). Comments and whitespace are included in the hashed bytes. Stored as a read-onlyMappingProxyTypeafter construction.technique_names – Sorted tuple of registered technique class names.
extractor_names – Sorted tuple of registered extractor class names.
image_et – Observation midtime ET (TDB seconds past J2000).
pipeline_run_iso8601 – UTC timestamp when the run began. Excluded from byte-identical regression-baseline comparison because it varies wall-clock-to-wall-clock for identical inputs.
The non-init field
spice_kernel_countis derived fromlen(spice_kernels)in__post_init__.
- class ProvenanceMetadata(git_sha: str | None, spice_kernels: tuple[str, ...], static_data_hashes: Mapping[str, str])[source]
Bases:
objectThe per-image runtime-derived provenance fields.
- Parameters:
git_sha – Short git SHA of the repository,
'dirty'if there are uncommitted changes, orNoneif not available.spice_kernels – Sorted tuple of SPICE kernel filenames actually loaded.
static_data_hashes – Mapping of static-data YAML filename to sha256-hex digest of the file’s raw bytes.
- collect_provenance_metadata() → ProvenanceMetadata[source]
Gather process-wide provenance metadata at navigate time.
- Returns:
A
ProvenanceMetadatainstance populated with the current git SHA, loaded SPICE kernel list, and static-data hashes.
Ensemble — reconcile per-technique results into a single NavResult.
The ensemble is the single point in the pipeline where multiple per-technique estimates become one offset. Every step is honest:
Drop
spurious=Trueresults.Drop
at_edge=Trueresults unless dropping them would empty the set.Group remaining results by Mahalanobis-distance agreement (single-link).
Pick the highest summed-confidence group.
Combine offsets within that group via precision-weighted (Kalman-style) merging.
Apply optional disagreement / conflict penalties.
Emit a NavResult.
The ensemble is tested in isolation against synthetic per-technique results; correctness here is what makes the rest of the pipeline trustworthy.
- class EnsembleConfig(agreement_sigma: float = 2.0, agreement_pixel_floor: float = 5.0, agreement_gap: float = 0.5, disagreement_penalty: float = 0.7, conflicted_confidence_multiplier: float = 0.3, min_confidence: float = 0.2, pinvh_rcond: float = 1e-09, max_allowed_rotation_deg: float = 5.0, tier_thresholds: dict[str, dict[str, float | None]]=<factory>)[source]
Bases:
objectTunable parameters of the ensemble combine.
Defaults match
config_540_orchestrator.yaml.- Parameters:
agreement_sigma – Mahalanobis-distance threshold for grouping.
agreement_pixel_floor – Translation-distance fallback grouping threshold in pixels. Two results are grouped when either their Mahalanobis distance is at most
agreement_sigmaor their Euclidean translation distance is at most this many pixels. The pixel floor exists because per-technique covariances are CRLB-tight (FFT subpixel localization forBodyDiscCorrelateNav, M-estimator information for the DT-fit techniques), well below the actual position uncertainty driven by model error and pointing residuals; without the floor, results agreeing visually to a few px register as hundreds of sigmas apart and never group. Set to0.0to disable the floor.agreement_gap – Minimum summed-confidence gap between best and runner-up groups before declaring a conflict.
disagreement_penalty – Multiplier on combined confidence when more than one group existed.
conflicted_confidence_multiplier – Additional multiplier when the conflicted branch fires.
min_confidence – Final-result threshold below which the ensemble returns NavResult.failed instead of NavResult.ok.
pinvh_rcond – rcond for
scipy.linalg.pinvh.max_allowed_rotation_deg – Maximum magnitude (in degrees) a 3-DoF result’s rotation may take before the ensemble rejects it. The rotation parameter is combined as a small angle (circular mean of
(dv, du, theta)triples); this bound enforces the small-angle assumption that every contributing technique fits against (every DT/star technique clamps its rotation to+-max_rotation_deg, default 5 deg). A 3-DoF result arriving withabs(rotation_rad)at or above this bound is a programming error upstream and trips an assertion.tier_thresholds – Mapping
rank -> {min_confidence, max_sigma_px}; seederive_confidence_rank.
- derive_confidence_rank(*, confidence: float, sigma_px: tuple[float, float] | None, tier_thresholds: dict[str, dict[str, float | None]] | None = None) → Literal['high', 'medium', 'low', 'conflicted', 'failed'][source]
Derive the five-bucket confidence rank from confidence + sigma.
max_sigma_pxcomparesmax(sigma_dv, sigma_du)only.high/medium/lowtiers require both confidence and sigma constraints;conflictedandfailedare status-driven and not chosen here.- Parameters:
confidence – Combined confidence in
[0, 1].sigma_px – Per-axis 1sigma marginal uncertainty (use
Noneto mean “unknown / not applicable”).tier_thresholds – Mapping
rank -> {min_confidence, max_sigma_px}withmax_sigma_pxallowed to beNone.
- Returns:
'high','medium', or'low'if any tier matches; else'failed'.
- ensemble(results: list[NavTechniqueResult], *, feature_inventory: list[NavFeatureSummary], image_classifier: NavImageClassifierResult, provenance: Provenance, config: EnsembleConfig | None = None, model_metadata: dict[str, dict[str, Any]] | None = None, annotations: Annotations | None = None) → NavResult[source]
Reconcile per-technique results into a single NavResult.
- Parameters:
results – Per-technique results from one or both passes.
feature_inventory – Feature inventory (kept + gated entries).
image_classifier – Image-quality classifier verdict.
provenance – Reproducibility envelope.
config – Optional
EnsembleConfigoverrides.model_metadata – Optional per-NavModel diagnostic dict map.
annotations – Optional pre-built annotation collection from the orchestrator’s
_collect_annotationspass.
- Returns:
A single NavResult — ok / conflicted / failed.
Curator — turn a NavResult into a JSON-friendly metadata dict.
The curator picks JSON-friendly fields from a NavResult, rounds floats to
documented precision, replaces inf with the JSON_INF_SENTINEL finite
sentinel, and emits the navigation_result block consumed by downstream
readers.
Every per-technique diagnostic field that ships in the JSON appears in the
technique’s CURATOR_FIELDS allow-list; assert_diagnostic_fields_present
fails CI if a new diagnostic field is added without updating the allow-list.
- assert_diagnostic_fields_present(result: NavResult) → None[source]
Verify every per-technique diagnostic field has a CURATOR_FIELDS entry.
An unmapped field is a programmer error and raises
AssertionErrorso CI fails the build before a new diagnostic silently disappears from the JSON output.- Parameters:
result – NavResult whose per_technique entries are inspected.
- Raises:
AssertionError – if any field on a diagnostic dataclass is missing from its
CURATOR_FIELDSallow-list.
- build_metadata_dict(result: NavResult) → dict[str, Any][source]
Build the additive
navigation_resultmetadata block.Output is a JSON-serializable dict; the orchestrator merges it into the existing per-image
_metadata.jsonschema as the additivenavigation_resultkey.- Parameters:
result – The full in-memory NavResult.
- Returns:
Dict ready for
json.dump.- Raises:
AssertionError – if any diagnostic field is missing a
CURATOR_FIELDSentry.
Per-NavStatusReason operator-readable INFO log templates.
The orchestrator emits one INFO line per status_reason summarizing the final outcome. Templates here let tests assert the operator-readable narrative for every reason.
Per-instrument configuration consumed by the orchestrator.
Each ObsSnapshotInst carries its already-resolved per-camera config
mapping on obs.inst_config (set by ObsInst.from_file). This
module reads the data_units, noise, and
image_quality_thresholds blocks defined in
config_4N0_inst_*.yaml and returns the values the orchestrator
needs.
The returned InstrumentSettings is a plain dataclass so the
orchestrator can branch on data_units without re-reading raw YAML.
- class InstrumentSettings(data_units: Literal['raw_dn', 'calibrated_if'], saturation_dn: float | None, marker_value: float, thresholds: ImageQualityThresholds, fit_camera_rotation: bool, max_rotation_deg: float)[source]
Bases:
objectPer-instrument settings the orchestrator needs at navigate time.
- Parameters:
data_units –
raw_dnorcalibrated_ifperconfig_4N0_inst_*.yaml.saturation_dn – Per-instrument saturation DN;
Nonefor calibrated-IF instruments.marker_value – Missing-data sentinel value (
0for most raw instruments;NaNfor calibrated-IF).thresholds – Image-quality thresholds for the classifier. All values normalised to the appropriate units (DN for
raw_dn, I/F forcalibrated_if).fit_camera_rotation – Per-camera flag enabling 3-DoF technique fits.
max_rotation_deg – Maximum allowed rotation magnitude when
fit_camera_rotationis True.
- thresholds: ImageQualityThresholds
- instrument_settings_from_obs(obs: Any) → InstrumentSettings[source]
Build the orchestrator’s per-instrument settings from an obs.
Reads
obs.inst_config(anAttrDictpopulated byObsInst.from_file) and returns a frozenInstrumentSettingsinstance. Wheninst_configisNone(test fixtures, simulated obs without per-instrument wiring), defaults appropriate for an untypedraw_dninstrument are returned:saturation_dn=None(no saturation mask) and the standardImageQualityThresholdsdefaults so the legacy code path stays unchanged.- Parameters:
obs – An
ObsSnapshotInst(or a stand-in carrying theinst_configattribute).- Returns:
An
InstrumentSettingsinstance.- Raises:
ValueError – If
inst_configis supplied but missing required fields, or carries an unrecogniseddata_unitsvalue.