feat(phases 2-7): implement full GPS-denied navigation pipeline

Phase 2 — Visual Odometry:
  - ORBVisualOdometry (dev/CI), CuVSLAMVisualOdometry (Jetson)
  - TRTInferenceEngine (TensorRT FP16, conditional import)
  - create_vo_backend() factory

Phase 3 — Satellite Matching + GPR:
  - SatelliteDataManager: local z/x/y tiles, ESKF ±3σ tile selection
  - GSD normalization (SAT-03), RANSAC inlier-ratio confidence (SAT-04)
  - GlobalPlaceRecognition: Faiss index + numpy fallback

Phase 4 — MAVLink I/O:
  - MAVLinkBridge: GPS_INPUT 15+ fields, IMU callback, 1Hz telemetry
  - 3-consecutive-failure reloc request
  - MockMAVConnection for CI

Phase 5 — Pipeline Wiring:
  - ESKF wired into process_frame: VO update → satellite update
  - CoordinateTransformer + SatelliteDataManager via DI
  - MAVLink state push per frame (PIPE-07)
  - Real pixel_to_gps via ray-ground projection (PIPE-06)
  - GTSAM ISAM2 update when available (PIPE-03)

Phase 6 — Docker + CI:
  - Multi-stage Dockerfile (python:3.11-slim)
  - docker-compose.yml (dev), docker-compose.sitl.yml (ArduPilot SITL)
  - GitHub Actions: ci.yml (lint+pytest+docker smoke), sitl.yml (nightly)
  - tests/test_sitl_integration.py (8 tests, skip without SITL)

Phase 7 — Accuracy Validation:
  - AccuracyBenchmark + SyntheticTrajectory
  - AC-PERF-1: 80% within 50m 
  - AC-PERF-2: 60% within 20m 
  - AC-PERF-3: p95 latency < 400ms 
  - AC-PERF-4: VO drift 1km < 100m  (actual ~11m)
  - scripts/benchmark_accuracy.py CLI

Tests: 195 passed / 8 skipped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Yuzviak
2026-04-02 17:00:41 +03:00
parent a15bef5c01
commit 094895b21b
40 changed files with 4572 additions and 497 deletions
+84
View File
@@ -0,0 +1,84 @@
name: CI
# Run on every push and PR to main/dev/stage* branches.
on:
push:
branches: [main, dev, "stage*"]
pull_request:
branches: [main, dev]
jobs:
# ---------------------------------------------------------------------------
# Lint — ruff for style + import sorting
# ---------------------------------------------------------------------------
lint:
name: Lint (ruff)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install ruff
run: pip install --no-cache-dir "ruff>=0.9"
- name: Check style and imports
run: ruff check src/ tests/
# ---------------------------------------------------------------------------
# Unit tests — fast, no SITL, no GPU
# ---------------------------------------------------------------------------
test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
needs: lint
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install system deps (OpenCV headless)
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends libgl1 libglib2.0-0
- name: Install package + dev extras
run: pip install --no-cache-dir -e ".[dev]"
- name: Run unit tests (excluding SITL integration)
run: |
python -m pytest tests/ \
--ignore=tests/test_sitl_integration.py \
-q \
--tb=short \
--timeout=60
# ---------------------------------------------------------------------------
# Docker build smoke test — verify image builds successfully
# ---------------------------------------------------------------------------
docker-build:
name: Docker build smoke test
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: docker build -t gps-denied-onboard:ci .
- name: Health smoke test (container start)
run: |
docker run -d --name smoke -p 8000:8000 gps-denied-onboard:ci
sleep 5
curl --retry 5 --retry-delay 2 --fail http://localhost:8000/health
docker stop smoke
+74
View File
@@ -0,0 +1,74 @@
name: SITL Integration
# Run manually or on schedule (nightly on main).
# Requires Docker Compose SITL harness (docker-compose.sitl.yml).
on:
workflow_dispatch:
inputs:
sitl_speedup:
description: SITL simulation speedup factor (default 1)
default: "1"
type: string
schedule:
# Nightly at 02:00 UTC on main branch
- cron: "0 2 * * *"
jobs:
sitl-integration:
name: SITL GPS_INPUT integration
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build gps-denied image
run: docker build -t gps-denied-onboard:sitl .
- name: Pull ArduPilot SITL image
run: docker pull ardupilot/ardupilot-dev:latest
- name: Start SITL services
run: |
docker compose -f docker-compose.sitl.yml up -d ardupilot-sitl gps-denied
echo "Waiting for SITL to become healthy..."
for i in $(seq 1 30); do
if docker compose -f docker-compose.sitl.yml ps ardupilot-sitl \
| grep -q "healthy"; then
echo "SITL is healthy"
break
fi
echo " attempt $i/30..."
sleep 5
done
- name: Run SITL integration tests
run: |
docker compose -f docker-compose.sitl.yml run \
--rm \
-e ARDUPILOT_SITL_HOST=ardupilot-sitl \
-e ARDUPILOT_SITL_PORT=5762 \
integration-tests
- name: Collect logs on failure
if: failure()
run: |
docker compose -f docker-compose.sitl.yml logs ardupilot-sitl > sitl.log 2>&1
docker compose -f docker-compose.sitl.yml logs gps-denied > gps-denied.log 2>&1
- name: Upload logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: sitl-logs
path: |
sitl.log
gps-denied.log
retention-days: 7
- name: Stop SITL services
if: always()
run: docker compose -f docker-compose.sitl.yml down -v