mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-21 10:31:13 +00:00
[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:
@@ -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.
|
||||
Reference in New Issue
Block a user