Extending the System
Adding a new dataset
To add a dataset, create a class in src/nav/dataset/ that inherits from
DataSet (or from
DataSetPDS3 for archives). Implement
_img_name_valid(), the file-yielding
methods, and
add_selection_arguments() to expose CLI
selection flags. Register the dataset in src/nav/dataset/__init__.py so
it becomes available to the CLI.
Example:
from nav.dataset.dataset_pds3 import DataSetPDS3
class DataSetNewInstrument(DataSetPDS3):
def __init__(self, *, config=None):
super().__init__(config=config)
@staticmethod
def _img_name_valid(name: str) -> bool:
return name.startswith("NEW") and name.endswith(".IMG")
The dataset will automatically be available to the CLI once registered.
Implementing PDS4 bundle generation methods
To support PDS4 bundle generation, datasets override the PDS4 hook methods on
DataSet. The base implementations are
non-abstract; each raises NotImplementedError so that a dataset that
cannot be packaged as a PDS4 bundle simply leaves them unimplemented.
pds4_bundle_template_dir(): returns the
absolute path to the template directory for PDS4 label generation. If a
relative name is provided in config, it should be resolved relative to
src/pds4/templates/.
pds4_bundle_name(): returns the bundle
name (e.g. "<instrument_name>_backplanes_rsfrench2027").
pds4_bundle_path_for_image(): maps an
image name to a bundle directory path (e.g.
"1234xxxxxx/123456xxxx"). This is a static method.
pds4_path_stub(): returns the full path
stub including directory and filename prefix (e.g.
"1234xxxxxx/123456xxxx/1234567890w").
pds4_template_variables(): returns a
dictionary mapping template variable names to values for PDS4 label
generation. This should extract values from navigation metadata, backplane
metadata, and PDS3 index rows (if available).
pds4_image_name_to_data_lid(): converts
an image name to a data product LID. Returns a full LID string (e.g.
"urn:nasa:pds:<bundle_name>:data:<image_name>").
pds4_image_name_to_data_lidvid():
converts an image name to a data product LIDVID.
pds4_image_name_to_browse_lid():
converts an image name to a browse product LID.
pds4_image_name_to_browse_lidvid():
converts an image name to a browse product LIDVID.
For datasets that do not support PDS4 bundle generation, leaving these
methods unimplemented (so they inherit the base raise
NotImplementedError) is the supported pattern. See
DataSetPDS3CassiniISS for a
complete implementation example.
Adding a new instrument
To add an instrument, implement a subclass of
ObsSnapshotInst in src/nav/obs/ that
provides from_file() and
any instrument-specific helpers. Update the instrument registry in
src/nav/obs/__init__.py so datasets can resolve the instrument class.
from nav.obs.obs_snapshot_inst import ObsSnapshotInst
from nav.support.types import PathLike
class ObsNewInstrument(ObsSnapshotInst):
def __init__(self, obs, *, config=None, **kwargs):
super().__init__(obs, config=config, **kwargs)
@classmethod
def from_file(
cls,
path: PathLike,
*,
config=None,
extfov_margin_vu=None,
**kwargs,
):
...
Adding a new NavModel
NavModel subclasses self-register via
__init_subclass__. To add a new predicted-scene generator:
Create a class in src/nav/nav_model/ inheriting from
NavModel (or from one of the abstract
bases such as
NavModelBodyBase).
Implement create_model(),
to_features(), and
to_annotations().
Override instances_for_obs() if
the model auto-instantiates per-observation (one instance per body in
FOV, one per planet with visible rings, one stars model). Subclasses
that require operator parameters (simulated models populated from GUI
JSON) inherit the empty default; the caller constructs them directly.
from nav.nav_model.nav_model import NavModel
class NavModelNewFeature(NavModel):
def __init__(self, name, obs, *, config=None):
super().__init__(name, obs, config=config)
def create_model(self) -> None:
...
def to_features(self, context) -> list[NavFeature]:
...
def to_annotations(self, context) -> Annotations:
...
Adding a new NavTechnique
NavTechnique subclasses also
self-register via __init_subclass__.
Create a class in src/nav/nav_technique/ inheriting from
NavTechnique.
Set the class attributes
name,
accepts_feature_types,
and (if relevant)
requires_prior.
Implement is_feasible()
(must read feature metadata only, no pixels) and
navigate().
from nav.feature.feature_type import NavFeatureType
from nav.nav_technique.feasibility import NavFeasibilityReport
from nav.nav_technique.nav_technique import NavTechnique
from nav.nav_technique.technique_result import NavTechniqueResult
class NavTechniqueNewMethod(NavTechnique):
name = 'NavTechniqueNewMethod'
accepts_feature_types = frozenset({NavFeatureType.STAR})
def is_feasible(self, features):
return NavFeasibilityReport(feasible=len(features) >= 1, reason='ok')
def navigate(self, features, context) -> NavTechniqueResult:
...
The technique becomes visible to
NavOrchestrator as soon as the
module is imported. Glob filters on the orchestrator (only_techniques)
let you exclude or single out a technique without modifying the registry.
Adding to the image library via the manual-nav dialog
The interactive
ManualNavDialog (built by
NavTechniqueManual)
exposes a Save as Library Entry… button alongside the OK / Cancel
controls. Clicking it captures the current dv/du and writes a sidecar
seeded with the auto-fillable fields plus TODO_REPLACE_*
placeholders for the operator-curated ones (scene_tags,
primary_technique, notes). The YAML helper lives in
nav.ui.library_entry.
Operator workflow:
Open the manual-nav dialog on the candidate image and pick the
offset by hand (or accept Auto).
Click Save as Library Entry…. The save-file dialog suggests
<image_id>.yaml as the filename. Point it at the appropriate
scene-class directory under
tests/integration/image_library/images/<class>/. A companion
<image_id>.png capturing the red-image / green-model overlay
at the chosen (dv, du) is dropped next to the YAML so future
reviewers can see the scene at a glance; it is not consumed by any
test.
Open the saved YAML and fill in every TODO_REPLACE_* value. An
unedited template trips
tests.integration.sidecar.load_sidecar() so CI fails loudly if
you forget.
Run the structural-invariants test
(pytest tests/integration/test_image_library.py); the per-image
regression test (test_autonomous_nav.py) follows once
PDS3_HOLDINGS_DIR is set.
See Image Library for the sidecar schema and
the deeper rationale behind the curation policy.