Observations
Overview
The nav.obs subsystem wraps an oops snapshot in a
navigation-aware class that adds backplane caching, extended-FOV
accessors, image masks, and per-instrument calibration hooks. Every
navigation pipeline takes an
ObsSnapshotInst instance as its
input and reads the per-image data, geometry, and instrument-specific
calibration through that object.
The class hierarchy splits responsibility across three axes:
Obs— abstract observation root, wiresNavBaseinto theoopsclass tree so every concrete observation inheritsconfigandlogger.ObsSnapshot— extendsObsandoops.observation.snapshot.Snapshot. Adds the FOV / extended-FOV accessors, backplane caching, and the per-image mask helpers that every navigation model and technique consumes.ObsInst— abstract mix-in carrying per-instrument calibration: thefrom_fileconstructor contract, the optical PSF, the per-instrument visual-magnitude window, and the per-image public-metadata projection.ObsSnapshotInst— concrete mix-in ofObsSnapshotandObsInst. Per-mission subclasses derive from this base.
ObsSnapshot
ObsSnapshot is the navigation-side wrapper
around an oops snapshot. It exposes three families of helpers:
FOV / extended-FOV geometry.
data_shape_uv()/data_shape_vu()report the sensor shape;fov_v_min()/fov_v_max()/fov_u_min()/fov_u_max()give the in-sensor pixel bounds;extfov_margin_v()/extfov_margin_u()give the per-axis margin appended byInstrumentSettings; the correspondingextfov_*accessors give the extended bounds and shape.clip_fov()/clip_extfov()clamp(u, v)coordinates into either grid;clip_rect_fov()/clip_rect_extfov()clamp full rectangles.Mask and template constructors.
make_fov_zeros()/make_extfov_zeros()allocate float arrays of the right shape;make_extfov_false()allocates the boolean equivalent;unpad_array_to_extfov()crops a sensor-shaped array down to the extended-FOV grid.extfov_data_sensor_mask()returns a boolean mask that isTruewhere the extended-FOV pixel corresponds to a real sensor pixel andFalsein the margin.Inventory predicates.
inventory_body_in_fov()/inventory_body_in_extfov()consume anoopsinventory entry and return whether the predicted body bounding box overlaps the sensor / extended FOV. Per-NavModelinstances_for_obs()hooks (e.g.instances_for_obs()) call these to decide which bodies to instantiate.
Backplane caching and thread safety
Backplanes are cached in the underlying oops snapshot, so repeated
queries of the same backplane on the same
ObsSnapshot reuse the prior computation.
The cache is mutating state attached to the snapshot itself: every call
to backplane() (or to any helper
that builds an oops.Backplane from the snapshot, including
reproject() and
create_cartographic_model())
allocates per-quantity arrays inside the snapshot’s cache and reads
back any entries already present.
A single ObsSnapshot is therefore not
safe for concurrent use across threads. Two threads that simultaneously
sample backplanes through the same snapshot can race on the cache and
return inconsistent or partially-populated arrays. Code that needs to
parallelise over a single image must give each thread its own
from_file() -constructed snapshot
instance; the navigation pipeline runs serially per image, so the
single-threaded contract is sufficient for the orchestrator’s own use.
ObsInst
ObsInst is the per-instrument calibration
mix-in. It defines the abstract contract every per-mission subclass
must implement:
from_file()— load an image file and return the matchingObsSnapshotInst. Subclasses delegate the actual decode tooops.hosts.<mission>.<inst>.from_file, then wrap the resultingoopssnapshot.star_psf()— returns the per-instrument opticalPSF(typically aGaussianPSF). Used byNavModelStarsto predict the per-star detection footprint.star_psf_size()— returns the per-star kernel support rectangle in pixels.star_min_usable_vmag()/star_max_usable_vmag()— the per-instrument photometric window. Stars outside this window do not contribute predicted detections.get_public_metadata()— returns a JSON-friendly dict of per-image metadata fields (mission, instrument, exposure, filter wheel positions, etc.) for the per-image sidecar.
The inst_config property exposes the per-instrument YAML block
loaded from src/nav/config_files/config_4N0_inst_*.yaml so subclass
methods can read instrument-specific knobs without hard-coding them.
Per-instrument subclasses
Concrete subclasses live in src/nav/obs/ and are registered (via
the nav.obs package’s __init__.py) under a per-mission /
per-instrument key consumed by DataSet.
Shipping subclasses:
ObsCassiniISS— Cassini ISS NAC and WAC. Delegates tooops.hosts.cassini.iss.from_file.ObsVoyagerISS— Voyager 1 / 2 ISS NA and WA cameras. Delegates tooops.hosts.voyager.iss.from_file.ObsGalileoSSI— Galileo SSI (usesfull_fov=Trueto read the full sensor regardless of the on-chip ROI). Delegates tooops.hosts.galileo.ssi.from_file.ObsNewHorizonsLORRI— New Horizons LORRI (passescalibration=Falseso the raw pixel values pass through). Delegates tooops.hosts.newhorizons.lorri.from_file.ObsSim— simulated-image observation backed by a description of bodies and stars, consumed bynav_create_simulated_imageand the simulated-image GUI driver.
Each subclass overrides from_file() to pull the right
oops host, wires up the per-instrument PSF and photometric window, and forwards
per-image metadata into get_public_metadata().
Simulated-image instrument config: inherit / override / self-specify
A simulated scene names an instrument (coiss_nac, vgiss, …) so its
rendered frame and its ObsSim go through the same
per-instrument units, noise, saturation, and PSF the navigator applies to a real
frame. resolve_sim_inst_config() maps that name to the
matching config_4N0_inst_*.yaml block (or the standalone sim block for the
generic alias).
A scene may additionally carry an instrument_config mapping that is
deep-merged over the resolved block (nested mappings merge key-by-key; scalars
and lists replace). This gives three modes:
Inherit – omit
instrument_config; every physical parameter tracks the named instrument’s config.Override – supply only the keys to change; those are pinned to the scene and the rest still track the instrument.
Self-specify – name
genericand override everything; the scene’s config is fully its own.
Why it exists, and what breaks without it. A sim scene used as a navigation
fixture wants reproducible behavior. If every parameter is inherited live from a
real camera’s config, then editing that camera’s star_psf_sigma (or noise,
saturation, …) silently shifts the rendered image and the recovered offset of
every sim scene that names it – re-blessing baselines for a change that had
nothing to do with the simulator. Pinning a key via instrument_config decouples
it from the camera config: the merge produces a fresh dict, so a later camera-config
edit cannot reach a pinned key. Full self-specification (generic + complete
overrides) makes a scene immune to all instrument-config drift. The merge is
applied identically in both consumers – ObsSim.from_file and
render_combined_model() – so the rendered image and the
navigator’s instrument settings stay consistent. The one precedence subtlety: the
top-level scene noise block (the primary noise control) still wins over
instrument_config.noise for rendering, so instrument_config is the channel
for the instrument parameters that have no dedicated scene field (star PSF sigma,
data units, saturation / full-well DN, extfov margin, …).
Adding a new instrument
The end-to-end checklist lives at Extending the System; the obs-side bullet is:
Subclass
ObsSnapshotInstinsrc/nav/obs/obs_inst_<mission>_<inst>.py.Implement
from_file()(delegate to the matchingoops.hosts.<mission>.<inst>host module),star_psf(),star_min_usable_vmag(),star_max_usable_vmag(), andget_public_metadata().Register the subclass in
nav.obs(src/nav/obs/__init__.py) under the per-mission / per-instrument key the correspondingDataSetsubclass passes tofrom_file().Add the per-instrument config block at
src/nav/config_files/config_4N0_inst_<mission>_<inst>.yamlsoinst_configcarries the instrument’s tuning knobs.
API reference
The autodocumented API surface is at nav.obs.