Wires a real CoordinateTransformer into the processor and seeds the ESKF
with the dataset's first ground-truth lat/lon/alt before the frame loop.
Result on EuRoC MH_01 (100 frames):
eskf_initialized: 0/100 → 100/100
vo_success: 99/100 (unchanged)
eskf_has_position: 100/100
Satellite measurements are now correctly rejected by the Mahalanobis gate
(Δ² ~10⁶) because ORB produces unit-scale translations (scale_ambiguous=True)
which drive the ESKF position to diverge rapidly. The gate is working as
intended — the remaining issue is VO metric scale, not ESKF initialisation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Drives ORBVisualOdometry directly on raw EuRoC frames, bypassing ESKF and
satellite layers. ORB achieves 100% tracking on 99 frame pairs, confirming
that vo_success=0 in the full pipeline is caused by SequentialVisualOdometry's
MockInferenceEngine (random keypoints → RANSAC failure), not by VO backend
limitations on EuRoC indoor imagery.
Two tests: tracking rate ≥70% (passes, currently 100%), and sanity check
that at least one pair yields non-zero inliers (passes).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Opt-in trace_path parameter dumps one JSON record per processed frame
with the fields diagnostics need:
frame_idx, timestamp_ns, vo_success, alignment_success,
tracking_state, confidence,
eskf_initialized, eskf_position_enu (or None), eskf_pos_sigma_m,
estimate_lat/lon, gt_lat/lon/alt
No perf cost when trace_path is None. File is rotated per run — safe to
point at /tmp/foo.jsonl for ad-hoc debugging.
First real run on EuRoC MH_01 (100 frames) immediately exposes the
concrete divergence: vo_success=0/100 (VO never engages on EuRoC
grayscale imagery with current SP+LG adapter), eskf_initialized=0/100,
alignment_success=77/100 (satellite-fallback path fires). Diagnosis
that was hidden behind a single "ATE=10.9 km" number is now machine-
readable per frame.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Updates README, testing/README, next_steps.md, and ADR 0001 with the
first real EuRoC MH_01 e2e run (100 frames, ~30s wall-time, ATE RMSE
~10.9 km → xfail). Places the EuRoC result alongside the prior VPAIR
baseline (~1770 km) so future-reader can see both failure modes at a
glance:
- VPAIR diverges because no raw IMU → ESKF never engages
- EuRoC diverges because indoor scene has no satellite anchor, so
VO+ESKF drift without an external correction
Also records the branching policy (rename ``euroc_mh01`` →
``euroc_machine_hall``; empty URL due to DSpace UI gate; manual
fetch via DOI 10.3929/ethz-b-000690084).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The prior registry entry was speculative: ``euroc_mh01`` pointing at an
old ``robotics.ethz.ch`` URL that no longer resolves (TCP timeout).
The dataset moved to ETH Research Collection (DOI 10.3929/ethz-b-000690084)
as a single 12.6 GB ``machine_hall.zip`` bundle containing MH_01…MH_05.
There's no stable direct download URL — DSpace gates behind a UI —
so:
- Renamed entry: ``euroc_mh01`` → ``euroc_machine_hall`` (matches the
actual artifact).
- SHA256 set to the real bundle hash 5ed7d07…
- URL left empty (same pattern as ``vpair_sample``); the CLI now
exits 3 and prints fetch instructions for empty-URL entries instead
of crashing on ``urllib.request.urlretrieve("")``.
- Adapter ``DatasetNotAvailableError`` message and conftest skip-reason
updated to tell engineers how to fetch/unpack manually.
- ``test_registry_has_euroc_machine_hall`` pin test replaces the old
pin; asserts real hash (not the ``"0"*64`` placeholder).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First real e2e run on EuRoC MH_01 (indoor micro-MAV, ASL format from
machine_hall bundle, SHA256 5ed7d07…). 100-frame CI-tier completes in
~30s end-to-end. Pipeline emits GPS estimates (raw IMU present in
EuRoC so ESKF path is active), but ATE RMSE ≈ 10.9 km on an indoor
trajectory that physically spans ~20 m — satellite-anchoring path is
not yet wired for indoor data, so VO+ESKF drift dominates.
Test gates via xfail (same pattern as VPAIR) until VO/ESKF tuning is
done. Constant EUROC_MH01_MAX_FRAMES is explicit so the cap is
discoverable and easy to raise when full-sequence runs become
worthwhile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caps the iteration length (and the matching GT slice) when set, so CI
tiers can stay fast on multi-thousand-frame sequences like EuRoC MH_01
(3682 frames ≈ 3+ hours at 3-5s/frame). Also useful for eyeballing a
new adapter's first N frames before committing to a full run.
Three new harness tests cover truncation, explicit None, and over-large
limits. No change to existing adapters or downstream tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an "E2E Test Harness (Public UAV Datasets)" subsection to
§Testing Strategy explaining the three-tier adapter layout and
pointing readers at testing/README.md for architecture and ADR 0001
for selection rationale. Updates Related Artifacts to list the new
in-repo docs. next_steps.md cross-links the ADR as the authoritative
decision record (brainstorm drafts stay local).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First Architecture Decision Record for this project. Captures the
rationale for building the e2e harness on VPAIR / MARS-LVIG / EuRoC
rather than blocking on proprietary Mavic data collection; lists
three alternatives considered and why rejected; records the first
real-run baseline (VPAIR ATE ~1770 km) as a measurable starting
point for future VO+ESKF tuning regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Explains the DatasetAdapter contract (name/capabilities/iter_*),
capability-flag semantics (has_raw_imu, has_rtk_gt, platform_class),
the recipe for adding a new adapter (fabricated fixture → adapter →
conftest fixture → integration test → registry SHA256), and the
current state of each shipped adapter including the VPAIR ~1770 km
ATE real-run baseline. Lives next to the code so it stays in sync.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The comment above `on:` says "push and PR to main/dev/stage*" but the
pull_request trigger only listed [main, dev]. Result: PRs into stage
branches got no automated lint/test run — we noticed when PR #1
(into stage1) showed "no checks reported".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four I001 violations surfaced when running ruff over the full src/
tests/ tree (the CI command) rather than just the testing subpath:
- src/gps_denied/testing/coord.py
- src/gps_denied/testing/datasets/vpair.py
- tests/e2e/test_coord.py
- tests/e2e/test_vpair_adapter.py
All auto-fixable; no behavioural change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Converts the raw notes into a living roadmap:
- checklist items for each of the 4 top-level tasks
- [decision] annotations that record WHY we chose each path
(public datasets vs proprietary capture; VPAIR first vs EuRoC;
DJI Mavic deprioritised; EuRoC URL migration)
- current status of e2e harness (section 3) with real-run numbers
- chronology section so future-you can trace the timeline
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Record first real e2e run on VPAIR sample (fixed-wing, 300-400 m
nadir): pipeline completes, ATE RMSE ~1770 km → xfail. VO without
IMU/satellite anchoring diverges on fixed-wing. Covered by xfail
branch; expected to flip to strict assert after VO+GPR tuning for
high-altitude nadir imagery.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
URL left empty because VPAIR sample is form-gated on Zenodo.
Registry records the known-good SHA256 for manual downloads; the
download_dataset() helper refuses empty URLs so this cannot be used
to auto-fetch a changed artifact.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Real VPAIR sample layout differs from the prior speculative adapter:
- poses_query.txt (not poses.csv) with ECEF xyz + Euler roll/pitch/yaw
- no native timestamps — synthesised at 5 Hz
- PNG images referenced by relative filepath
Adapter now uses coord helpers (ecef_to_wgs84, euler_to_quaternion).
Test fixture and conftest skip-reason updated to match.
Integration test xfail condition extended to cover large ATE values
when VO+GPR is not yet tuned for 300-400m nadir aerial imagery.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous commit 56d2e98 asserted lat=48.1351/lon=11.5820/alt=520 for
ECEF (4177789.3, 855098.1, 4727807.9) — those numbers were a
copy-paste guess from an external converter, not consistent with the
stated ECEF input. Both Heikkinen closed-form and Bowring iterative
independently give lat≈48.1414°, lon≈11.5674°, alt≈570.75 m from that
input. Implementation was correct; test data was wrong.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closed-form Heikkinen method for ECEF conversion (centimetre accuracy,
no iteration). ZYX aerospace-convention Euler → quaternion. Both needed
by upcoming VPAIRAdapter rewrite; reusable for other datasets shipping
ECEF or Euler poses (e.g. some MARS-LVIG releases).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
test_eskf.py: 18 tests covering initialization, IMU prediction, VO/satellite
updates, confidence tiers, and full fusion integration.
test_coordinates.py: 17 new tests for K matrix, ray-ground intersection,
pixel-GPS roundtrips, and cv2.perspectiveTransform homography.
All 35 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>