[AZ-334] C1 KLT/RANSAC strategy — engine-rule simple-baseline VIO

Implement KltRansacStrategy, the ADR-002 engine-rule mandatory
simple-baseline VioStrategy for E-C1. Pure-Python facade over
OpenCV's cv2.goodFeaturesToTrack / calcOpticalFlowPyrLK /
findEssentialMat / recoverPose pipeline — no C++/pybind11 binding
by design so a Tier-0 workstation runs the strategy with
`pip install opencv-python` and the BUILD_KLT_RANSAC=ON gate alone.
Constructor + state machine + FDR transition spine mirror
Okvis2Strategy + VinsMonoStrategy so the AZ-331 factory + IT-12
comparative harness treat all three as drop-in substitutable; the
duplication is the consolidation target now formally in scope for
the next cumulative review (batches 52-54).

AC coverage: AC-1..AC-11 + NFR-perf mapped to passing tests
(25 tests, 23 pass + 2 tier-2 skipped on dev/CI runners; all 25
pass under GPS_DENIED_TIER=2). Honest-covariance invariant (AC-9)
implemented as residual-scatter / (N_inliers - 5) with an inlier-
count penalty — no client-side floor or smoother; cov Frobenius
grows monotonically across DEGRADED. Camera-agnostic source
(AC-11) enforced by CI-grep gate that excludes docstring text.

Test-Run Cadence: focused suite tests/unit/c1_vio/ green (95 passed,
6 skipped); config-loader + compose-root suites green; full-suite
gate deferred to Step 16 per implement skill.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-14 02:40:01 +03:00
parent 4815dd6aa1
commit ceb24b5a62
10 changed files with 2371 additions and 14 deletions
@@ -0,0 +1,177 @@
# Batch 54 — Cycle 1 Report
**Date**: 2026-05-14
**Tasks**: AZ-334 (C1 KLT/RANSAC Strategy)
**Verdict**: COMPLETE — PASS_WITH_WARNINGS
## Summary
Implemented `KltRansacStrategy`, the ADR-002 engine-rule mandatory
simple-baseline VIO for C1. Pure-Python facade over OpenCV's
`cv2.goodFeaturesToTrack` / `cv2.calcOpticalFlowPyrLK` /
`cv2.findEssentialMat` / `cv2.recoverPose` pipeline — no C++/pybind11
binding by design so a Tier-0 workstation can run the strategy with
`pip install opencv-python` and the AZ-331 factory's
`BUILD_KLT_RANSAC=ON` gate. Mirrors the AZ-332 OKVIS2 + AZ-333
VINS-Mono facade pattern on the orchestration spine so the AZ-331
factory + IT-12 comparative harness treat all three strategies as
drop-in substitutable.
## Files added / modified
### Added (3)
- `src/gps_denied_onboard/components/c1_vio/klt_ransac.py`
Python facade, ~770 lines.
- `tests/unit/c1_vio/test_klt_ransac_strategy.py` — AC-1..AC-11 +
NFR-perf + KltRansacConfig validation tests, ~990 lines, 25 tests
(23 pass + 2 tier-2 skipped on dev/CI runners).
- `_docs/03_implementation/reviews/batch_54_review.md` — code review
report.
### Modified (4)
- `src/gps_denied_onboard/components/c1_vio/config.py` — add
`KltRansacConfig` dataclass + `klt_ransac` field on `C1VioConfig`;
`__all__` updated. (Pre-existing `KNOWN_STRATEGIES` already had
`klt_ransac` from AZ-331.)
- `src/gps_denied_onboard/components/c1_vio/__init__.py` — export
`KltRansacConfig`.
- `cpp/klt_ransac/CMakeLists.txt` — replace AZ-263 placeholder with
a deliberate "pure-Python; no native target" STATUS message so
the build graph stays symmetric with `cpp/okvis2/` and
`cpp/vins_mono/` while the `BUILD_KLT_RANSAC=ON` flag only gates
the Python module import at the AZ-331 composition-root factory.
- `tests/unit/c1_vio/test_protocol_conformance.py` — introduce the
`_STRATEGIES_WITHOUT_NATIVE_BINDING` category and route
`klt_ransac` through it inside
`test_ac5_build_vio_strategy_flag_on_but_module_missing` so the
parametrised factory test correctly handles the pure-Python
shape (no native binding to fail on; the construction should
succeed and return a `VioStrategy` instance instead).
## AC coverage (AC-1..AC-11 + NFR-perf)
All 11 ACs + NFR-perf mapped to passing tests (see
`batch_54_review.md` Phase 2 table). AC-9 + NFR-perf are
tier-2-tagged per the task spec; they skip on macOS dev + GitHub
Actions Linux runner with `Tier-2-only test; set GPS_DENIED_TIER=2
to run`. AC-3 / AC-6 / AC-9 / AC-10 / AC-11 / NFR-perf monkeypatch
`cv2.findEssentialMat` + `cv2.recoverPose` +
`RansacFilter.filter_correspondences` to deterministic values so
the unit suite exercises the FACADE's state machine without
depending on real OpenCV geometry on synthetic correspondences;
real-geometry validation lives in C1-IT-12 (Jetson Tier-2 fixture).
## Test results
### Focused suite — `tests/unit/c1_vio/`
```
95 passed, 6 skipped in 1.67s
```
The 6 skips are the 2 OKVIS2 tier-2 tests + the 2 VINS-Mono tier-2
tests + the 2 new KLT/RANSAC tier-2 tests (`test_ac9_*` and
`test_nfr_perf_*`).
### Adjacent regression — config / compose-root
```
17 passed in 1.96s
```
(`test_az269_config_loader.py`, `test_az270_compose_root.py` — all
green; the new `KltRansacConfig` registration did not break the
schema-loader or compose-root layered-import guards.)
### Tier-2 verification
```
GPS_DENIED_TIER=2 pytest tests/unit/c1_vio/test_klt_ransac_strategy.py
→ 25 passed in 0.43s
```
All 25 tests pass under the tier-2 gate (including AC-9 honest-
covariance monotonicity over 48 synthetic frames + NFR-perf p95
record).
### Full suite
Deferred per the implement skill's Test-Run Cadence — the full
unit-suite gate runs exactly once at Step 16 (end of implementation
phase), not per-batch. Focused tests + cumulative review (every K
batches) catch cross-batch regressions before then.
## Architectural decisions
- **No native binding by design**: KLT/RANSAC is pure Python over
OpenCV's Python bindings. The `cpp/klt_ransac/CMakeLists.txt`
placeholder is preserved (with an explanatory STATUS message) for
build-graph symmetry with `cpp/okvis2/` and `cpp/vins_mono/`; the
`BUILD_KLT_RANSAC=ON` flag only gates the Python module import at
the AZ-331 composition-root factory.
- **Constructor shape matches factory**: `KltRansacStrategy(config,
*, fdr_client, clock=None)` mirrors `Okvis2Strategy` +
`VinsMonoStrategy` so the AZ-331 factory invokes all three via the
same call shape. The task spec's illustrative constructor
(with explicit injection of `CameraCalibration` / `ImuPreintegrator`
/ `RansacFilter` / `Logger`) was deliberately not adopted because
it would diverge from the existing factory contract; the same
dependencies are resolved internally instead — `RansacFilter` is
static (AZ-282 stateless helper) and `ImuPreintegrator` is
constructed lazily on the first `process_frame` call (it needs
the per-call `CameraCalibration` which is not available at
construction time).
- **Honest covariance, AC-9 compliant**: per-frame covariance =
`np.eye(6) * (sigma_sq + inlier_penalty) / max(inlier_count - 5, 1)`
where `sigma_sq = median_residual_px**2` and `inlier_penalty =
threshold_px / max(inlier_count, 1)`. No client-side floor or
smoother; the formula grows monotonically as inliers drop or
residuals scatter. Tier-2 test walks 48 scripted-inlier frames
through the DEGRADED window and verifies monotonicity.
- **Camera agnostic, AC-11 enforced**: no `adti20` / `adti26`
literals in executable source (docstring mentions are excluded
from the CI grep via AST-aware stripping in the test). The
per-call `CameraCalibration` argument carries intrinsics; the
same code path produces sensible `VioOutput` for two distinct
calibrations (different f, cx, cy).
- **State machine mirrors OKVIS2 / VINS-Mono**: `INIT` until
`warm_start_max_frames` is exhausted, then `TRACKING` if inlier
count ≥ `min_features_for_pose`, else `DEGRADED`; sustained
pose-recovery failure for `lost_frame_threshold` consecutive
frames raises `VioFatalError` with state == `LOST`. Exactly one
`vio.health` FDR record per transition (AC-10).
- **Error envelope closed**: every OpenCV `cv2.error` is caught at
each call site and rewrapped into `VioFatalError` with
`__cause__` chaining (AC-4). `RansacFilterError` +
`ImuPreintegrationError` are also caught and rewrapped.
## Out of scope / deferred
- Real geometry validation against Derkachi fixtures — Tier-2
follow-up; C1-IT-12 binds KLT/RANSAC alongside OKVIS2.
- Warm-start hint persistence (AZ-335) — separate batch.
- Strategy-facade consolidation hygiene PBI — now formally in scope
for the cumulative review covering batches 52-54 (next trigger).
All three strategy shapes (OKVIS2, VINS-Mono, KLT/RANSAC) are
now visible so the right factoring (template-method base class
for the orchestration spine + per-strategy geometry hook) can be
scoped without over-fitting.
## Honest-covariance numeric envelope (AC-9 evidence)
Tier-2 test walks the following inlier-count sequence through the
DEGRADED gate (`min_features_for_pose=50`):
| Frame range | Inlier count | State | Cov Frobenius (approx) |
|-------------|--------------|----------|------------------------|
| 1 | first-frame | INIT | 24.49 (10·√6) |
| 2..29 | 80 | TRACKING | ≈ 0.0056 |
| 30 | 40 | DEGRADED | ≈ 0.013 |
| 31..43 | 35..12 | DEGRADED | strictly increasing |
| 44..49 | 10 | DEGRADED | ≈ 0.127 |
The Frobenius norm grows monotonically across the entire DEGRADED
window — the AC-9 honest-covariance invariant is upheld by the
formula's structure, not by a floor clamp.
@@ -0,0 +1,242 @@
# Code Review Report
**Batch**: 54 (AZ-334 — C1 KLT/RANSAC Strategy)
**Date**: 2026-05-14
**Verdict**: PASS_WITH_WARNINGS
## Scope
Single-task batch implementing `KltRansacStrategy`, the mandatory
simple-baseline `VioStrategy` that satisfies the ADR-002 engine rule
(every component MUST ship a simple-baseline strategy alongside its
production-default). Pure-Python over OpenCV's `cv2.goodFeaturesToTrack`
/ `cv2.calcOpticalFlowPyrLK` / `cv2.findEssentialMat` / `cv2.recoverPose`
path; no C++/pybind11 native binding by design — Tier-0 workstation can
run the strategy with `pip install opencv-python` only.
### Changed files
- `src/gps_denied_onboard/components/c1_vio/klt_ransac.py` — new
Python facade (~770 lines, including module docstring + AC mapping
+ risk-mitigation notes).
- `src/gps_denied_onboard/components/c1_vio/config.py` — add
`KltRansacConfig` dataclass + `klt_ransac` field on `C1VioConfig`;
`__all__` updated; `KNOWN_STRATEGIES` already had `klt_ransac`.
- `src/gps_denied_onboard/components/c1_vio/__init__.py` — export
`KltRansacConfig`.
- `cpp/klt_ransac/CMakeLists.txt` — replace placeholder message
with a deliberate "pure-Python; no native target" explanation so
the build graph stays symmetric with `cpp/okvis2/` and
`cpp/vins_mono/` while the `BUILD_KLT_RANSAC=ON` flag only gates
the Python module import at the AZ-331 composition-root factory.
- `tests/unit/c1_vio/test_klt_ransac_strategy.py` — new test module
covering AC-1..AC-11 + NFR-perf (~990 lines, 25 tests; 23 pass + 2
tier-2 skipped on dev/CI runners).
- `tests/unit/c1_vio/test_protocol_conformance.py` — introduce the
`_STRATEGIES_WITHOUT_NATIVE_BINDING` category and route
`klt_ransac` through it inside `test_ac5_build_vio_strategy_flag_on_but_module_missing`
so the parametrised factory test correctly handles the pure-Python
shape (no native binding to fail on).
## Phase 2 — Spec Compliance
| AC | Test | Verified |
|-------|----------------------------------------------------------------------------|----------|
| AC-1 | `test_ac1_current_strategy_label_returns_klt_ransac` + | ✓ |
| | `test_ac1_constructor_rejects_mismatched_strategy_label` | |
| AC-2 | `test_ac2_first_frame_emits_init_state_with_identity_pose` | ✓ |
| AC-3 | `test_ac3_steady_state_frame_emits_pose_and_spd_covariance` | ✓ |
| AC-4 | `test_ac4_cv2_error_in_find_essential_mat_rewrapped_to_vio_fatal_error` + | ✓ |
| | `test_ac4_cv2_error_in_recover_pose_rewrapped_to_vio_fatal_error` | |
| AC-5 | `test_ac5_reset_to_warm_start_clears_feature_buffer_and_seeds_bias` + | ✓ |
| | `test_ac5_reset_to_warm_start_idempotent_across_consecutive_calls` + | |
| | `test_ac5_reset_to_warm_start_rejects_non_pose3_hint` | |
| AC-6 | `test_ac6_low_inlier_count_emits_degraded_with_monotonic_covariance` | ✓ |
| AC-7 | `test_ac7_sustained_pose_recovery_failure_raises_vio_fatal_error` | ✓ |
| AC-8 | `test_ac8_strategy_module_not_imported_at_package_load` + | ✓ |
| | `test_protocol_conformance.py::test_ac5_build_vio_strategy_flag_*` | |
| AC-9 | `test_ac9_honest_covariance_monotonic_during_degraded` (tier2) | ✓ |
| AC-10 | `test_ac10_fdr_vio_health_emitted_per_transition` | ✓ |
| AC-11 | `test_ac11_source_has_no_camera_id_literals` + | ✓ |
| | `test_ac11_strategy_handles_two_distinct_calibrations` | |
| NFR-perf | `test_nfr_perf_process_frame_records_p95` (tier2) | ✓ |
All 11 ACs + NFR-perf mapped to passing tests. Test suite reports
25 tests, 23 pass + 2 tier2 skipped on the standard dev/CI runner;
all 25 pass under `GPS_DENIED_TIER=2`. Adjacent regression:
`tests/unit/c1_vio/` reports 95 passed + 6 skipped (6 = the 2
KLT-tier2 + 2 OKVIS2-tier2 + 2 VINS-Mono-tier2 tests); config-loader
and compose-root suites green (17 passed).
## Phase 3 — Code Quality
- **SOLID**: `KltRansacStrategy` has a single responsibility (Python
facade over OpenCV's KLT/RANSAC path). Constructor injection per
ADR-009 — `Config` + `FdrClient` enter explicitly; `Clock` is
optional with a `WallClock` default; the AZ-276 `ImuPreintegrator`
is constructed lazily on the first `process_frame` call (it
requires the per-call `CameraCalibration` which is not available
at constructor time — matches the existing factory pattern across
OKVIS2 / VINS-Mono).
- **Error handling**: error envelope closed at the `VioError` family;
every OpenCV `cv2.error` is caught at each call site and rewrapped
into `VioFatalError` with `__cause__` chaining (AC-4). Pose-recovery
failures route through `_pose_recovery_failed` which raises
`VioInitializingError` until `lost_frame_threshold` is exhausted,
then escalates to `VioFatalError` (AC-7). RansacFilterError +
ImuPreintegrationError are also caught and rewrapped.
- **Naming**: matches the OKVIS2 / VINS-Mono facade naming exactly
(intentional — IT-12 harness substitutability).
- **Complexity**: `process_frame` is ~120 lines; the dominant cost is
the explicit step-numbered ladder (IMU push → grayscale → first-frame
branch → KLT track → RANSAC filter → essential-matrix → pose recover
→ covariance → VioOutput build → state classify → re-seed features).
Splitting further would obscure the linear flow that maps 1:1 onto
the task spec's "Outcome" numbered list.
- **DRY**: structural duplication with OKVIS2 / VINS-Mono facades —
see F1 below; deliberately deferred to the post-batch-54 hygiene
PBI (now scheduled by the next cumulative review, batches 52-54).
- **Test quality**: each AC has a behaviourally-meaningful assertion
(covariance SPD, frame_id echoed, transition states ordered,
monotonic covariance growth during DEGRADED, etc.). No "did not
throw" placeholder tests. AC-3 / AC-6 / AC-9 / AC-10 / AC-11 /
NFR-perf monkeypatch `cv2.findEssentialMat` + `cv2.recoverPose`
+ `RansacFilter.filter_correspondences` to deterministic values so
the unit suite exercises the FACADE's state machine without
depending on real OpenCV geometry on synthetic correspondences;
real-geometry validation lives in C1-IT-12 (Jetson Tier-2 fixture).
- **Dead code**: none. `_drain_into_list` removed from the test
helper after the simpler `_drain` was introduced.
## Phase 4 — Security Quick-Scan
- No SQL / command injection paths. No `subprocess(shell=True)`,
`eval`, `exec`.
- No hardcoded secrets, API keys, or credentials.
- Input validation: `_intrinsics_3x3` rejects non-3x3 K with
`VioFatalError`; `_grayscale` rejects unsupported image shapes;
`KltRansacConfig.__post_init__` validates every knob range
(max_corners ≥ 4, klt_window_size_px odd ≥ 3, klt_pyramid_levels
≥ 1, min_features_for_pose ≥ 5, ransac_inlier_ratio in (0, 1],
essential_matrix_ransac_threshold_px > 0).
- Sensitive data: per-frame DEBUG log defaults OFF
(`KltRansacConfig.per_frame_debug_log = False`) — matches
`description.md` § 9 logging hygiene.
PASS.
## Phase 5 — Performance Scan
- Hot path: `process_frame`. IMU push loop is
`O(samples_per_window)` — unavoidable. KLT track + RANSAC are
multi-threaded internally by OpenCV; bound at 30 % of one core
per ADR-002 budget partition.
- No N+1, no unbounded buffers (FdrClient capacity bounded by
AZ-273 ringbuf; the strategy keeps a single
`_prev_features` numpy array sized at `max_corners`).
- Covariance estimator: `_estimate_covariance` does one
`np.eye(6) * scalar` — O(1).
- AC-9 honest-covariance: the residual_var / DOF formula has no
client-side floor; cov Frobenius grows monotonically as
inlier_count drops (verified in tier-2 test).
PASS.
## Phase 6 — Cross-Task Consistency
N/A — single-task batch. Cross-task consistency with AZ-332 +
AZ-333 is tracked in the next cumulative review (batches 52-54),
which will see all three strategy facades and the duplication
finding (F1) at the same time.
## Phase 7 — Architecture Compliance
- **Layer direction**: `klt_ransac.py` imports from `_types.nav`,
`_types.calibration` (TYPE_CHECKING only), `clock.wall_clock`,
`components.c1_vio.config` (TYPE_CHECKING only),
`components.c1_vio.errors`, `fdr_client`, `helpers.imu_preintegrator`,
`helpers.ransac_filter`, `logging` — all L1/L2 substrate per the
c1 layering. PASS.
- **Public API respect**: `KltRansacConfig` exported through
`c1_vio/__init__.py`; `KltRansacStrategy` deliberately NOT exported
(lazy import only via `runtime_root.vio_factory`) — matches the
Risk-2/Risk-3 pattern from OKVIS2 and VINS-Mono. PASS.
- **No new cyclic dependencies**: introduced module is a leaf — no
back-edges to its own importers.
- **Native binding location**: NONE by design. `cpp/klt_ransac/`
carries a CMakeLists that returns a STATUS message documenting
the absence of native target; the directory is preserved for
build-graph symmetry with `cpp/okvis2/` and `cpp/vins_mono/`.
- **Build flag respect**: `BUILD_KLT_RANSAC=OFF` keeps the
composition-root factory from importing the strategy module; the
AZ-331 factory raises `StrategyNotAvailableError` before any
import — Risk-3 mitigation intact for operator-tooling binaries
(which do not need any VIO at all).
- **AC-11 camera agnostic**: source CI-grep gate verifies no
`adti20` / `adti26` literals in executable code (docstrings are
excluded via AST-aware stripping in the test). PASS.
PASS.
## Findings
| # | Severity | Category | File:Line | Title |
|---|----------|----------|-----------|-------|
| 1 | Low | Maintainability | `src/gps_denied_onboard/components/c1_vio/klt_ransac.py` | Structural duplication with `okvis2.py` / `vins_mono.py` — now scheduled for post-batch-54 hygiene PBI via cumulative review |
| 2 | Low | Maintainability | `tests/unit/c1_vio/test_klt_ransac_strategy.py` | `_patch_pose_recovery` helper is bespoke per-strategy; the same patching pattern could plausibly be shared with OKVIS2 / VINS-Mono fake-binding fixtures |
### Finding details
**F1: Structural duplication of strategy facade** (Low / Maintainability)
- Location: `src/gps_denied_onboard/components/c1_vio/klt_ransac.py`
vs `src/gps_denied_onboard/components/c1_vio/okvis2.py` /
`vins_mono.py`
- Description: The new `KltRansacStrategy` mirrors `Okvis2Strategy`
+ `VinsMonoStrategy` ~70 % verbatim on the orchestration spine —
`_classify_state`, `_tick_lost`, `_emit_transition`,
`_bias_norm`, `_now_iso`, `_se3_from_4x4`, the constructor strategy-
label guard, and the FDR record-emit shape are byte-equivalent
modulo strategy-label constants. The geometry-specific pipeline
(KLT seed/track, RANSAC filter, findEssentialMat, recoverPose,
residual-scatter covariance) IS unique to this strategy and lives
in its own module — that boundary is correct. The shared
orchestration spine is the consolidation target tracked by the
hygiene PBI deferred since batch 53; the cumulative review
scheduled for batches 52-54 (next trigger) will formally raise the
PBI now that all three strategy shapes are visible.
- Suggestion: defer (one more time) to the cumulative review for
batches 52-54 — the right factoring is now visible (template-
method base class for the orchestration spine + per-strategy
geometry hook). Do NOT create the PBI ad-hoc here; let the
cumulative review own the cross-batch refactor scope.
- Task: AZ-334
**F2: Test patching helper could be shared** (Low / Maintainability)
- Location: `tests/unit/c1_vio/test_klt_ransac_strategy.py`
(`_patch_pose_recovery`) vs `tests/unit/c1_vio/conftest.py`
(`FakeOkvis2Backend` / `FakeVinsMonoBackend`)
- Description: KLT/RANSAC uses real OpenCV bindings (no fake-binding
fixture) so the existing conftest fakes don't apply directly. The
per-test helper `_patch_pose_recovery` does the equivalent job of
forcing a deterministic-success path. This is a different
abstraction shape from the conftest fakes but lives at the same
layer; consolidating with the post-batch-54 hygiene PBI would let
all three strategies share a single per-strategy "ScriptedSuccess"
fixture surface.
- Suggestion: same as F1 — let the cumulative review's hygiene PBI
own the cross-cutting test-fixture refactor.
- Task: AZ-334
## Verdict
**PASS_WITH_WARNINGS** — two Low-severity duplication findings, both
intentionally deferred and now formally in scope for the next
cumulative review (batches 52-54). No Critical, High, or Medium
findings. All 11 ACs + NFR-perf covered with passing tests.
Pre-existing environment-dependent perf flake noted in batch 53
(`tests/unit/c12_operator_orchestrator/test_cli_console_script.py::test_cold_start_under_500ms_p99`)
is still environmental and untouched by this batch — reported, not
blocking.
+2 -1
View File
@@ -12,5 +12,6 @@ sub_step:
retry_count: 0
cycle: 1
tracker: jira
last_completed_batch: 53
last_completed_batch: 54
last_cumulative_review: batches_49-51
current_batch: 55