Introduction

This is the developer manual for RMS-NAV, the spacecraft image-navigation system distributed as the rms-nav PyPI package. The user manual lives under User Guide (one chapter per CLI driver); this manual is for contributors and maintainers — people who modify, extend, build, test, or release the package.

Package overview

RMS-NAV ingests images from Cassini ISS, Voyager 1/2 ISS, Galileo SSI, and New Horizons LORRI; predicts the per-pixel scene geometry from SPICE kernels; and reports the offset between the predicted and observed scene that brings the two into alignment. Downstream the same package generates per-pixel backplanes, PDS4 bundles, body / ring mosaics, and reprojections.

The runtime is pure Python (3.11+), with NumPy / SciPy for numerics, oops for geometry and SPICE access, starcat / psfmodel for star navigation, filecache for transparent local-or-remote file access, and PyQt6 for the optional manual-nav and mosaic-viewer GUIs. No GPU is required.

Repository layout

The repo is a standard src-layout Python package:

rms-nav/
  pyproject.toml              # PEP 621 metadata + tool config (ruff/mypy/pytest)
  README.md                   # PyPI-facing summary
  CONTRIBUTING.md             # contributor checklist
  scripts/
    run-all-checks.sh         # parallel wrapper: ruff + mypy + pytest + sphinx + pymarkdown
  src/
    nav/                      # importable top-level package (`import nav`)
      config/                 # YAML loader + DEFAULT_CONFIG + logger + path helpers
      config_files/           # bundled YAML defaults (numeric-prefix load order)
      dataset/                # DataSet + per-mission subclasses (file enumeration)
      obs/                    # Obs / ObsSnapshot / ObsInst / per-instrument
      feature/                # NavFeature dataclass + geometry / flags / reliability
      nav_model/              # NavModel + per-family subpackages (stars, rings, ...)
      nav_technique/          # NavTechnique + per-technique subclasses + dt_fitting
      nav_orchestrator/       # NavOrchestrator + ensemble + curator + classifier
      annotation/             # Annotation / Annotations (summary-PNG overlay)
      reproj/                 # BodyMosaic, RingMosaic, cartographic_model
      sim/                    # simulated-image renderer
      support/                # cross-cutting helpers (filters, types, file, time, ...)
      ui/                     # PyQt6 widgets (manual nav dialog, mosaic viewer)
      navigate_image_files.py # top-level driver called by nav_offset
    backplanes/               # per-pixel geometry products (driven by nav_backplanes)
    pds4/                     # PDS4 bundle generation (driven by nav_create_bundle)
    reproj_cli/               # CLI-only helpers shared by mosaic drivers
    main/                     # CLI entry-point dispatch modules
  tests/
    nav/                      # unit tests, mirror src/nav layout
    integration/              # operator-curated image library + regression tests
  docs/
    user_guide/               # user manual chapters
    dev_guide/                # this manual
    api_reference/            # autodoc landing pages
    conf.py                   # Sphinx config

The src/nav/ tree is the importable public package; everything else under src/ is supporting code (backplanes, pds4, reproj_cli, main).

Install

git clone https://github.com/SETI/rms-nav.git
cd rms-nav
python -m venv venv
source venv/bin/activate                  # Windows: venv\Scripts\activate

Editable install of runtime + dev tools (ruff, mypy, pytest, coverage):

pip install -e ".[dev]"

The [dev] group transitively includes the docs group (Sphinx, myst-parser, sphinx-rtd-theme, sphinxcontrib-mermaid). The runtime-only install (pip install rms-nav) is what end users get from PyPI.

Editable installs + mypy: export SETUPTOOLS_ENABLE_FEATURES=legacy-editable if mypy cannot find the nav package.

Environment variables

Most runs need at least one of these (CLI flags override env vars override config defaults). Each accepts a local path or a URL handled transparently by filecache:

  • SPICE_PATH — required by every real navigation run; points at the SPICE kernels directory.

  • PDS3_HOLDINGS_DIR — root for PDS3 holdings (default https://pds-rings.seti.org/holdings).

  • PDS4_HOLDINGS_DIR — root for PDS4 holdings.

  • OOPS_RESOURCES — root for the oops resources bundle.

  • UCAC4_PATH / YBSC_PATH — star-catalog roots used by NavModelStars.

  • NAV_RESULTS_ROOT — output directory for _metadata.json and _summary.png files written by nav_offset.

Running the CLI tools

Every CLI listed in [project.scripts] is installed onto $PATH by pip install. The full set:

Script

Purpose

nav_offset

Run autonomous navigation on one image (or a batch); writes JSON metadata + summary PNG. Documented at Image Navigation.

nav_offset_cloud_tasks

Queue-driven navigation variant; reads task JSON, processes one batch.

nav_backplanes / nav_backplanes_cloud_tasks

Generate per-pixel backplanes from a navigated image. See Backplane Generation and Backplanes.

nav_backplane_viewer

PyQt6 viewer for a backplane file.

nav_create_bundle / nav_create_bundle_cloud_tasks

Build a PDS4 bundle from navigated images + backplanes. See PDS4 Bundle Generation and PDS4 Bundle Generation.

nav_mosaic / nav_mosaic_rings / nav_mosaic_body

Reprojection drivers (rings, body, dispatcher). See Reprojection Mosaicing and Reprojection Internals.

nav_mosaic_display / nav_mosaic_display_rings / nav_mosaic_display_body

PyQt6 mosaic viewer.

nav_mosaic_cloud_tasks

Queue-driven mosaic builder.

nav_create_simulated_image

Render a synthetic test image (operator-supplied bodies / rings / stars). See The Image Simulator.

Quick smoke test:

nav_offset coiss N1234567890 --dry-run

Running the test suite

Pytest defaults exclude integration tests; pass -m "" (empty marker filter) to include them. pyproject.toml sets addopts = ["-m", "not integration"].

pytest                                      # unit suite (fast)
pytest -m ""                                # full suite (incl. integration)
pytest -m integration                       # only integration
pytest -n auto --dist=loadfile              # parallel (matches CI)
pytest tests/nav/reproj/test_bodies.py      # one file
pytest tests/nav/reproj/test_bodies.py::test_foo  # one test
pytest --cov                                # with coverage

pytest-xdist must run with --dist=loadfile — the default scheduling crashes PyQt6 workers when tests from one file split across processes.

The integration suite pulls real images from the holdings URLs and uses the operator-curated regression library at tests/integration/image_library/. It needs the env vars listed above (in particular PDS3_HOLDINGS_DIR, UCAC4_PATH, YBSC_PATH, OOPS_RESOURCES) plus a SPICE_PATH. scripts/run-all-checks.sh -i (or --integration) flips the marker for the all-checks runner.

Test markers and layout

  • Unit tests live under tests/nav/ and mirror src/nav/ directory by directory (tests/nav/reproj/test_bodies.pysrc/nav/reproj/bodies.py).

  • Integration tests live under tests/integration/ and carry the integration marker. Two sub-layers: the structural-invariants test (fast, no holdings needed) and the per-image regression test (slow, needs holdings).

  • The image library (operator-curated regression cohort) lives at tests/integration/image_library/. See Image Library for the calibration workflow, sidecar schema, and baseline tooling.

Lint, types, formatter

ruff check src tests              # lint
ruff format --check src tests     # format check (CI uses --check; local: drop --check)
mypy src tests                    # type-check (strict mode is on)
pymarkdown scan docs/ .cursor/ README.md CONTRIBUTING.md  # markdown lint

Project conventions: line length 100, single-quoted strings, three-group import order (stdlib / third-party / local), Google-style docstrings with Parameters: (not Args:) wrapped at 90 chars, at most three positional parameters per function (rest keyword-only after *), modules under 1000 lines, no module-level # type: ignore without a specific error code. See Best Practices for the full list and the rationale.

The all-in-one wrapper:

./scripts/run-all-checks.sh        # ruff + mypy + pytest + sphinx + pymarkdown, parallel
./scripts/run-all-checks.sh -i     # same, but include integration tests

Building the documentation

Documentation source is reStructuredText under docs/ plus Markdown via myst-parser. Sphinx config lives at docs/conf.py. Build with:

pip install -e ".[docs]"           # or [dev]
sphinx-build -W -b html docs docs/_build

The -W flag promotes warnings to errors; CI runs the same. The built HTML opens at docs/_build/index.html. Per-format alternates are available from the docs/ directory’s Makefile (make latexpdf, make singlehtml, make epub).

Mermaid diagrams render via sphinxcontrib-mermaid. Validate complex diagrams in the Mermaid Live Editor (https://mermaid.live/) before committing.

The autodoc API pages under docs/api_reference/ are populated from docstrings in the source tree; new modules require a corresponding entry under docs/api_reference/ so they appear in the rendered API surface.

CI / CD pipeline

GitHub Actions defines three workflows under .github/workflows/:

  • run-tests.yml — runs on every PR and on pushes to main, plus a weekly cron. Three jobs:

    • Lint — Python 3.13. ruff check, ruff format --check, mypy.

    • Test — matrix across Python 3.11 / 3.12 / 3.13 / 3.14 on Ubuntu. Runs pytest -m "not integration" -n auto --dist=loadfile with the holdings / catalog env vars exported. Uploads coverage to Codecov from the 3.13 job on pushes (PRs from forks are excluded for secrets reasons).

    • Docssphinx-build -W against the [docs] extra.

    Integration tests are skipped in CI (slow, holdings-dependent). Maintainers run them locally before merging anything that could plausibly regress navigation accuracy.

  • publish_to_test_pypi.yml — runs on pushes to main once tests pass. Builds a wheel + sdist and uploads to TestPyPI under the dev version stamped by setuptools_scm.

  • publish_to_pypi.yml — triggered by publishing a GitHub Release on main. Builds via python -m build and uploads to production PyPI using the PYPI_API_TOKEN repo secret.

Release / deployment

Versions are derived automatically from git tags by setuptools_scm; there is no version string committed to the repo.

Cutting a release:

  1. Land all release content on main (squash-merged).

  2. From a clean checkout of main, draft a GitHub Release and tag it vX.Y.Z (semver). Publishing the release triggers publish_to_pypi.yml.

  3. Verify the new version appears on https://pypi.org/project/rms-nav/ and that pip install rms-nav==X.Y.Z works in a clean venv.

  4. ReadTheDocs builds the new tag automatically from the same Git ref; verify the tagged version appears in the version selector at https://rms-nav.readthedocs.io/.

Pre-release iteration uses TestPyPI (publish_to_test_pypi.yml); install from there with pip install --index-url https://test.pypi.org/simple/ rms-nav.

Contribution workflow

The contributor checklist is at Contributing. Highlights:

  • Conventional Commits subjects (feat:, fix:, docs:, refactor:, test:, perf:, ci:, chore:, style:). Subject imperative, ≤ 50 chars, no trailing period. One logical change per commit.

  • PRs squash-merge to main; the squash message becomes the release notes entry.

  • Every PR must pass ./scripts/run-all-checks.sh locally and the same checks in CI.

  • Static-data values (per-body shape, per-instrument calibration) require a citation traceable to a fetched document; see Config and Static Data.

Pointers to the rest of this manual