mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-04-22 07:06:38 +00:00
refactor(vo): address final review — accurate docstring + update_depth_hint tests
Final review findings (Important): - I1: e2e test docstring overclaimed — harness always uses ORBVisualOdometry. Rewrite docstring to describe the actual scope: smoke test + ORB regression guard. Wiring Mono-Depth wrapper through the harness is a sprint 2 task. - I2: update_depth_hint had no tests. Add 2 tests: clamp at 1.0m for bogus values, and verify next compute_relative_pose uses the updated scale. - I3: add TODO marker for sprint 2 deduplication with CuVSLAMVisualOdometry. No behavior change — only docstrings, TODO markers, and test coverage. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -395,6 +395,9 @@ class CuVSLAMVisualOdometry(ISequentialVisualOdometry):
|
||||
# ---------------------------------------------------------------------------
|
||||
# CuVSLAMMonoDepthVisualOdometry — cuVSLAM Mono-Depth mode (sprint 1 production)
|
||||
# ---------------------------------------------------------------------------
|
||||
# TODO(sprint 2): collapse duplicated SDK-load / _init_tracker scaffolding with
|
||||
# CuVSLAMVisualOdometry once Inertial mode is removed. Kept separate for sprint 1
|
||||
# so the Inertial → Mono-Depth migration is reversible.
|
||||
|
||||
# Reference altitude used to normalise ORB unit-scale translation in dev/CI.
|
||||
# At this altitude the ORB unit vector is scaled to match expected metric displacement.
|
||||
|
||||
+16
-9
@@ -115,19 +115,27 @@ async def test_euroc_mh01_gps_rmse_within_ceiling(euroc_mh01_root: Path):
|
||||
@pytest.mark.needs_dataset
|
||||
@pytest.mark.asyncio
|
||||
async def test_euroc_mh01_mono_depth_within_ceiling(euroc_mh01_root: Path):
|
||||
"""Mono-Depth backend ATE on EuRoC — regression guard for VO migration.
|
||||
"""CuVSLAMMonoDepthVisualOdometry instantiation smoke test + ORB-pipeline regression guard.
|
||||
|
||||
Verifies CuVSLAMMonoDepthVisualOdometry._compute_via_orb_scaled produces
|
||||
metric translations consistent with the baseline ORB pipeline when
|
||||
depth_hint_m scale equals the calibrated VO_SCALE_M.
|
||||
Scope of this test:
|
||||
1. SMOKE: CuVSLAMMonoDepthVisualOdometry constructs with EuRoC-typical
|
||||
depth_hint and camera params without raising.
|
||||
2. REGRESSION GUARD: runs the existing ORB-based harness (which uses
|
||||
ORBVisualOdometry() directly — see src/gps_denied/testing/harness.py)
|
||||
with the calibrated VO_SCALE_M and asserts ATE stays under 0.5 m.
|
||||
|
||||
EuRoC indoor != production outdoor nadir. Poor ATE here is not a blocker
|
||||
for production. Test documents baseline and prevents unexpected regression.
|
||||
NOT in scope (deliberately): this test does NOT exercise
|
||||
CuVSLAMMonoDepthVisualOdometry through the pipeline. The E2EHarness
|
||||
currently hardcodes ORBVisualOdometry(); wiring the Mono-Depth wrapper
|
||||
through the harness is a sprint 2 task. The scale-math invariant is
|
||||
covered by test_mono_depth_depth_hint_scales_translation in test_vo.py.
|
||||
|
||||
EuRoC indoor != production outdoor nadir. Baseline ATE 0.2046m documented.
|
||||
"""
|
||||
from gps_denied.core.vo import CuVSLAMMonoDepthVisualOdometry
|
||||
from gps_denied.schemas import CameraParameters
|
||||
|
||||
# Sanity: class instantiates and reports metric scale.
|
||||
# (1) Smoke test — class instantiates with EuRoC-typical params.
|
||||
cam = CameraParameters(
|
||||
focal_length=16.0, sensor_width=23.2, sensor_height=17.4,
|
||||
resolution_width=752, resolution_height=480,
|
||||
@@ -137,8 +145,7 @@ async def test_euroc_mh01_mono_depth_within_ceiling(euroc_mh01_root: Path):
|
||||
)
|
||||
assert vo._depth_hint_m == EUROC_MH01_MONO_DEPTH_HINT_M
|
||||
|
||||
# Full e2e using the calibrated scale — pipeline-equivalent to baseline
|
||||
# ORB until cuVSLAM SDK lands on Jetson.
|
||||
# (2) Regression guard — ORB pipeline baseline stays under 0.5m ATE.
|
||||
adapter = EuRoCAdapter(euroc_mh01_root)
|
||||
harness = E2EHarness(
|
||||
adapter, max_frames=EUROC_MH01_MAX_FRAMES, vo_scale_m=EUROC_MH01_VO_SCALE_M,
|
||||
|
||||
@@ -301,3 +301,43 @@ def test_mono_depth_create_vo_backend_selects_it():
|
||||
from gps_denied.core.vo import CuVSLAMMonoDepthVisualOdometry, create_vo_backend
|
||||
vo = create_vo_backend(prefer_mono_depth=True, depth_hint_m=600.0)
|
||||
assert isinstance(vo, CuVSLAMMonoDepthVisualOdometry)
|
||||
|
||||
|
||||
def test_mono_depth_update_depth_hint_clamps_below_one():
|
||||
"""update_depth_hint clamps bogus/negative barometer values to minimum 1.0m."""
|
||||
from gps_denied.core.vo import CuVSLAMMonoDepthVisualOdometry
|
||||
vo = CuVSLAMMonoDepthVisualOdometry(depth_hint_m=600.0)
|
||||
vo.update_depth_hint(-5.0)
|
||||
assert vo._depth_hint_m == 1.0
|
||||
vo.update_depth_hint(0.0)
|
||||
assert vo._depth_hint_m == 1.0
|
||||
vo.update_depth_hint(0.5)
|
||||
assert vo._depth_hint_m == 1.0
|
||||
|
||||
|
||||
def test_mono_depth_update_depth_hint_affects_subsequent_scale():
|
||||
"""update_depth_hint changes scale used by next compute_relative_pose call."""
|
||||
from unittest.mock import patch
|
||||
from gps_denied.core.vo import CuVSLAMMonoDepthVisualOdometry, ORBVisualOdometry
|
||||
from gps_denied.schemas.vo import RelativePose
|
||||
|
||||
cam = CameraParameters(
|
||||
focal_length=16.0, sensor_width=23.2, sensor_height=17.4,
|
||||
resolution_width=640, resolution_height=480,
|
||||
)
|
||||
img = np.zeros((480, 640), dtype=np.uint8)
|
||||
unit_pose = RelativePose(
|
||||
translation=np.array([1.0, 0.0, 0.0]),
|
||||
rotation=np.eye(3),
|
||||
confidence=1.0, inlier_count=100, total_matches=100,
|
||||
tracking_good=True, scale_ambiguous=True,
|
||||
)
|
||||
|
||||
with patch.object(ORBVisualOdometry, "compute_relative_pose", return_value=unit_pose):
|
||||
vo = CuVSLAMMonoDepthVisualOdometry(depth_hint_m=600.0)
|
||||
pose_before = vo.compute_relative_pose(img, img, cam)
|
||||
vo.update_depth_hint(1200.0)
|
||||
pose_after = vo.compute_relative_pose(img, img, cam)
|
||||
|
||||
assert np.allclose(pose_before.translation, [1.0, 0.0, 0.0]) # 600/600 = 1.0x
|
||||
assert np.allclose(pose_after.translation, [2.0, 0.0, 0.0]) # 1200/600 = 2.0x
|
||||
|
||||
Reference in New Issue
Block a user