mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-23 01:16:38 +00:00
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:
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user