Skip to content

Add custom C-matrix support via generic Observation.get_cmatrix/set_cmatrix - #201

Open
jnspitale wants to merge 23 commits into
mainfrom
jns-200-custom_Cmatrix
Open

Add custom C-matrix support via generic Observation.get_cmatrix/set_cmatrix#201
jnspitale wants to merge 23 commits into
mainfrom
jns-200-custom_Cmatrix

Conversation

@jnspitale

@jnspitale jnspitale commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • hosts/cassini/iss.py from_file() accepts a caller-supplied cmatrix= — the spice-convention C-matrix (J2000 → CASSINI_ISS_<camera>, exactly as returned by cspyce.pxform() or recorded in a CK) — plus an optional frame_id=. A custom-cmatrix load never loads a CK, never builds the SPICE camera frames (now built lazily by ISS.define_camera_frames()), and never reports CKs in spice_kernels.
  • Generic Observation.get_cmatrix(uv=None, time=None) and Observation.set_cmatrix(cmatrix, frame_id=None) on the base class. Both are driven by a single host class attribute, CMATRIX_ROTATION (the spice-frame → oops-frame convention rotation, reached through the observation's host subfield); the inverse direction is derived by transposition. No host-specific set/get methods, and nothing frame-shaped is stored on the observation — get_cmatrix derives the C-matrix from self.frame at call time.
  • set_cmatrix validates its input: the matrix must be a proper floating-point rotation. Non-finite values, boolean/integer data, reflections, scalings, and non-orthogonal matrices raise ValueError at the boundary.
  • set_cmatrix(frame_id=) only overrides frame registrations it made itself; an ID already held by any other frame (e.g. CASSINI_ISS_NAC, J2000) raises instead of silently replacing its primary definition. frame_id=None (default) gives each observation its own unregistered frame; re-using a set_cmatrix-issued ID re-points every observation sharing that registered frame, which is the point of giving it an ID.
  • oops/frame/cmatrix.py Cmatrix gains an override= option so an owned registration can be cleanly replaced.

Breaking changes

  • CASSINI_ISS_NAC_FLIPPED / CASSINI_ISS_WAC_FLIPPED are renamed to *_SPICE.
  • ISS.initialize() alone no longer registers CASSINI_ISS_NAC/CASSINI_ISS_WAC; they appear after the first plain (SPICE-pointed) from_file(), which calls define_camera_frames().
  • The offset_wac initialize option is removed: it made the WAC pointing a composite (Navigation offset on top of the SPICE chain), so get_cmatrix could not agree with pxform and the meaning of from_file(cmatrix=) depended on an initialize-time flag.

Review

Addresses the independent verification review (Custom C-Matrix Verification, R. French, 2026-08-14): F1 rotation validation, F2 frame-registry protection, F3 offset_wac removal, F5/F6 docstring clarifications (stale derived geometry; no CK loaded so pxform against the SPICE camera frame fails for that epoch). F4 (enabling the other hosts: Galileo SSI, Voyager ISS, NH LORRI) is deliberately deferred to #204 — this branch is the template.

Test plan

  • tests/hosts/cassini/iss/test_iss.py — 18 tests: round-trip vs pxform, per-observation frame isolation, no leakage into plain loads, frame_id reuse/collision, C-matrix validation, CK loading and spice_kernels reporting
  • tests/frame/test_cmatrix.py
  • tests/observation suite

🤖 Generated with Claude Code

https://claude.ai/code/session_015Wg9zMfz6FNEotebnS1hmm

jnspitale and others added 2 commits August 10, 2026 12:21
Add cmatrix/frame_id/map_other_camera arguments to hosts/cassini/iss.py
from_file, letting a caller supply a C-matrix (J2000 -> camera image frame)
instead of SPICE pointing for the camera named in the label. Optionally derive
and register the co-mounted other camera's frame from the fixed inter-camera
rotation. The override lives in a new ISS.set_cmatrix method; the SPICE-derived
camera frames are now built lazily by ISS.define_camera_frames() so a custom
C-matrix without mapping never depends on SPICE (no CK loaded).

Also add an override option to oops/frame/cmatrix.py Cmatrix so it can replace
an already-registered frame.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Generic getter on the Observation base class that evaluates self.frame's
rotation relative to a reference frame (default J2000) at a time picked
from a (u,v) location (default the FOV center), via the existing
midtime_at_uv(). Returns the raw Matrix3 so it round-trips with the
cmatrix inputs accepted by ISS.from_file()/ISS.set_cmatrix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWmXpTiygmBh786ED6GigN
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Changes

The Cassini ISS host now accepts custom SPICE C-matrices, converts them into observation frames, and creates camera frames lazily. C-matrix registration supports controlled overrides. New tests cover conversions, frame isolation, CK loading, registration, and reset behavior.

Cassini ISS C-matrix flow

Layer / File(s) Summary
C-matrix conversion and registration
oops/frame/cmatrix.py, oops/observation/observation_.py
Observation converts between SPICE and observation-frame conventions. Cmatrix supports optional override registration.
ISS camera frame setup
oops/hosts/cassini/iss.py
ISS.from_file accepts custom matrices and frame IDs. CK loading and camera-frame construction now depend on the pointing source.
ISS C-matrix validation
tests/hosts/cassini/iss/*
Tests cover matrix round trips, custom and registered frames, CK behavior, global-frame preservation, reset cleanup, and test-runner registration.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🟡 Moderate · up to 20230

Repeated custom C-matrix assignment can leave stale camera pointing, potentially producing incorrectly oriented observations, while observations missing required host metadata fail unclearly. Merge should wait for explicit replacement handling and clearer validation.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: custom C-matrix support and a new observation-level C-matrix getter.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@oops/hosts/cassini/iss.py`:
- Around line 497-501: The rel calculation in the camera-frame setup must use
immutable SPICE-only frame references rather than mutable CASSINI_ISS_* frames
that set_cmatrix can replace. Update define_camera_frames and the surrounding
frame setup to retain and use dedicated SPICE inter-camera frames for rel, and
add a regression test covering a single-camera override followed by a
mapped-camera load.

In `@tests/observation/test_snapshot.py`:
- Around line 135-139: Extend the snapshot observation test around cmatrix() so
it uses a time-dependent frame and observation whose midtime_at_uv() returns
different values for different UV coordinates. Assert that selecting distinct
UVs and times produces the corresponding transformed matrices, ensuring
cmatrix() actually uses both uv and time rather than returning the fixed
Cmatrix.
🪄 Autofix

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 Plus

Run ID: 05dd31e2-5483-41dc-b1c7-b30af5a8aac7

📥 Commits

Reviewing files that changed from the base of the PR and between 5b1d0d4 and cdad9ca.

📒 Files selected for processing (4)
  • oops/frame/cmatrix.py
  • oops/hosts/cassini/iss.py
  • oops/observation/observation_.py
  • tests/observation/test_snapshot.py

Comment thread oops/hosts/cassini/iss.py Outdated
Comment thread tests/observation/test_snapshot.py Outdated
…ests

Compute the map_other_camera inter-camera rotation from dedicated,
immutable CASSINI_ISS_<camera>_SPICE frames instead of the mutable
CASSINI_ISS_<camera> frames that set_cmatrix can override. A
single-camera override followed by a mapped-camera load would otherwise
derive `rel` from the overridden custom frame, corrupting the mapped
camera's pointing.

Add a regression test (test_map_after_single_camera_override) covering
that ordering, wire the ISS Cmatrix test module into the unittester, and
extend the Snapshot cmatrix() test to use a time-dependent SpinFrame and
a uv-dependent midtime so it verifies cmatrix() actually consults both
uv and time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jnspitale

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jnspitale
jnspitale marked this pull request as ready for review August 10, 2026 23:13
A recorded C-matrix is the SPICE camera-frame attitude, but the oops
observation frame is that frame rotated 180 deg about the boresight, so
feeding one into from_file(cmatrix=...) landed the frame ~0.5 deg off.
Apply the fixed rotation (ROT180) at the boundary in ISS.set_cmatrix so a
recorded/pxform C-matrix reproduces the observation's pointing, and add
ISS.oops_from_host / host_from_oops to convert between the two
conventions.

Also fix two frame-registry defects:
- A custom C-matrix no longer leaks into the next plain load. The default
  (frame_id=None) case now gives each observation its own unregistered
  Cmatrix frame instead of overriding the global CASSINI_ISS_<camera>, so
  loading another image never changes an earlier one's pointing. The
  map_other_camera case, which does override the globals, now clears
  ISS.frames_defined so the next plain load rebuilds them from SPICE.

Correct the Observation.cmatrix() docstring: it returns the generic
observation-frame attitude, which for ISS is not interchangeable with the
SPICE-convention cmatrix input.

Extend the ISS cmatrix tests: a pxform round-trip (zero offset), the
default-frame isolation and no-leak regressions, the map reclaim path, and
the oops_from_host / host_from_oops conversions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@oops/hosts/cassini/iss.py`:
- Around line 597-608: Update the mapped-camera handling around
define_camera_frames and the returned frame ID so each mapped observation
receives a separate fixed frame rather than the mutable global CASSINI_ISS
camera frame. Keep the global frames only for mapped-camera registration,
preserve the observation’s frame across subsequent plain and mapped loads, and
add a regression test that reads the first mapped observation after both a plain
load and a second mapped load.

In `@tests/hosts/cassini/iss/test_iss.py`:
- Around line 66-80: Use a consistent evaluation time for all C-matrix
comparisons in tests/hosts/cassini/iss/test_iss.py: in lines 66-80, compare
against baseline.cmatrix(time=baseline.tstart), and in lines 97-104, pass
time=baseline.tstart to both baseline.cmatrix() calls. Keep the existing
round-trip and boundary-rotation assertions unchanged.
🪄 Autofix

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 Plus

Run ID: 30a38d76-68e1-45c1-b1ae-bc8aad0c3fa1

📥 Commits

Reviewing files that changed from the base of the PR and between 4ab0943 and d92a221.

📒 Files selected for processing (3)
  • oops/hosts/cassini/iss.py
  • oops/observation/observation_.py
  • tests/hosts/cassini/iss/test_iss.py

Comment thread oops/hosts/cassini/iss.py Outdated
Comment thread tests/hosts/cassini/iss/test_iss.py Outdated
jnspitale and others added 2 commits August 11, 2026 12:26
Returns an ISS observation's pointing as a SPICE camera-frame C-matrix (as
cspyce.pxform would return it), so it can be fed back into
from_file(cmatrix=...) to reproduce the pointing. Implemented as
host_from_oops(obs.cmatrix()), with uv/time selection delegated to
Observation.cmatrix(). Adds a test covering the pxform match and the
set/get round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the redundant rot180 = ROT180 local alias and reference the
module-level ROT180 constant directly in the camera-frame constructions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@oops/hosts/cassini/iss.py`:
- Line 621: Add the return annotation -> oops.Matrix3 to ISS.get_cmatrix, or
explicitly exclude this method from ANN205 if this module intentionally omits
return annotations. Preserve the existing method behavior and signature
parameters.
🪄 Autofix

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 Plus

Run ID: dee3c0fe-c3da-4dcc-bad5-8df0c442da67

📥 Commits

Reviewing files that changed from the base of the PR and between d92a221 and 5ef6ce7.

📒 Files selected for processing (2)
  • oops/hosts/cassini/iss.py
  • tests/hosts/cassini/iss/test_iss.py

Comment thread oops/hosts/cassini/iss.py Outdated
jnspitale and others added 8 commits August 11, 2026 13:53
Move the map_other_camera branch of set_cmatrix into its own method,
ISS.set_cmatrix_both_cameras(cmatrix, camera, time), which registers both
camera frames from one camera's custom C-matrix and returns the label
camera's frame ID. set_cmatrix now delegates to it. Behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the cmatrix argument from ISS.map_other_camera; it now reads the
label camera's current pointing from the registered CASSINI_ISS_<camera>
frame and maps that to the other camera via the fixed inter-camera
rotation. set_cmatrix's map_other_camera=True branch now installs the
label camera's custom frame before delegating. Behavior is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Observation.get_cmatrix() now derives the spice-frame (SPICE camera-frame)
C-matrix at call time as host.CMATRIX_ROTATION * self.frame.wrt(J2000), with
no stored cmatrix_frame subfield. The observation carries only a `host`
reference; ISS exposes CMATRIX_ROTATION (renamed from ROT180) as a class
attribute for this.

- Remove the per-observation cmatrix_frame subfield from from_file/from_index.
- Remove the redundant Observation.cmatrix() (raw oops-frame attitude accessor)
  and the host-side ISS.get_cmatrix(); get_cmatrix() is now the sole accessor.
- Rename conversion helpers host_from_oops/oops_from_host ->
  spice_from_oops/oops_from_spice.
- Rename ROT180 -> CMATRIX_ROTATION throughout.
- Clarify docstrings around the two conventions: spice-frame (z=LOS, x left,
  y up) vs oops-frame (z=LOS, x right, y down), related by CMATRIX_ROTATION.
- Use dedicated *_SPICE frames in define_camera_frames.
- Repoint tests to get_cmatrix(); drop the removed cmatrix() tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirrors iss.set_cmatrix as the inverse of get_cmatrix, applying the
host's CMATRIX_ROTATION to build an observation frame; supports an
optional frame_id, without the map_other_camera machinery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Only the label camera's frame is ever created; a custom C-matrix never
touches the other camera or the global frame registry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observation.set_cmatrix now attaches the frame itself (assigns
self.frame) instead of returning it. from_file builds the Snapshot with
a placeholder J2000 frame when a custom C-matrix is given, then applies
the pointing through the generic method once the host subfield is in
place; no CKs are loaded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@oops/observation/observation_.py`:
- Around line 482-487: Update get_cmatrix() and set_cmatrix() to explicitly
validate that the observation has a host subfield before accessing
host.CMATRIX_ROTATION. Raise a clear error identifying the missing convention
rotation when host is absent, while preserving the existing behavior when the
host is present.
- Around line 517-524: Update set_cmatrix to accept an override=False parameter
and replace the duplicated Cmatrix construction branches with one
Cmatrix(attitude, frame_id=frame_id, override=override) call. Document that
override=True replaces the existing primary definition for a duplicate frame_id,
while preserving unregistered-frame behavior when frame_id is None.
🪄 Autofix

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 Plus

Run ID: 1da334a4-806c-418f-9170-cb7f3b10316d

📥 Commits

Reviewing files that changed from the base of the PR and between 5ef6ce7 and 202305b.

📒 Files selected for processing (3)
  • oops/hosts/cassini/iss.py
  • oops/observation/observation_.py
  • tests/hosts/cassini/iss/test_iss.py

Comment thread oops/observation/observation_.py Outdated
Comment thread oops/observation/observation_.py Outdated
jnspitale and others added 2 commits August 13, 2026 13:45
… frame_id reuse, informative errors

get_cmatrix/set_cmatrix now derive the spice-frame<->oops-frame inverse
rotation by transposing the host's single CMATRIX_ROTATION attribute,
instead of requiring hosts to expose a redundant CMATRIX_ROTATION_INV
that could be defined inconsistently. Re-using a frame_id in set_cmatrix
now passes override=True so the new pointing cleanly replaces the
primary frame definition instead of leaving a stale one behind
(Frame.register's secondary-definition path). Both methods now raise a
clear ValueError when the observation's host doesn't define
CMATRIX_ROTATION, rather than a bare AttributeError.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWmXpTiygmBh786ED6GigN
frame_id was only consulted when cmatrix was also given; passing it
alone was silently ignored rather than raising.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWmXpTiygmBh786ED6GigN
jnspitale and others added 2 commits August 13, 2026 13:46
Cassini.used_kernels() gained a ck keyword; from_file passes
ck=(cmatrix is None). Without this, a custom-cmatrix observation's
spice_kernels could list CK basenames it never used -- e.g. the
gapfill CKs Cassini.initialize() furnishes unconditionally, or a CK
furnished earlier in the session by a plain SPICE-pointed load.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWmXpTiygmBh786ED6GigN
- "coordinate  system" had a double space.
- The define_camera_frames comment claimed override=True refreshes
  frames after ISS.reset(), but neither ISS.reset() nor Cassini.reset()
  clears the frame registry, so the *_SPICE SpiceFrames (registered
  without override) leave a stale primary definition after a rebuild.
  Harmless here since they're used as direct object references, not
  looked up by ID, but the comment now says so instead of implying a
  full refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WWmXpTiygmBh786ED6GigN
@astrocfi

Copy link
Copy Markdown
Contributor

@rfrenchseti

Copy link
Copy Markdown
Collaborator

One forward-compatibility note on Observation.set_cmatrix(), since #203 is in flight against the same base.

The frame_id=None branch is documented as attaching "a fresh unregistered frame owned by the observation, so loading other images never disturbs its pointing". That ownership property does hold as this PR stands — I checked that constructing many unregistered Cmatrix frames leaves Frame.FRAME_CACHE and Frame.WAYFRAME_REGISTRY flat at 2 entries, and that two frames built from an equal-valued matrix are distinct objects.

But it isn't a property that Cmatrix.__init__ or Frame.register() actually promises. It is emergent from how the current registry treats a frame_id of None, and #203 changes exactly that: under its registry rewrite Frame._register() inserts every frame into a per-subclass _WAYFRAMES dict regardless of frame_id, and equal-valued unregistered frames dedup to a single shared wayframe object. On that branch Cmatrix(m)._wayframe is Cmatrix(m)._wayframe is True, and each construction is retained globally for the life of the process.

If #203 lands after this, the docstring's guarantee becomes false without anything in either PR failing — the frame is no longer owned by the observation, and it is no longer isolated from global state. Two observations handed equal-valued C-matrices would share a frame object.

Either of these would be enough to keep that from happening quietly:

  • Document the weaker, implementation-independent property — that the frame is simply not registered under a frame ID — rather than ownership or isolation from other loads.
  • Add a test that pins the property you actually want: that two observations given equal-valued C-matrices don't end up sharing a frame object, and that set_cmatrix(frame_id=None) leaves the frame registry unchanged. That turns a silent semantic regression into a failing test whenever the registry internals move.

Worth coordinating with #203 either way, since the two PRs are independent branches off main and whichever merges second inherits the interaction.

@rfrenchseti rfrenchseti left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See earlier comments

jnspitale and others added 2 commits August 17, 2026 10:49
With offset_wac=True, the WAC observation frame carried a Navigation
offset on top of the SPICE chain, so get_cmatrix() reported a composite
attitude rather than the pxform value its docstring promises, and the
meaning of from_file(cmatrix=) depended on a flag set at initialize
time. Resolves finding F3 of the rms-oops #201 verification review by
removing the option entirely: both camera frames are now built the same
way, directly from the SPICE frame and CMATRIX_ROTATION.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Wg9zMfz6FNEotebnS1hmm
Address findings F1, F2, F5 and F6 of the rms-oops #201 verification
review:

- F1: set_cmatrix() now rejects anything that is not a proper
  floating-point rotation -- non-finite values, boolean/integer data,
  reflections, scalings and non-orthogonal matrices all raise
  ValueError at the boundary instead of corrupting geometry downstream.
- F2: set_cmatrix(frame_id=) only overrides a registration it made
  itself; a frame_id held by any other frame (e.g. CASSINI_ISS_NAC or
  J2000) raises instead of silently replacing its primary definition.
  Re-using a set_cmatrix-issued ID still re-points every observation
  sharing that frame, as documented.
- F5: document that geometry derived before a set_cmatrix() call is not
  invalidated and must be rebuilt.
- F6: document that a custom-cmatrix load furnishes no CK, so pxform
  against the SPICE camera frame at that epoch will fail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Wg9zMfz6FNEotebnS1hmm
@jnspitale jnspitale changed the title Add custom C-matrix support and Observation.cmatrix() getter Add custom C-matrix support via generic Observation.get_cmatrix/set_cmatrix Aug 17, 2026
jnspitale and others added 2 commits August 17, 2026 19:02
Move the proper-rotation checks out of set_cmatrix() into a standalone
static method that returns the validated Matrix3, so callers (e.g. a
host's from_file) can validate a recorded C-matrix at any boundary
without mutating an observation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Wg9zMfz6FNEotebnS1hmm
The per-observation ownership documented for set_cmatrix(frame_id=None)
is emergent from how the current registry treats unregistered frames,
not a property Cmatrix or Frame.register promises. PR #203's registry
rewrite dedups equal-valued unregistered frames to a shared wayframe
and retains every construction globally, which would falsify the
contract without anything failing. Pin it: equal-valued C-matrices
yield distinct frame objects, and a default load leaves the wayframe
registry and frame cache unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015Wg9zMfz6FNEotebnS1hmm
@jnspitale

Copy link
Copy Markdown
Collaborator Author

Good catch — agreed that the isolation property was emergent rather than promised anywhere. Addressed by pinning it as a test rather than weakening the docstring, since per-observation ownership is the feature this argument exists for, not an accident we're willing to lose:

test_default_frames_are_distinct_and_stay_out_of_registry now asserts exactly the two properties you suggested — observations given equal-valued C-matrices get distinct frame objects, and a set_cmatrix(frame_id=None) load leaves Frame.WAYFRAME_REGISTRY and Frame.FRAME_CACHE at their prior sizes (measured after a warm load+get_cmatrix cycle, so lazy one-time registrations don't confound it). Under the #203 registry semantics you describe, both assertions fail, so whichever branch merges second now hits a failing test at rebase instead of a silent regression.

The retention/dedup question itself is #203's design call — raised there with a pointer back to this contract.

@jnspitale

Copy link
Copy Markdown
Collaborator Author

This is ready for re-review. Main concern is interaction with 203.

@markshowalter markshowalter left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We're going to need to discuss this. My first issue is that the override option in Cmatrix needs to be implemented via the new Fittable/Mutable API. You can't just replace the definition of an object without notifying any object on which it depends. This is what the new API handles automatically. I did not define class Cmatrix to be Fittable, and recommend that we don't. Instead, a user should be able to define a Navigation frame that operates on the Cmatrix, and then adjust the Navigation. A lot of this needs to be built into the planned rewrite of the host modules. Until then, we can discuss what is specifically needed as an interim fix.

Maybe we can just define every Cassini frame as a Navigation atop a Cmatrix, because a Navigation object has very little overhead, and then you could update the Navigation at will using obs.frame.set_params(). That would guarantee that the changes propagate forward to anything derived from obs, such as backplanes. However, the quantities you use to define the Navigation are 2 or 3 rotation angles, not a brand new Cmatrix.

My only issue with set_cmatrix is the name, because we use "cmatrix" widely in the source code to refer to the OOPS version, whereas this function wants to return the SPICE C kernel definition, whatever it is. The solution would be to rename it get_spice_cmatrix, so there is no ambiguity. Eventually, get_spice_cmatrix will need to be implemented for all hosts, but that can come with the hosts cleanup.

@jnspitale

Copy link
Copy Markdown
Collaborator Author

I also do not like using cmatrix when we're talking about an orientation in the OOPS convention. I use "orientation matrix" to denote this in OMINAS and reserve "cmatrix" for the SPICE orientation matrix. This would be less confusing.

@rfrenchseti

Copy link
Copy Markdown
Collaborator

I'm assuming this should be closed now since it's been superseded by #203.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants