Files
gps-denied-onboard/docs/superpowers/specs/2026-04-18-oss-stack-tech-audit-design.md
T
Yuzviak dfac8d32b4 docs(tech-audit): expand design doc with reconciliation, risk budget, aero-vloc plan, SITL decomposition
Adds: solution.md reconciliation (cuVSLAM Inertial→Mono-Depth gap),
migration steps through e2e harness, risk budget decision tree,
aero-vloc benchmark action plan with pass/fail criteria,
and SITL GPS_INPUT test decomposition with MAVProxy reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 15:57:49 +03:00

360 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Tech Audit — Open-Source Stack для GPS-Denied Navigation
**Дата:** 2026-04-18
**Горизонт:** 23 тижні (sprint 1)
**Контекст:** Fixed-wing БПЛА, Jetson Orin Nano Super, nadir камера ADTI 20L V1, ArduPilot FC
**Regression guard:** 196 passed / 8 skipped (non-e2e). EuRoC ESKF ATE baseline ~0.205 м (100 кадрів).
---
## 0. Reconciliation: три джерела
Цей документ синтезує три джерела і де вони суперечать одне одному — фіксує явно.
| Джерело | Ключове твердження | Статус |
|---|---|---|
| `solution.md` | cuVSLAM **Inertial mode** з IMU @200 Hz | ⚠️ Архітектурна помилка — Inertial потребує stereo |
| `stage2_ideas/cross_view_place_recognition_stack.md` | SP+LG+FAISS для satellite matching | ✅ Backlog сам каже "порівняй з AnyLoc першим" |
| Ресерч (2026-04-18) | cuVSLAM Mono-Depth + barometer; DINOv2-VLAD як baseline GPR | ✅ Консистентний з stage2 backlog |
**Що НЕ робимо зараз:** переписувати `solution.md`. Це окрема задача з `next_steps.md` ("Аудит відповідності солюшну"). Цей план — migration поверх існуючого ORB baseline, не greenfield.
**Що змінюємо:** фіксуємо cuVSLAM Mono-Depth як sprint 1 production VO (замість Inertial який фізично неможливий без stereo). GPR: AnyLoc як sprint 1 baseline, SP+LG — stage 2 evaluation (консистентно з backlog).
---
## 1. Архітектурний розрив
### cuVSLAM Inertial mode потребує stereo — у нас mono
`solution.md` планує cuVSLAM Inertial mode. Але:
- cuVSLAM Visual-Inertial = **stereo камера обов'язкова**. Mono-Inertial режиму не існує у v15.
- cuVSLAM Mono дає лише rotation, без metric scale → неможливо побудувати GPS_INPUT.
- Camera пілота (вперед) + nadir камера (вниз) ≠ stereo (stereo = дві камери в одному напрямку, ∆x = 10–20 см).
**Рішення для sprint 1:** cuVSLAM **Mono-Depth** — барометрична висота подається як synthetic depth, відновлює metric scale.
Scale для nadir над рівною місцевістю детермінований: `scale = altitude / focal_length`. Барометр → ESKF → scale. VO потребує лише точний 2D planar tracking.
### Чому drift на маршруті не критичний
```
VO drift між кадрами (~0.3–0.5 м на сотні метрів)
ESKF + IMU (короткострокова стабілізація, ~мс)
Place Recognition ← satellite tiles (глобальна корекція кожні ~500 м)
GTSAM loop closure (stage 2)
```
GPR скидає накопичений drift → загальна похибка на 10 км маршруту: **15 м**. Прийнятно для GPS-denied.
**Ризик GPR:** хмари, однорідна місцевість (поле, ліс), застарілі тайли → drift накопичується до наступного match. Це відоме обмеження, не сюрприз.
---
## 2. Рішення по шарах стеку
### 2.1 Visual Odometry
| Компонент | Dev/CI | Production (Jetson) | Рішення |
|---|---|---|---|
| **VO backend** | ORBVisualOdometry (OpenCV) | cuVSLAM **Mono-Depth** | Фіксуємо Mono-Depth замість Inertial |
| **SuperPoint+LightGlue** | — | — | ❌ Відхилено для VO: 15–33× повільніше cuVSLAM, немає IMU fusion |
| **XFeat** | — | Satellite tile matching | Не VO fallback — окремий трек (§2.3) |
**Відхилені альтернативи для sprint 1:**
- **ORB-SLAM3 Mono-Inertial** (~0.08 м EuRoC) — потребує IMU ≥100 Hz по MAVLink. ArduPilot за замовчуванням шле 50 Hz. Можливо після підняття `SR*_RAW_SENS`, але не пріоритет.
- **cuVSLAM Stereo-Inertial** (0.054 м) — потребує hardware (друга камера). Довгострокова ціль.
### 2.2 ESKF
| Рішення | Обґрунтування |
|---|---|
| Залишаємо numpy 15-state ESKF | Достатній для 5–10 Hz VO на повільному fixed-wing |
| **Пінити `numpy==1.26.4`** | NumPy 2.0 ламає GTSAM Python bindings silently (issue #2264) |
| `manifpy` — тільки якщо знайдемо quaternion баги | pip-installable, не додаємо превентивно |
| IMU preintegration | Не потрібен на 510 Hz — step-by-step propagation еквівалентний |
### 2.3 Place Recognition
**Поточний стан:** MockInferenceEngine (dev), Faiss numpy fallback. Реального GPR ще немає.
**Sprint 1 baseline:** AnyLoc-VLAD-DINOv2 (offline-capable).
| Компонент | Рішення | Обґрунтування |
|---|---|---|
| **Global descriptor** | DINOv2-VLAD (AnyLoc) | 1012 мс TRT FP16 на Jetson, offline |
| **Local matching** | XFeat (satellite↔UAV frame) | ~50100 мс TRT, cross-view gap краще ніж ORB |
| **Index** | Faiss GPU | Залишаємо |
| **INT8 quantization** | ❌ Не робити | Broken для ViT на Jetson (NVIDIA/TRT#4348, dinov2#489) |
| **NetVLAD** | ❌ Deprecated | DINOv2-VLAD +2.4% R@1 на MSLS 2024 |
| **Satellite tiles** | MapTiler offline MBTiles | Zoom 18 (0.6 м/px), Україна, JPEG offline |
**Stage 2 evaluation (не зараз):** SuperPoint+LightGlue+FAISS (з `stage2_ideas/`) — backlog сам каже порівняти з AnyLoc першим. Ця оцінка відбудеться після того як AnyLoc baseline зафіксований і виміряний.
**Довгострокова ціль:** EigenPlaces (ICCV 2023) — кращий ONNX export, viewpoint-robust.
### 2.4 Factor Graph (GTSAM)
**Sprint 1: пропускаємо.** ESKF достатній для Gaussian GPS_INPUT 510 Hz.
FGO дає перевагу (~15 vs 34 см) лише при non-Gaussian noise з outliers. Коли прийде час — GTSAM 4.2 stable (не 4.3a1 alpha). miniSAM стейл з 2019. g2o Python experimental.
### 2.5 MAVLink / ArduPilot
| Рішення | Обґрунтування |
|---|---|
| **pymavlink** залишаємо | MAVSDK-Python не підтримує GPS_INPUT (PX4-first) |
| Reference: `MAVProxy/modules/mavproxy_GPSInput.py` | Точне кодування GPS_INPUT #232: lat/lon ×1e7, alt мм, vel см/с |
| Injection rate: 510 Hz | Не 20 Hz — timing jitter задокументований при вищих rate |
| Yaw extension field | Не використовувати — ArduPilot 4.x ігнорує |
---
## 3. Міграційний план через e2e-харнес
**Принцип:** кожна зміна VO backend проходить через E2EHarness до merge. Поточний regression guard — 196 passed / 8 skipped. EuRoC ESKF RMSE ceiling = 0.5 м (2× від baseline ~0.205 м).
### Крок 0 — стабілізація baseline (тиждень 1, день 1–2)
```bash
# Зафіксувати numpy
pip install numpy==1.26.4
# Записати в pyproject.toml: numpy>=1.24,<2.0
# Верифікувати що baseline не зламався
pytest tests/ -x --ignore=tests/e2e -q # має бути 196 passed
pytest tests/e2e/test_euroc.py -v --dataset-path ./datasets/euroc # ATE ~0.205 м
```
**Gate:** якщо ATE після numpy pin відхиляється > 5% від 0.205 м → зупинитись і дебажити до merge.
### Крок 1 — cuVSLAM Mono-Depth adapter (тиждень 1, день 3–5)
1. Додати `CuVSLAMMonoDepthVisualOdometry` backend у `src/gps_denied/core/vo.py`
2. Приймає барометричну висоту як `depth_hint_m` параметр
3. Mock реалізація для dev/CI (повертає scale-corrected RelativePose)
4. Запустити e2e на EuRoC: порівняти ATE з ORB baseline
```bash
pytest tests/e2e/test_euroc.py -v -k "cuvslam_mono_depth"
# Записати результат в docs/
```
**Gate:** ATE cuVSLAM Mono-Depth має бути ≤ 0.5 м (поточний ceiling). Якщо гірше → див. Risk Budget (§4).
### Крок 2 — AnyLoc GPR integration (тиждень 2)
1. Реалізувати `AnyLocGPR` клас що замінює MockInferenceEngine
2. Offline DINOv2-VLAD descriptor, Faiss index на test tile set
3. e2e на VPAIR sample (є satellite-like tiles): перевірити GPR hit rate
4. EuRoC: GPS-estimate ATE залишиться xfail (indoor, немає реальних тайлів — ок)
### Крок 3 — SITL MAVLink integration (тиждень 3)
Деталі в §6.
### Крок 4 — aero-vloc benchmark (тиждень 2–3, паралельно)
Деталі в §5.
---
## 4. Risk Budget: якщо cuVSLAM Mono-Depth гірше ніж ORB
**Сценарій:** cuVSLAM Mono-Depth на EuRoC дає ATE > 0.205 м (поточний ORB baseline).
Це **очікувано і не є блокером** — EuRoC indoor ≠ наш production сценарій (outdoor nadir, відома висота). Але потрібне рішення.
### Дерево рішень
```
cuVSLAM Mono-Depth ATE на EuRoC
├─ ≤ 0.205 м (краще або рівно ORB)
│ → ✅ Merge, продовжуємо
├─ 0.205–0.5 м (гірше ніж ORB але в межах ceiling)
│ → ⚠️ Прийнятно для sprint 1 — EuRoC не є target dataset
│ → Записати в docs, відкрити тікет для наступного кроку
│ → Продовжуємо, але плануємо IMU rate upgrade
└─ > 0.5 м (перевищує ceiling)
→ Три варіанти:
├─ A) Тюнінг depth_hint scaling (барометр calibration)
│ Тривалість: 1 день
│ Спробуй першим — часто проблема в неправильному scale factor
├─ B) Підняти IMU rate → 200 Hz (SR*_RAW_SENS)
│ + перейти на ORB-SLAM3 Mono-Inertial (~0.08 м EuRoC)
│ Тривалість: 3–5 днів (C++ build + Python binding)
│ Потрібно: підтвердити FC має H743, перевірити UART bandwidth
└─ C) Залишити ORB як production VO тимчасово
+ зосередитись на GPR (AnyLoc) як основному correction механізмі
Тривалість: 0 (нічого не міняємо у VO)
Прийнятно якщо GPR hits кожні ≤ 500 м
```
**Важливо:** EuRoC MH_01 — indoor MAV з forward camera. cuVSLAM Mono-Depth оптимізований для outdoor nadir. Поганий ATE на EuRoC ≠ поганий ATE на реальному польоті. Льотні тести є остаточним арбітром.
---
## 5. aero-vloc benchmark — що робити з результатами
[aero-vloc](https://github.com/prime-slam/aero-vloc) — benchmark framework для aerial visual localization. Запускається на UAV↔satellite парах.
### Навіщо
До того як фіксувати дизайн Faiss index (tile size, descriptor dim, retrieval strategy) — треба знати реальні числа для нашого типу зображень: nadir, fixed-wing altitude, Ukrainian terrain.
### Що запустити
```bash
git clone https://github.com/prime-slam/aero-vloc
cd aero-vloc
# 1. Підготувати пари: UAV кадри з наших SRT/відео + відповідні MapTiler тайли
# 2. Запустити benchmark з кількома дескрипторами:
python benchmark.py --query ./our_nadir_frames/ \
--database ./satellite_tiles/ \
--descriptors netvlad dinov2_vlad eigenplaces \
--top-k 5
# 3. Записати R@1, R@5, медіанна помилка локалізації в метрах
```
### Що зробити з результатами
| Результат | Дія |
|---|---|
| DINOv2-VLAD R@1 ≥ 60% | ✅ Підтверджує AnyLoc як sprint 1 baseline |
| DINOv2-VLAD R@1 < 40% | ⚠️ Перевірити domain gap — можливо треба fine-tuning або EigenPlaces |
| EigenPlaces > DINOv2-VLAD на ≥10% R@1 | Прискорити перехід на EigenPlaces (з stage 2 → sprint 2) |
| Медіанна помилка > 50 м | Проблема з tile resolution або GSD mismatch → перевірити zoom level |
| Медіанна помилка ≤ 20 м | ✅ Відповідає `solution.md` цілі `<20 м absolute anchor` |
**Результати зберегти в:** `_docs/00_research/oss_stack_options/aero_vloc_results.md`
**Використати для:** фіналізації Faiss index design (tile size, overlap, descriptor dim) перед реалізацією AnyLocGPR.
---
## 6. SITL Integration Test Plan
SITL (Software-In-The-Loop) = ArduPilot запущений як симулятор, приймає GPS_INPUT від нашої системи через MAVLink без реального hardware.
### Налаштування SITL
```bash
# Запустити ArduPilot SITL (потрібен окремий binary або Docker)
sim_vehicle.py -v ArduPlane --console --map
# Параметри ArduPilot для GPS_INPUT:
# GPS1_TYPE = 14 (MAVLink)
# GPS_RATE_MS = 200 (5 Hz мінімум)
# EK3_SRC1_POSXY = 1, EK3_SRC1_VELXY = 1
```
### Декомпозиція тестів
Тест 1 — **Field encoding** (unit, без SITL):
```python
# Верифікувати кодування полів за MAVProxy reference:
# MAVProxy/modules/mavproxy_GPSInput.py
def test_gps_input_field_encoding():
msg = build_gps_input(lat=48.123, lon=37.456, alt_m=600.0,
vn=10.0, ve=5.0, vd=0.0,
h_acc=15.0, v_acc=8.0, fix_type=3)
assert msg.lat == int(48.123 * 1e7) # lat ×1e7
assert msg.lon == int(37.456 * 1e7) # lon ×1e7
assert msg.alt == int(600.0 * 1000) # alt мм
assert msg.vn == int(10.0 * 100) # vel см/с
assert msg.satellites_visible == 10 # synthetic, prevent failsafe
assert msg.fix_type == 3
```
Тест 2 — **Rate delivery** (з реальним pymavlink, mock SITL endpoint):
```python
# Верифікувати що GPS_INPUT виходить на 510 Hz без jitter > ±20 мс
def test_gps_input_rate_5hz():
timestamps = collect_gps_input_timestamps(duration_s=10)
intervals = np.diff(timestamps)
assert np.mean(intervals) == pytest.approx(0.2, abs=0.02) # 5 Hz ±10%
assert np.max(intervals) < 0.25 # жоден interval не > 250 мс
```
Тест 3 — **Confidence tier transitions** (вже є в `test_sitl_integration.py`, розширити):
```python
# HIGH → MEDIUM → LOW → FAILED transitions
# Верифікувати fix_type і horiz_accuracy змінюються коректно
# Вже: test_reloc_request_after_3_failures_with_sitl
# Додати: test_fix_type_degrades_without_satellite_match
```
Тест 4 — **ArduPilot EKF acceptance** (повний SITL):
```python
# Запустити справжній SITL, подати GPS_INPUT, перевірити що EKF приймає
# Метрика: GLOBAL_POSITION_INT від SITL відповідає нашому GPS_INPUT з похибкою < 5 м
# Це верифікує що ArduPilot не відкидає наші повідомлення (наприклад через fix_type=0)
```
### Reference implementation
Взяти за основу `MAVProxy/modules/mavproxy_GPSInput.py`:
- Точне кодування всіх 15+ полів GPS_INPUT (#232)
- Обробка GPS time (Unix → GPS epoch, leap seconds)
- hdop/vdop synthetic values
**Зауваження щодо FC процесора:** H743 приймає GPS_INPUT over UART. F405 — мовчки ігнорує. Перевірити до льотних тестів.
---
## 7. Критичні перевірки перед кодом
### 7.1 numpy pin — негайно
```toml
# pyproject.toml
dependencies = [
"numpy>=1.24,<2.0", # NumPy 2.0 ламає GTSAM bindings (issue #2264)
...
]
```
### 7.2 Flight Controller processor
- **H743** → GPS_INPUT over serial ✅
- **F405** → мовчки ігнорує GPS_INPUT ❌
- Перевірити: Mission Planner → Help → About, або питати постачальника
### 7.3 IMU rate по MAVLink
- Default ArduPilot: **50 Hz**
- Для ORB-SLAM3 Mono-Inertial (майбутнє): **≥100 Hz**
- Змінити: `SR2_RAW_SENS = 10` (×10 → 100 Hz) або `= 20` (200 Hz)
- Sprint 1 (cuVSLAM Mono-Depth): не критично, але виміряти поточне значення
---
## 8. Аналоги системи
| Система | VO | Scale source | Fusion | Satellite anchor |
|---|---|---|---|---|
| **Наша (sprint 1)** | cuVSLAM Mono-Depth | Барометр | ESKF | DINOv2-VLAD+Faiss |
| **Наша (stage 2)** | cuVSLAM Stereo-Inertial | IMU | ESKF+GTSAM | SP+LG+Faiss |
| **VINS-Fusion** | Mono-Inertial | IMU | Factor graph | Немає |
| **OpenVINS** | MSCKF Mono | IMU | EKF | Немає |
| **ArduPilot EKF3** | — | GPS | EKF | GPS (не satellite matching) |
Унікальність нашої архітектури: **VIO + global satellite anchor** без GPS. Не просто odometry — прив'язка до карти в реальному часі.
---
## 9. Відкриті питання
1. Якість cuVSLAM Mono-Depth на feature-poor nadir terrain (рівне поле, ліс) — тільки льотні тести відповідять
2. aero-vloc покаже реальний R@1 для DINOv2-VLAD на нашому типі зображень
3. FC processor: H743 чи F405? — блокер для SITL тестів
4. IMU rate: скільки зараз по MAVLink? — вплине на roadmap VO upgrade
5. MapTiler MBTiles для України: ліцензія дозволяє offline onboard deployment?