Skip to content

feat: improve star/ring navigation and correlation accuracy#91

Merged
rfrenchseti merged 14 commits intomainfrom
rf_260414_rings
Apr 21, 2026
Merged

feat: improve star/ring navigation and correlation accuracy#91
rfrenchseti merged 14 commits intomainfrom
rf_260414_rings

Conversation

@rfrenchseti
Copy link
Copy Markdown
Collaborator

@rfrenchseti rfrenchseti commented Apr 16, 2026

Purpose

This branch makes a broad set of improvements to navigation accuracy and reliability across the star, ring, and correlation subsystems. The primary motivation is to fix the single-bright-star case where pyramid correlation was picking far-off spurious peaks, and to add the ring-plane star-exclusion and planet-shadow-removal features needed for ring-plane imaging sequences.

Changes

Correlation (src/nav/support/correlate.py)

  • Re-derived masked_ncc: computes the true Pearson-r NCC surface and also returns the unnormalized covariance numerator.
  • Peak localization now uses the numerator surface, not the NCC, to avoid the NCC plateau problem when a sparse PSF template causes many shifts to score near 1.0 (most visible with a single bright star).
  • nms_topk: new non-maximum suppression function to return top-k peaks with configurable suppression radius and a max_offset_vu window.
  • navigate_with_pyramid_kpeaks: coarse-to-fine multi-peak pyramid with prior propagation. max_offset_vu is accepted and scaled (floored to 1) at each pyramid level so every level retains at least 1-pixel tolerance.
  • navigate_single_scale_kpeaks: single-scale k-peak correlation with sub-pixel refinement.

Star correlation and refinement (src/nav/nav_technique/nav_technique_correlate_all.py)

  • max_offset_vu=obs.extfov_margin_vu now passed to navigate_with_pyramid_kpeaks so per-star searches are restricted to the physically-reachable offset range.
  • Star refinement now logs reduced_chi2, peak_snr, x_err, y_err, scale, and noise_rms per star when log_star_refinement_detail is set.
  • reduced_chi2 floor: bright stars are no longer rejected solely on a low chi2 score unless peak_snr is also below threshold, eliminating spurious outlier rejection for well-fit bright stars.
  • vmag_spread == 0 guard: if all stars have the same magnitude the reliability list defaults to 1.0 for all stars instead of dividing by zero.

Ring-plane star exclusion (src/nav/nav_model/nav_model_stars.py)

  • _mark_conflicts_obj now performs a ring-plane backplane check when ring_occlusion_enabled is True (replacing the previous unconditional True).
  • ring_occlusion_radii_km config key defines per-planet annuli; pairs are validated (length >= 2, inner < outer) with clear ValueError messages.
  • Stars whose predicted position falls in a configured annulus of the closest planet's ring plane receive a RING: <PLANET> conflict label.
  • Fixes Star navigation: ring-plane occlusion via ring-radius backplane + photometric star-refinement gates #90.

Ring feature extent check (src/nav/nav_model/rings/ring_feature.py, nav_model_rings.py)

  • New RingFeature.max_extent_radius property (a + ae across all edges, raises ValueError when no edges are present).
  • NavModelRings performs a fast FOV extent check before the expensive resolutions backplane: if the minimum visible ring radius exceeds every feature's maximum extent, the model exits early.

Planet shadow removal (src/nav/nav_model/nav_model_rings.py)

  • New rings.remove_planet_shadow config flag (default False). When set, the where_inside_shadow backplane is computed once and applied to every rendered feature's model image and mask. Backplane failures are caught and logged as warnings so shadow removal does not abort model creation.

Manual navigation dialog (src/nav/ui/manual_nav_dialog.py)

  • New _CorrMapDialog popup: displays the full 2-D NCC surface as a color-mapped Matplotlib figure with the current offset overlaid as a red cross and the global peak circled in green.
  • Binary model mask overlay: _show_mask toggle renders the model mask as a semi-transparent white tint over the image/model composite.
  • _MASK_OVERLAY_ALPHA and _CORR_MAP_* module-level constants replace magic literals.
  • max_offset_vu is wired from obs.extfov_margin_vu at the call site.

Utilities and logging (src/nav/support/misc.py, config/logger.py)

Configuration (src/nav/config_files/)

  • Consolidated and renamed YAML config files; added ring_occlusion_radii_km, ring_occlusion_enabled, star_refinement_* keys, and remove_planet_shadow.

Documentation and tests

  • New docs/developer_guide_nav_technique_correlate.rst covering the correlation algorithm, pyramid strategy, peak selection, and the NCC numerator rationale.
  • New docs/developer_guide_navigation_models_rings.rst describing the rings navigation model domain types, filtering pipeline, and rendering contracts.
  • Updates to docs/introduction_configuration.rst and docs/user_guide_navigation.rst for the reorganized YAML layout and new navigation-related options.
  • Extended tests/nav/support/test_correlate.py: 261 tests including max_offset_vu masking, pyramid prior propagation, and nms_topk.
  • tests/nav/nav_model/test_nav_model_rings.py: ring extent test.

Type of Change

  • Bug fix (non-breaking)
  • New feature (non-breaking)
  • Breaking change (fix or feature that alters existing behavior or public API)
  • Refactor (no functional or API changes)
  • Documentation
  • Tests only (no production code change)
  • CI / Build / Dependencies

Testing

  • Unit tests pass
  • Integration tests pass (if applicable)
  • End-to-end tests pass (if applicable)
  • New or updated tests for changed code
  • Tested manually (describe below if applicable)

All 261 pytest tests pass (pytest -n auto). Ruff, Mypy (122 files), Sphinx (-W), and PyMarkdown all pass clean. Single-star correlation was verified manually on COISS images where the previous code picked spurious off-center peaks; the revised NCC-numerator path converges to the correct star position.

Potential Impacts

  • masked_ncc return type changed: now returns (ncc, numerator) instead of ncc alone. Any caller outside this repo that imported masked_ncc directly must unpack the tuple.
  • navigate_with_pyramid_kpeaks signature: max_offset_vu keyword argument added; callers that did not pass it are unaffected.
  • No changes to the public NavModelResult / NavTechnique API.

Checklist

  • Code follows project style (ruff check, ruff format)
  • Type annotations present and mypy passes
  • Docstrings and Sphinx docs updated (if applicable)
  • CHANGES.md updated (if user-facing change)
  • No temporary or debug code left in
  • Breaking changes flagged in Type of Change above

Notes

The masked_ncc return-type change is the only public-API break; all internal callers have been updated.

Summary by CodeRabbit

  • Documentation

    • Expanded developer and user guides with a new correlate technique page, ring-model and star-refinement docs, navigation/configuration updates, and logging docs.
  • New Features

    • Added CLI log-level flags and per-image logging; correlation-map visualization in manual navigation UI; optional planet-shadow masking for rings; star-refinement quality gates and ring-occlusion checks to reduce false positives.
  • Configuration

    • Restructured and added YAML config files with centralized general/logging and detailed star/ring/offset settings.
  • Tests

    • Added comprehensive tests for correlation and navigation routines.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

Walkthrough

Split/added focused config files and logging controls; introduced MAIN/IMAGE loggers and per-image logs; implemented ring-shadow masking and ring-plane occlusion for stars; added photometric gating in star refinement; changed masked NCC to return numerator and propagate max-offset constraints through pyramid navigation; updated UI, docs, and tests.

Changes

Cohort / File(s) Summary
Configuration files
src/nav/config_files/config_01_general.yaml, src/nav/config_files/config_02_offset.yaml, src/nav/config_files/config_03_stars.yaml, src/nav/config_files/config_04_bodies.yaml, src/nav/config_files/config_05_rings.yaml, src/nav/config_files/config_06_titan.yaml, src/nav/config_files/config_07_bootstrap.yaml, src/nav/config_files/config_01_settings.yaml
Split monolithic settings into focused YAMLs; added general logging keys and new offset/star/rings/bootstrap parameters; emptied the old config_01_settings.yaml.
Logging infra & CLI
src/nav/config/logger.py, src/nav/config/__init__.py, src/main/nav_offset.py, src/nav/navigate_image_files.py
Added MAIN_LOGGER/IMAGE_LOGGER, _resolve_level(), setup_logging(), image_log_handlers(); exported logger symbols; added CLI flags to override log levels; integrated per-image log file creation and wiring via log_arguments.
Correlation math & navigation
src/nav/support/correlate.py, src/nav/nav_technique/nav_technique_correlate_all.py, tests/nav/support/test_correlate.py
masked_ncc() now returns (ncc, numerator) and uses an FFT-sum covariance numerator plus _NCC_EPS; added _NCC_EPS constant; propagated max_offset_vu into nms_topk, navigate_single_scale_kpeaks, and navigate_with_pyramid_kpeaks with per-level scaling; navigate_with_pyramid_kpeaks and correlate-all now accept/forward offset constraints; added refinement metadata extraction and photometric gating; extended/added tests.
Star model: occlusion & conflicts
src/nav/nav_model/nav_model_stars.py
Added validated per-planet ring-annuli cache and ring-plane occlusion checks using ring_radius backplane; set star.conflicts = 'RING: <PLANET>' when matched; preserved body-intercept precedence.
Ring model: shadow masking & quick checks
src/nav/nav_model/nav_model_rings.py, src/nav/nav_model/rings/ring_feature.py, tests/nav/nav_model/test_nav_model_rings.py
Added RingFeature.max_extent_radius property and quick-extent prefilter; introduced optional planet-shadow masking (rings.remove_planet_shadow) with mask application to feature models/masks; updated tests to avoid triggering shadow path.
UI: correlation map & mask overlay
src/nav/ui/manual_nav_dialog.py
Added _CorrMapDialog using matplotlib to render FFT-shifted NCC numerator and peak overlay; added display-mask checkbox and mask overlay in compose; integrated max_offset_vu into Auto search and added dialog launcher.
Master & utilities
src/nav/nav_master/nav_master.py, src/nav/support/misc.py
Wrapped body-model creation in a logger context; added current_git_version(), get_local_host_name(), and log_run_environment() helpers to emit startup environment info.
Docs & Sphinx
docs/conf.py, docs/introduction_configuration.rst, docs/user_guide_navigation.rst, docs/developer_guide.rst, docs/developer_guide_nav_technique_correlate.rst, docs/developer_guide_navigation_models_rings.rst
Expanded autodoc mocks to include matplotlib Qt backends; documented new config files and logging CLI flags; documented correlate-all star-refinement gates and ring shadow/occlusion behavior; added correlation technique developer doc.
Tests
tests/nav/support/test_correlate.py, tests/nav/nav_model/test_nav_model_rings.py
Added comprehensive correlate tests and adjusted ring-model tests to explicitly disable shadow removal in mocks.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related issues

  • Code rabbit comments 9/23/2025 #6 #10 — Shares objectives: ring-plane occlusion and photometric refinement gates; this PR implements ring-radius backplane checks and photometric gating aligned with that issue.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: improve star/ring navigation and correlation accuracy' clearly and concisely summarizes the main objective of the PR, which encompasses improvements across star navigation, ring modeling, and correlation algorithms.
Linked Issues check ✅ Passed The PR successfully addresses linked issue #90: implements ring-plane occlusion checking via ring_radius backplane with configurable annuli, adds photometric refinement gates with distinct conflict labels, introduces configurable thresholds for chi-squared/SNR/position-error, and includes comprehensive documentation and tests.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives: correlation improvements, star/ring navigation refinements, configuration reorganization, documentation updates, and test expansion. No unrelated or scope-creeping modifications are present.
Docstring Coverage ✅ Passed Docstring coverage is 84.13% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The pull request includes a comprehensive description covering Purpose, Changes, Type of Change, Testing, Potential Impacts, Checklist, and Notes, following the required template structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/user_guide_navigation.rst (1)

338-379: ⚠️ Potential issue | 🟡 Minor

Fix stale config filename references after config split.

Line 338 and Line 379 point readers to config_01_general.yaml, but the documented keys are offset.* and stars.*. Those sections should reference the corresponding split config files to prevent misconfiguration.

Proposed doc fix
-The following configuration options in ``config_01_general.yaml`` control the behavior of ``correlate_all``:
+The following configuration options in ``config_02_offset.yaml`` control the behavior of ``correlate_all``:
@@
-Example from ``config_01_general.yaml``:
+Example from ``config_03_stars.yaml``:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/user_guide_navigation.rst` around lines 338 - 379, The docs incorrectly
reference config_01_general.yaml; update those references to the actual split
config files that now contain the documented keys (replace the occurrences at
the offsets section and the ring occlusion section). Specifically, change the
mention of config_01_general.yaml where you document offset.* and
general.log_level_nav_correlate_all to the config file that contains the
offset/logging keys (the file that defines
offset.correlation_fft_upsample_factor, offset.star_refinement_*, and
general.log_level_nav_correlate_all), and change the mention where you document
stars.ring_occlusion_radii_km to the config file that contains the stars.* keys
(the file that defines stars.ring_occlusion_radii_km). Ensure the referenced
filenames match the repo's split config filenames and update any example snippet
headers to the same filenames.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nav/config_files/config_01_general.yaml`:
- Line 13: The hardcoded truetype_font_dir value should be replaced with a
configurable, platform-aware default: read from an environment variable (e.g.,
TRUETYPE_FONT_DIR) if set, otherwise detect platform (process.platform /
os.platform) and choose a sensible default per OS (Linux:
/usr/share/fonts/truetype, macOS: /Library/Fonts or ~/Library/Fonts, Windows:
C:\Windows\Fonts), or call a font-discovery helper if available; update the
config key truetype_font_dir to use that resolved value and add
documentation/comments in config_01_general.yaml explaining how to override on
non-Linux systems and suggesting a font-discovery library as an alternative.

In `@src/nav/config_files/config_05_rings.yaml`:
- Line 14: The YAML key curvature_to_reduce_features in config_05_rings.yaml
uses the numeric literal 1.5707963267948966; add a short inline comment after
that value stating it equals π/2 radians (90 degrees) — e.g., append "# pi/2
radians (90°)" to clarify intent for future maintainers.
- Around line 29-70: The file contains large commented-out configuration blocks
(e.g., parameter_font, parameter_overlay_start, parameter_overlay_step,
parameter_overlay_line_len_thickness, parameter_overlay_box_size and font
entries) that clutter the active config; either delete these legacy/commented
blocks if they are no longer needed, or move them into a separate
example/documentation file (or a dedicated comments/example section) and
reference it, ensuring all references to the identifiers like parameter_font,
parameter_overlay_start, parameter_overlay_step,
parameter_overlay_line_len_thickness, and parameter_overlay_box_size are
preserved in the example file for future use.

In `@src/nav/config_files/config_06_titan.yaml`:
- Around line 1-2: Update the titan configuration entry to document units for
atmosphere_height: modify the titan block (the key "titan" and its
"atmosphere_height" field) to include an inline comment indicating the expected
units (e.g., "# km") so maintainers immediately know the measurement units.

In `@src/nav/config_files/config_07_bootstrap.yaml`:
- Around line 12-20: Add inline YAML comments next to the angle and resolution
keys (max_phase_angle, max_incidence_angle, max_emission_angle, lon_resolution,
lat_resolution, max_subsolar_dist) showing the equivalent values in degrees
(e.g., "# 135 degrees", "# 70 degrees", "# 0.5 degrees", "# 45 degrees") so the
numeric radian values remain unchanged but are immediately human-readable; use
standard YAML comment syntax (#) and place comments on the same lines as the
existing entries for quick reference.

In `@src/nav/config/logger.py`:
- Around line 78-85: The setup_logging() routine is adding stream and file
handlers to MAIN_LOGGER every time it's called (via calls to
pdslogger.stream_handler and pdslogger.file_handler), causing duplicate logs;
make setup_logging() idempotent by first checking/clearing existing handlers on
MAIN_LOGGER (or detecting existing stream/file handlers) before calling
MAIN_LOGGER.add_handler, or return early if already configured, and then add the
handlers only when absent so repeated calls don't append duplicates.
- Around line 46-51: The current code assumes `level` is a string and calls
`level.upper()` which can raise unclear errors or allow invalid values; in the
function that computes the log level (the block returning level.upper()),
validate that `level` is either None or an instance of str, raise a TypeError if
it is not, then normalize with level = level.strip() and ensure the uppercased
value is one of the allowed log levels (e.g.,
"DEBUG","INFO","WARNING","ERROR","CRITICAL"); if it is not, raise a ValueError
with a clear message before returning the normalized string. Ensure you update
the code paths that call this logic (the function that reads from
`arguments`/`config.general`) to surface these exceptions.

In `@src/nav/nav_model/nav_model_stars.py`:
- Around line 585-586: The code accesses external YAML-backed config attributes
directly which can raise AttributeError if keys are missing; change reads of
self._stars_config.ring_occlusion_enabled and self._stars_config.ring_occlusion
(and the other instance around lines ~899–900) to use dictionary-style .get(...)
with safe defaults (False for ring_occlusion_enabled and {} for ring_occlusion),
and ensure _mark_conflicts_obj still receives the computed rings_can_conflict
boolean; additionally, add a small validation at the public API boundary to
raise a clear ValueError/TypeError if the config object is not a dict-like
structure expected by nav_model_stars.
- Around line 902-916: The loop validating annulus entries (iterating raw ->
planet_key, pairs -> pair and appending to validated) must first ensure each
pair is exactly a 2-item sequence of numeric values and that both numbers are
finite before coercing to float; update the checks in that block (referencing
raw, planet_key, pairs, pair, validated, and the ring_occlusion_radii_km
validation) to reject non-sequences, sequences with length != 2, non-numeric
elements, or NaN/inf and raise a clear ValueError describing the planet_key and
offending value instead of relying on len(pair) or float() throwing incidental
errors.

In `@src/nav/nav_technique/nav_technique_correlate_all.py`:
- Around line 238-248: The code currently assumes opt_metadata returned from
psfmodel.PSF.find_position() contains keys reduced_chi2, peak_snr, x_err, y_err,
scale, noise_rms; wrap the extraction of opt_v, opt_u, opt_metadata and the
subsequent access to opt_metadata['reduced_chi2'], ['peak_snr'], ['x_err'],
['y_err'], ['scale'], ['noise_rms'] in a try/except that catches KeyError and
TypeError, and on error raise a ValueError that includes the offending key name
(or type issue) and instead of letting the exception propagate mark the star as
rejected with the flag 'REFINEMENT METADATA' so the pipeline continues
gracefully; reference psfmodel.PSF.find_position(), opt_metadata, opt_v/opt_u
and the rejection flag name when implementing the change.

In `@src/nav/navigate_image_files.py`:
- Around line 75-76: The per-image logging is recording raw CLI args
(sys.argv[1:]) from navigate_image_files.py into log_run_environment, exposing
secrets; fix by sanitizing before logging: update log_run_environment (in
src/nav/support/misc.py) to sanitize/redact sensitive patterns (signed URLs,
tokens, keys, passwords, emails) from the incoming command_list or add a helper
like sanitize_cli_args and call it inside log_run_environment, then log the
sanitized ' '.join(command_list) instead; alternatively, change the call site in
navigate_image_files.py to pass a sanitized copy of sys.argv[1:] to
log_run_environment rather than the raw list.

In `@src/nav/support/correlate.py`:
- Around line 573-574: Add a brief clarifying comment next to the prior_weight
assignment in the correlate function where prior_shift/ prior_at_scale are set
(the lines using prior_weight_final and prior_at_scale) explaining that when
coarser_prior_fullres is None (i.e., at the coarsest level) prior_at_scale will
be None and thus prior_weight is intentionally set to 0.0 so the coarsest-level
quality is unpenalised; reference the symbols prior_weight, prior_weight_final,
prior_at_scale, prior_shift so reviewers can easily locate the exact assignment
to annotate.
- Line 101: Extract the magic epsilon into a module-level constant (e.g. add
_NCC_EPS = 1e-12 near the top with other constants) and replace local uses of
eps in this file (the occurrences at the current function in correlate.py and
the other uses referenced at lines ~112 and ~126) with _NCC_EPS; update any
local variable assignments like eps = 1e-12 to reference _NCC_EPS so all places
use the single constant name.

In `@src/nav/support/misc.py`:
- Around line 150-160: Expand the get_local_host_name() docstring to be
testable: describe the function purpose, the return type and value (str), the
caching behavior via _LOCAL_HOST_NAME_CACHE, and the fallback behavior when
socket.getfqdn() raises an exception (returns the literal 'LOCAL HOST NAME
FAILED'); also mention side effects (updates the module-level
_LOCAL_HOST_NAME_CACHE) so a black-box test can assert return values and
subsequent cached returns from get_local_host_name().
- Around line 125-144: Update the docstring for current_git_version to include a
"Raises:" section and clarify that the function does not propagate exceptions
(it catches all exceptions internally and returns a string), and document the
return contract (returns the git describe string or the sentinel 'GIT DESCRIBE
FAILED'); reference the function current_git_version and the module-level cache
_GIT_VERSION_CACHE so readers know the result is cached for the process
lifetime.

In `@src/nav/ui/manual_nav_dialog.py`:
- Around line 143-170: The displayed image currently uses the NCC surface
`shifted` but the global peak is computed from `shifted` via
`np.argmax(shifted)`, which can pick an arbitrary plateau; instead compute the
peak from the numerator surface returned by `masked_ncc()` (the numerator
variable) and use that to set `peak_row`, `peak_col`, `peak_dv`, and `peak_du`
(the same places where `np.argmax(shifted)` is used), while keeping `shifted`
for plotting the NCC image; apply the same change to the other peak computation
site around lines 342-343 so the green peak marker always uses the
numerator-based argmax.

---

Outside diff comments:
In `@docs/user_guide_navigation.rst`:
- Around line 338-379: The docs incorrectly reference config_01_general.yaml;
update those references to the actual split config files that now contain the
documented keys (replace the occurrences at the offsets section and the ring
occlusion section). Specifically, change the mention of config_01_general.yaml
where you document offset.* and general.log_level_nav_correlate_all to the
config file that contains the offset/logging keys (the file that defines
offset.correlation_fft_upsample_factor, offset.star_refinement_*, and
general.log_level_nav_correlate_all), and change the mention where you document
stars.ring_occlusion_radii_km to the config file that contains the stars.* keys
(the file that defines stars.ring_occlusion_radii_km). Ensure the referenced
filenames match the repo's split config filenames and update any example snippet
headers to the same filenames.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f26ac5b-e724-4627-bea4-f6e4ef67ee6d

📥 Commits

Reviewing files that changed from the base of the PR and between 479ec43 and 57c9d38.

📒 Files selected for processing (39)
  • docs/conf.py
  • docs/developer_guide.rst
  • docs/developer_guide_nav_technique_correlate.rst
  • docs/developer_guide_navigation_models_rings.rst
  • docs/introduction_configuration.rst
  • docs/user_guide_navigation.rst
  • src/main/nav_offset.py
  • src/nav/config/__init__.py
  • src/nav/config/logger.py
  • src/nav/config_files/config_01_general.yaml
  • src/nav/config_files/config_01_settings.yaml
  • src/nav/config_files/config_02_offset.yaml
  • src/nav/config_files/config_03_stars.yaml
  • src/nav/config_files/config_04_bodies.yaml
  • src/nav/config_files/config_05_rings.yaml
  • src/nav/config_files/config_06_titan.yaml
  • src/nav/config_files/config_07_bootstrap.yaml
  • src/nav/config_files/config_10_satellites.yaml
  • src/nav/config_files/config_20_jupiter_rings.yaml
  • src/nav/config_files/config_21_saturn_rings.yaml
  • src/nav/config_files/config_22_uranus_rings.yaml
  • src/nav/config_files/config_23_neptune_rings.yaml
  • src/nav/config_files/config_30_inst_coiss.yaml
  • src/nav/config_files/config_31_inst_gossi.yaml
  • src/nav/config_files/config_32_inst_nhlorri.yaml
  • src/nav/config_files/config_33_inst_vgiss.yaml
  • src/nav/config_files/config_90_backplanes.yaml
  • src/nav/config_files/config_95_pds4.yaml
  • src/nav/nav_master/nav_master.py
  • src/nav/nav_model/nav_model_rings.py
  • src/nav/nav_model/nav_model_stars.py
  • src/nav/nav_model/rings/ring_feature.py
  • src/nav/nav_technique/nav_technique_correlate_all.py
  • src/nav/navigate_image_files.py
  • src/nav/support/correlate.py
  • src/nav/support/misc.py
  • src/nav/ui/manual_nav_dialog.py
  • tests/nav/nav_model/test_nav_model_rings.py
  • tests/nav/support/test_correlate.py
💤 Files with no reviewable changes (4)
  • src/nav/config_files/config_10_satellites.yaml
  • src/nav/config_files/config_90_backplanes.yaml
  • src/nav/config_files/config_95_pds4.yaml
  • src/nav/config_files/config_01_settings.yaml

Comment thread src/nav/config_files/config_01_general.yaml
Comment thread src/nav/config_files/config_05_rings.yaml
Comment thread src/nav/config_files/config_05_rings.yaml
Comment thread src/nav/config_files/config_06_titan.yaml
Comment thread src/nav/config_files/config_07_bootstrap.yaml
Comment thread src/nav/support/correlate.py Outdated
Comment thread src/nav/support/correlate.py
Comment thread src/nav/support/misc.py
Comment thread src/nav/support/misc.py
Comment thread src/nav/ui/manual_nav_dialog.py Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 16, 2026

Codecov Report

❌ Patch coverage is 26.78571% with 205 lines in your changes missing coverage. Please review.
✅ Project coverage is 42.36%. Comparing base (479ec43) to head (3484742).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...c/nav/nav_technique/nav_technique_correlate_all.py 0.00% 47 Missing ⚠️
src/nav/nav_model/nav_model_stars.py 8.69% 42 Missing ⚠️
src/nav/navigate_image_files.py 0.00% 30 Missing ⚠️
src/nav/config/logger.py 32.55% 29 Missing ⚠️
src/nav/support/misc.py 26.66% 22 Missing ⚠️
src/nav/nav_master/nav_master.py 0.00% 16 Missing ⚠️
src/nav/nav_model/nav_model_rings.py 11.76% 12 Missing and 3 partials ⚠️
src/nav/nav_model/rings/ring_feature.py 66.66% 1 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #91      +/-   ##
==========================================
+ Coverage   40.76%   42.36%   +1.60%     
==========================================
  Files          70       70              
  Lines        6229     6446     +217     
  Branches      875      916      +41     
==========================================
+ Hits         2539     2731     +192     
- Misses       3500     3509       +9     
- Partials      190      206      +16     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/nav/ui/manual_nav_dialog.py (1)

768-784: ⚠️ Potential issue | 🟠 Major

Clamp the auto offset to the same slice-safe bounds as dragging.

max_offset_vu allows peaks at the extended-FOV margin, but the drag path clamps to margin - 1 “to keep slices valid.” Auto can now set an endpoint offset and immediately refresh the overlay with it.

Suggested fix
         )
         dv, du = float(res['offset'][0]), float(res['offset'][1])
+        dv = float(
+            np.clip(
+                dv,
+                -self._obs.extfov_margin_v + 1,
+                self._obs.extfov_margin_v - 1,
+            )
+        )
+        du = float(
+            np.clip(
+                du,
+                -self._obs.extfov_margin_u + 1,
+                self._obs.extfov_margin_u - 1,
+            )
+        )
         self._dv, self._du = dv, du
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nav/ui/manual_nav_dialog.py` around lines 768 - 784, The auto-computed
offsets from navigate_with_pyramid_kpeaks are allowed up to
self._obs.extfov_margin_vu but must be clamped to the same slice-safe bounds
used by dragging (i.e., margin - 1) before applying them; after retrieving
res['offset'] into dv, du, clamp each to the range [-(self._obs.extfov_margin_vu
- 1), +(self._obs.extfov_margin_vu - 1)] and then assign to self._dv/self._du,
update the spin boxes (_spin_dv/_spin_du) and call _refresh_overlay() so the
overlay is rendered with the slice-safe, clamped offsets.
src/nav/support/correlate.py (1)

425-434: ⚠️ Potential issue | 🔴 Critical

Keep the no-candidate result schema compatible with pyramid logging.

With max_offset_vu, a level can legitimately have no eligible peaks. The fallback omits peak_val, but the pyramid loop logs res_lvl["peak_val"], causing a KeyError instead of returning a spurious/low-quality result.

Suggested fix
     if not candidates:
         # TODO When no candidates are found, the function returns cov: np.diag([1e6, 1e6])
         # and quality: -np.inf. Downstream code might not check for -np.inf quality and could
         # treat this as a valid result. Consider returning None or raising an exception instead.
         return {
             'offset': (0.0, 0.0),
             'cov': np.diag([1e6, 1e6]),
             'sigma_xy': (1e3, 1e3),
             'quality': -np.inf,
+            'peak_val': -np.inf,
+            'rc': (-1, -1),
         }

Also applies to: 568-591

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nav/support/correlate.py` around lines 425 - 434, The fallback branch
that returns when `candidates` is empty is missing the `peak_val` field, causing
pyramid logging to KeyError on `res_lvl["peak_val"]`; update that return dict to
include a `peak_val` key (set to a sensible sentinel such as 0.0 or -np.inf to
match the low-quality signal) alongside the existing `offset`, `cov`,
`sigma_xy`, and `quality` values, and make the same change to the analogous
fallback block later in the file (the second empty-candidates branch around the
568-591 region) so both branches provide a consistent result schema for
downstream code that reads `res_lvl["peak_val"]`.
♻️ Duplicate comments (2)
src/nav/nav_model/nav_model_stars.py (2)

633-634: ⚠️ Potential issue | 🟠 Major

Default optional ring-occlusion config keys before use.

This still directly reads YAML-backed attributes, so configs missing ring_occlusion_enabled or ring_occlusion_radii_km can raise AttributeError instead of using safe defaults.

Suggested fix
-            rings_can_conflict = self._stars_config.ring_occlusion_enabled
+            rings_can_conflict = bool(self._stars_config.get('ring_occlusion_enabled', False))
             self._mark_conflicts_obj(star, rings_can_conflict)
@@
-            raw: dict[str, list[list[float]]] = self._stars_config.ring_occlusion_radii_km or {}
+            raw_obj = self._stars_config.get('ring_occlusion_radii_km', {}) or {}
+            if not isinstance(raw_obj, dict):
+                raise ValueError(
+                    'stars.ring_occlusion_radii_km must be a mapping of planet name '
+                    'to annulus lists'
+                )
+            raw: dict[str, object] = raw_obj

As per coding guidelines, "Validate inputs at the public API boundary of the library. Raise clear ValueError or TypeError exceptions for invalid arguments."

Also applies to: 947-948

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nav/nav_model/nav_model_stars.py` around lines 633 - 634, The code reads
YAML-backed attributes directly (e.g., self._stars_config.ring_occlusion_enabled
and ring_occlusion_radii_km) which can raise AttributeError; update accesses to
use safe defaults via getattr(self._stars_config, "ring_occlusion_enabled",
False) and getattr(..., "ring_occlusion_radii_km", []) (or equivalent) before
calling _mark_conflicts_obj; additionally validate types/values (raise TypeError
if not bool/list or ValueError for invalid radii) at the public API boundary
where _stars_config is set/loaded so callers get clear errors; apply the same
changes for the other occurrence that uses ring_occlusion_* around
_mark_conflicts_obj.

950-953: ⚠️ Potential issue | 🟠 Major

Validate each planet’s annulus list before iterating it.

pairs still comes from YAML and may be a scalar/string/mapping; iterating it can produce incidental errors or misleading per-character validation. Require a list/tuple of annulus pairs first.

Suggested fix
             for planet_key, pairs in raw.items():
+                if not isinstance(pairs, (list, tuple)):
+                    raise ValueError(
+                        f'ring_occlusion_radii_km: expected a list of annulus pairs for '
+                        f'{planet_key!r}, got {type(pairs).__name__}: {pairs!r}'
+                    )
                 validated: list[tuple[float, float]] = []
                 for pair in pairs:

As per coding guidelines, "NEVER trust external input (function arguments from callers, file contents, environment variables, data from remote URLs)."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nav/nav_model/nav_model_stars.py` around lines 950 - 953, The loop over
raw items currently assumes pairs is iterable and a list of annulus pairs;
before iterating, ensure pairs is actually a list/tuple (not a
scalar/string/mapping) and skip or raise a clear validation error if not. In the
block iterating raw.items(), add a type check for the variable pairs (from the
planet_key iteration) and only proceed to build validated and call
_ring_occlusion_annulus_pair(pair, planet_key) when pairs is an instance of list
or tuple; for non-list inputs, log or raise a descriptive error mentioning
planet_key so malformed YAML entries are not iterated per-character.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/nav_offset.py`:
- Around line 321-329: navigate_image_files(...) returns (success_bool,
metadata_dict) but the call is used directly in an if, so the tuple is always
truthy; change the call to unpack the result such as "success, metadata =
navigate_image_files(...)" and then use "if success:" to gate incrementing
NUM_FILES_PROCESSED and any downstream logic that should only run on successful
navigation; keep references to navigate_image_files, NUM_FILES_PROCESSED and
metadata to ensure the metadata_dict is preserved for later use.

In `@src/nav/config/logger.py`:
- Around line 53-58: The code uses getattr(arguments, attr_name, None) to read
an argparse.Namespace; replace that with vars(arguments).get(attr_name) so the
Namespace is accessed via its dict form; specifically update the branch that
sets level from the local variable "arguments" (the expression using
getattr(arguments, attr_name, None)) to use vars(arguments).get(attr_name) and
keep the existing fallback to config.general via getattr(config.general,
attr_name, None).

In `@src/nav/support/correlate.py`:
- Around line 554-561: The scaling of max_offset_vu to max_offset_at_scale
currently uses integer floor division which can drop valid offsets at coarse
pyramid levels; update the computation in correlate.py where max_offset_at_scale
is computed (referencing max_offset_vu and s) to use ceiling scaling (e.g.,
math.ceil(max_offset_vu[0] / s) and math.ceil(max_offset_vu[1] / s)) while still
enforcing a minimum of 1 for each component, and add the necessary import for
math if not present.

In `@src/nav/support/misc.py`:
- Around line 178-200: The function log_run_environment currently logs
command_list verbatim; change it to sanitize/redact sensitive values before
logging by mapping command_list through a sanitizer (e.g., new helper
sanitize_arg or existing sanitizer utility) that masks tokens, signed URLs,
emails, API keys, passwords, and local paths (use pattern checks for “://”, long
hex/token-like strings, email regex, and home/absolute paths) and then log
'Command line: %s' with ' '.join(sanitized_list) instead of the raw
command_list; update or add unit tests for sanitize_arg and ensure the sanitizer
is used anywhere else command strings are logged.

---

Outside diff comments:
In `@src/nav/support/correlate.py`:
- Around line 425-434: The fallback branch that returns when `candidates` is
empty is missing the `peak_val` field, causing pyramid logging to KeyError on
`res_lvl["peak_val"]`; update that return dict to include a `peak_val` key (set
to a sensible sentinel such as 0.0 or -np.inf to match the low-quality signal)
alongside the existing `offset`, `cov`, `sigma_xy`, and `quality` values, and
make the same change to the analogous fallback block later in the file (the
second empty-candidates branch around the 568-591 region) so both branches
provide a consistent result schema for downstream code that reads
`res_lvl["peak_val"]`.

In `@src/nav/ui/manual_nav_dialog.py`:
- Around line 768-784: The auto-computed offsets from
navigate_with_pyramid_kpeaks are allowed up to self._obs.extfov_margin_vu but
must be clamped to the same slice-safe bounds used by dragging (i.e., margin -
1) before applying them; after retrieving res['offset'] into dv, du, clamp each
to the range [-(self._obs.extfov_margin_vu - 1), +(self._obs.extfov_margin_vu -
1)] and then assign to self._dv/self._du, update the spin boxes
(_spin_dv/_spin_du) and call _refresh_overlay() so the overlay is rendered with
the slice-safe, clamped offsets.

---

Duplicate comments:
In `@src/nav/nav_model/nav_model_stars.py`:
- Around line 633-634: The code reads YAML-backed attributes directly (e.g.,
self._stars_config.ring_occlusion_enabled and ring_occlusion_radii_km) which can
raise AttributeError; update accesses to use safe defaults via
getattr(self._stars_config, "ring_occlusion_enabled", False) and getattr(...,
"ring_occlusion_radii_km", []) (or equivalent) before calling
_mark_conflicts_obj; additionally validate types/values (raise TypeError if not
bool/list or ValueError for invalid radii) at the public API boundary where
_stars_config is set/loaded so callers get clear errors; apply the same changes
for the other occurrence that uses ring_occlusion_* around _mark_conflicts_obj.
- Around line 950-953: The loop over raw items currently assumes pairs is
iterable and a list of annulus pairs; before iterating, ensure pairs is actually
a list/tuple (not a scalar/string/mapping) and skip or raise a clear validation
error if not. In the block iterating raw.items(), add a type check for the
variable pairs (from the planet_key iteration) and only proceed to build
validated and call _ring_occlusion_annulus_pair(pair, planet_key) when pairs is
an instance of list or tuple; for non-list inputs, log or raise a descriptive
error mentioning planet_key so malformed YAML entries are not iterated
per-character.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8a240f53-88eb-4ded-be22-ef1296fab7ae

📥 Commits

Reviewing files that changed from the base of the PR and between 57c9d38 and b90a743.

📒 Files selected for processing (7)
  • src/main/nav_offset.py
  • src/nav/config/logger.py
  • src/nav/nav_model/nav_model_stars.py
  • src/nav/nav_technique/nav_technique_correlate_all.py
  • src/nav/support/correlate.py
  • src/nav/support/misc.py
  • src/nav/ui/manual_nav_dialog.py

Comment thread src/main/nav_offset.py Outdated
Comment thread src/nav/config/logger.py
Comment thread src/nav/support/correlate.py Outdated
Comment thread src/nav/support/misc.py
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/main/nav_offset.py (1)

3-75: ⚠️ Potential issue | 🟡 Minor

Update the stale CLI usage name.

Line 3 now identifies this as nav_offset.py, but the usage text still prints nav_main_offset on Lines 59, 67, and 75.

Proposed fix
-        print('Usage: nav_main_offset <dataset_name> [args]')
+        print('Usage: nav_offset <dataset_name> [args]')
@@
-        print('Usage: nav_main_offset <dataset_name> [args]')
+        print('Usage: nav_offset <dataset_name> [args]')
@@
-        print('Usage: nav_main_offset <dataset_name> [args]')
+        print('Usage: nav_offset <dataset_name> [args]')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/nav_offset.py` around lines 3 - 75, The CLI usage text is stale: in
parse_args (function parse_args) update all printed usage strings that say
"nav_main_offset" to the correct script name "nav_offset" (replace occurrences
in the print calls that show usage and unknown-dataset help lines so the
messages in parse_args reflect the current filename).
src/nav/support/correlate.py (2)

158-202: 🛠️ Refactor suggestion | 🟠 Major

Validate max_offset_vu at the API boundary.

This is a new public parameter; a negative component silently wipes the whole surface to -inf and returns no peaks (the caller then hits the empty-candidate path). Reject invalid input explicitly.

🛡️ Proposed validation
     corr_v, corr_u = corr.shape
     work = corr.copy()
     if max_offset_vu is not None:
         max_v, max_u = max_offset_vu
+        if max_v < 0 or max_u < 0:
+            raise ValueError(
+                f'max_offset_vu components must be non-negative, got {max_offset_vu}'
+            )
         rows = np.arange(corr_v)

As per coding guidelines: "Validate inputs at the public API boundary of the library. Raise clear ValueError or TypeError exceptions for invalid arguments."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nav/support/correlate.py` around lines 158 - 202, nms_topk currently
accepts max_offset_vu with negative components which silently masks the whole
surface; add explicit validation at the start of nms_topk to ensure
max_offset_vu is either None or a tuple of two non-negative integers (or ints
castable), and raise a clear ValueError (or TypeError if the types are wrong) if
the tuple length is not 2 or any component is negative; update the checks around
max_v, max_u (the variables derived from max_offset_vu) so that they only run
after validation and include this explicit error handling in the nms_topk
function signature.

426-436: ⚠️ Potential issue | 🟠 Major

KeyError risk in pyramid-level logging when max_offset_vu prunes all peaks.

When nms_topk returns an empty list (easy to trigger now that max_offset_at_scale can clamp to (1, 1) at the coarsest level, or if a caller passes a tight max_offset_vu), navigate_single_scale_kpeaks returns the fallback dict at Lines 430-435, which omits peak_val (and rc). The pyramid-level debug log at Line 594 then raises KeyError: 'peak_val', masking the real "no candidates within offset bound" failure and aborting the navigation before the final full-resolution pass can run.

Either normalize the empty-candidate dict to include the keys the rest of the module reads, or make the logging robust. Normalizing is strictly better because downstream consumers (e.g. evaluate_candidate callers, tests) will behave consistently.

🐛 Proposed fix
     if not candidates:
         # TODO When no candidates are found, the function returns cov: np.diag([1e6, 1e6])
         # and quality: -np.inf. Downstream code might not check for -np.inf quality and could
         # treat this as a valid result. Consider returning None or raising an exception instead.
         return {
             'offset': (0.0, 0.0),
             'cov': np.diag([1e6, 1e6]),
             'sigma_xy': (1e3, 1e3),
             'quality': -np.inf,
+            'peak_val': 0.0,
+            'rc': (0, 0),
         }

Also applies to: 589-595

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/nav_offset.py`:
- Around line 158-194: The CLI accepts --log-level-image-console and
--log-level-image-file but setup_logging() only resolves main logger levels, so
validate the image log level args immediately after parsing (same place where
you validate --log-level-main-console and --log-level-main-file) so invalid
values error out at startup; call the same validation logic used for main levels
(or reuse the helper that translates/checks level names) to canonicalize/raise
on --log-level-image-console and --log-level-image-file before entering the
batch loop and before calling setup_logging(), referencing the CLI arg names and
setup_logging() so reviewers can find the change.
- Around line 317-320: The current MAIN_LOGGER.info call is logging full
external URLs via imagefiles.image_files -> f.image_file_url.as_posix(), which
may expose tokens; update the log to emit a stable non-sensitive identifier
(e.g., f.results_path_stub or imagefiles.results_path_stub) instead of the full
URL. Locate the MAIN_LOGGER.info call and replace the join over
f.image_file_url.as_posix() with a join over the results_path_stub property for
each image file (or a single results_path_stub from imagefiles if available) so
logs contain only the safe identifier.

---

Outside diff comments:
In `@src/main/nav_offset.py`:
- Around line 3-75: The CLI usage text is stale: in parse_args (function
parse_args) update all printed usage strings that say "nav_main_offset" to the
correct script name "nav_offset" (replace occurrences in the print calls that
show usage and unknown-dataset help lines so the messages in parse_args reflect
the current filename).

In `@src/nav/support/correlate.py`:
- Around line 158-202: nms_topk currently accepts max_offset_vu with negative
components which silently masks the whole surface; add explicit validation at
the start of nms_topk to ensure max_offset_vu is either None or a tuple of two
non-negative integers (or ints castable), and raise a clear ValueError (or
TypeError if the types are wrong) if the tuple length is not 2 or any component
is negative; update the checks around max_v, max_u (the variables derived from
max_offset_vu) so that they only run after validation and include this explicit
error handling in the nms_topk function signature.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 266a7ca0-fbdc-4423-9b06-d3abb65592f5

📥 Commits

Reviewing files that changed from the base of the PR and between b90a743 and 803adae.

📒 Files selected for processing (2)
  • src/main/nav_offset.py
  • src/nav/support/correlate.py

Comment thread src/main/nav_offset.py
Comment thread src/main/nav_offset.py
@rfrenchseti rfrenchseti merged commit e4843fe into main Apr 21, 2026
8 checks passed
@rfrenchseti rfrenchseti deleted the rf_260414_rings branch April 21, 2026 02:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant