fix(harness): switch VO backend to ORBVisualOdometry

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) <noreply@anthropic.com>
This commit is contained in:
Yuzviak
2026-04-18 14:38:26 +03:00
committed by Maksym Yuzviak
parent c9bd45a098
commit 1ed7729fc2
2 changed files with 7 additions and 6 deletions
+2 -2
View File
@@ -29,7 +29,7 @@ from gps_denied.core.metric import MetricRefinement
from gps_denied.core.models import ModelManager from gps_denied.core.models import ModelManager
from gps_denied.core.processor import FlightProcessor from gps_denied.core.processor import FlightProcessor
from gps_denied.core.recovery import FailureRecoveryCoordinator 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.schemas.graph import FactorGraphConfig
from gps_denied.testing.datasets.base import ( from gps_denied.testing.datasets.base import (
DatasetAdapter, DatasetAdapter,
@@ -175,7 +175,7 @@ class E2EHarness:
streamer.push_event = AsyncMock() streamer.push_event = AsyncMock()
proc = FlightProcessor(repo, streamer) proc = FlightProcessor(repo, streamer)
mm = ModelManager() mm = ModelManager()
vo = SequentialVisualOdometry(mm) vo = ORBVisualOdometry()
gpr = GlobalPlaceRecognition(mm) gpr = GlobalPlaceRecognition(mm)
gpr.load_index(self._flight_id, "dummy") gpr.load_index(self._flight_id, "dummy")
metric = MetricRefinement(mm) metric = MetricRefinement(mm)
+5 -4
View File
@@ -38,9 +38,9 @@ async def test_euroc_mh01_rmse_within_ceiling(euroc_mh01_root: Path):
result = await harness.run() result = await harness.run()
if result.estimated_positions_enu.shape[0] == 0: if result.estimated_positions_enu.shape[0] == 0:
pytest.xfail( pytest.xfail(
"Pipeline currently emits zero GPS estimates on EuRoC — " "Pipeline emits GPS estimates via fallback satellite matching but ESKF never "
"expected: VO works but satellite matching + ESKF anchoring not yet tuned. " "initialises (no start_gps call in harness). VO now engages at 99% with ORB. "
"Convert to regular assert once the pipeline stabilises." "Next: wire ESKF init with a synthetic GPS origin in the harness."
) )
# Align lengths by truncating to shorter (estimates may lag GT at start) # 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]) 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: if ate["rmse"] >= EUROC_MH01_RMSE_CEILING_M:
pytest.xfail( pytest.xfail(
f"ATE RMSE={ate['rmse']:.2f}m exceeds {EUROC_MH01_RMSE_CEILING_M}m ceiling. " 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" assert ate["rmse"] < EUROC_MH01_RMSE_CEILING_M, f"ATE RMSE={ate['rmse']:.2f}m"