[AZ-358] [AZ-361] C4 OpenCVGtsamPoseEstimator + Jacobian thermal hybrid

Implement the single production-default C4 PoseEstimator strategy.

AZ-358 — Marginals path: OpenCV solvePnPRansac (SOLVEPNP_IPPE) on
best-candidate inliers, PriorFactorPose3 with Jacobian-derived initial
covariance, flushed into C5's iSAM2 graph via the widened
ISam2GraphHandle.update(graph, values, None) (Option B). Posterior
covariance from compute_marginals().marginalCovariance(pose_key) with
SPD-defensive Cholesky check. Tile pixel -> ENU world conversion via
the shared WgsConverter + a configurable tile_size_px. Two spec
deviations now documented in the AZ-358 task file: PriorFactorPose3
over GenericProjectionFactorCal3DS2 (avoids unbounded landmark
variables; same Fisher information on the pose marginal) and explicit
(graph, values, timestamps) update args (aligns with C5's impl).

AZ-361 — Jacobian + thermal hybrid: per-frame dispatch on
thermal_state.thermal_throttle_active selects the cv2.projectPoints-
derived 6x6 information matrix (with ridge regularisation) as the
emitted covariance. Skips the iSAM2 factor add under throttle
(Invariant 12). Emits CovarianceDegradedWarning via warnings.warn
(never raised); paired WARN log + FDR record rate-limited per
covariance_degraded_warn_window_ns (default 60 s) via an injected
monotonic Clock. Supersedes the AZ-358 NotImplementedError stub.

Widens ISam2GraphHandle from get_pose_key only to all five C4-facing
methods (add_factor, update, compute_marginals, last_anchor_age_ms);
C5's existing ISam2GraphHandleImpl already satisfies the superset, so
no C5 source change this batch. Threads fdr_client + clock through
pose_factory composition.

Registers two new FDR payload kinds: pose.frame_done (per-call
telemetry; both success and PnpFailureError paths) and
pose.covariance_degraded (per-window throttle exposure).

Tests: 21 new (AZ-358 AC-1..11 + AZ-361 AC-1..10/12/13; AZ-361 AC-11
RMSE-ratio informational per spec, not asserted). Updates 2 existing
test files for Protocol widening and the FDR-schema round trip.

Code review verdict: PASS_WITH_WARNINGS (5 findings: Medium x2,
Low x3; none blocking). Full suite: 1958 passed, 1 unrelated
host-dependent perf failure (c12 CLI cold-start, pre-existing).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 05:01:14 +03:00
parent 360aece7a6
commit 4eac24f37a
13 changed files with 2452 additions and 35 deletions
@@ -36,12 +36,33 @@ class C4PoseConfig:
* ``thermal_throttle_threshold_celsius`` — informational only;
the actual ``ThermalState.thermal_throttle_active`` decision is owned by C7
(AZ-302). Default 75.0 °C.
* ``covariance_degraded_warn_window_ns`` — AZ-361 rate-limit
window for ``CovarianceDegradedWarning`` emissions AND the
paired ``c4.pose.covariance_degraded`` WARN log + FDR record.
Default 60 s. Set to 0 to disable rate-limiting (useful in
tests that want to see every warning).
* ``ridge_regularisation_epsilon`` — AZ-361 ridge added to
``JᵀJ/σ²`` before inversion to stabilise near-singular
Jacobians on degenerate inlier sets. Default 1e-9; raise
to 1e-6 if Jacobian-path SPD failures begin spiking in
forensics.
* ``tile_size_px`` — AZ-358 satellite-tile pixel dimensions
(square). Used to map ``MatchResult`` inlier tile-pixel
coordinates back to WGS84 lat/lon → local-ENU world points
consumed by ``solvePnPRansac``. Default 256 matches the OSM /
C6 tile-cache convention. If the upstream tile source
provides a different square size, override at composition
time; the spec assumes a square tile (any non-square tile
handling would land in a future config extension).
"""
strategy: str = "opencv_gtsam"
ransac_iterations: int = 200
ransac_reprojection_threshold_px: float = 4.0
thermal_throttle_threshold_celsius: float = 75.0
covariance_degraded_warn_window_ns: int = 60_000_000_000
ridge_regularisation_epsilon: float = 1e-9
tile_size_px: int = 256
def __post_init__(self) -> None:
if self.strategy not in KNOWN_POSE_STRATEGIES:
@@ -62,3 +83,17 @@ class C4PoseConfig:
"C4PoseConfig.thermal_throttle_threshold_celsius must be > 0; "
f"got {self.thermal_throttle_threshold_celsius}"
)
if self.covariance_degraded_warn_window_ns < 0:
raise ConfigError(
"C4PoseConfig.covariance_degraded_warn_window_ns must be >= 0; "
f"got {self.covariance_degraded_warn_window_ns}"
)
if self.ridge_regularisation_epsilon <= 0.0:
raise ConfigError(
"C4PoseConfig.ridge_regularisation_epsilon must be > 0; "
f"got {self.ridge_regularisation_epsilon}"
)
if self.tile_size_px <= 0:
raise ConfigError(
f"C4PoseConfig.tile_size_px must be > 0; got {self.tile_size_px}"
)