From 1ed7729fc2568897cea8ecde565e087389f17c8c Mon Sep 17 00:00:00 2001 From: Yuzviak Date: Sat, 18 Apr 2026 14:38:26 +0300 Subject: [PATCH] fix(harness): switch VO backend to ORBVisualOdometry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SequentialVisualOdometry uses MockInferenceEngine (random keypoints) in dev/CI, so RANSAC on random point pairs finds ≈0 geometric inliers and vo_success is always False. ORBVisualOdometry uses real OpenCV ORB features and achieves 99/100 tracking on EuRoC MH_01. ESKF still never initialises (no start_gps call in harness) — that is the next layer to address. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/gps_denied/testing/harness.py | 4 ++-- tests/e2e/test_euroc.py | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/gps_denied/testing/harness.py b/src/gps_denied/testing/harness.py index ef2ed8c..b006664 100644 --- a/src/gps_denied/testing/harness.py +++ b/src/gps_denied/testing/harness.py @@ -29,7 +29,7 @@ from gps_denied.core.metric import MetricRefinement from gps_denied.core.models import ModelManager from gps_denied.core.processor import FlightProcessor from gps_denied.core.recovery import FailureRecoveryCoordinator -from gps_denied.core.vo import SequentialVisualOdometry +from gps_denied.core.vo import ORBVisualOdometry from gps_denied.schemas.graph import FactorGraphConfig from gps_denied.testing.datasets.base import ( DatasetAdapter, @@ -175,7 +175,7 @@ class E2EHarness: streamer.push_event = AsyncMock() proc = FlightProcessor(repo, streamer) mm = ModelManager() - vo = SequentialVisualOdometry(mm) + vo = ORBVisualOdometry() gpr = GlobalPlaceRecognition(mm) gpr.load_index(self._flight_id, "dummy") metric = MetricRefinement(mm) diff --git a/tests/e2e/test_euroc.py b/tests/e2e/test_euroc.py index f32b136..dc9f721 100644 --- a/tests/e2e/test_euroc.py +++ b/tests/e2e/test_euroc.py @@ -38,9 +38,9 @@ async def test_euroc_mh01_rmse_within_ceiling(euroc_mh01_root: Path): result = await harness.run() if result.estimated_positions_enu.shape[0] == 0: pytest.xfail( - "Pipeline currently emits zero GPS estimates on EuRoC — " - "expected: VO works but satellite matching + ESKF anchoring not yet tuned. " - "Convert to regular assert once the pipeline stabilises." + "Pipeline emits GPS estimates via fallback satellite matching but ESKF never " + "initialises (no start_gps call in harness). VO now engages at 99% with ORB. " + "Next: wire ESKF init with a synthetic GPS origin in the harness." ) # Align lengths by truncating to shorter (estimates may lag GT at start) n = min(result.estimated_positions_enu.shape[0], result.ground_truth.shape[0]) @@ -51,6 +51,7 @@ async def test_euroc_mh01_rmse_within_ceiling(euroc_mh01_root: Path): if ate["rmse"] >= EUROC_MH01_RMSE_CEILING_M: pytest.xfail( f"ATE RMSE={ate['rmse']:.2f}m exceeds {EUROC_MH01_RMSE_CEILING_M}m ceiling. " - "VO + ESKF anchoring not yet tuned for EuRoC indoor MAV imagery." + "VO engages (99%) but ESKF never initialises without a start_gps call in the " + "harness — estimates come from satellite fallback only." ) assert ate["rmse"] < EUROC_MH01_RMSE_CEILING_M, f"ATE RMSE={ate['rmse']:.2f}m"