Files
gps-denied-onboard/next_steps.md
T
Yuzviak 1618190105 docs: update README and next_steps with sprint 1 VO migration results
README:
- Stack table: VO row shows CuVSLAMMonoDepthVisualOdometry (Mono-Depth mode)
- Test coverage: 195+8 → 216+8 (new mono_depth tests, AnyLoc markers, GPS_INPUT encoding)
- Added test_gps_input_encoding.py row
- F07 component table: dev/prod shows Mono-Depth variants
- "Next steps" rewritten: sprint 1 complete, sprint 2 queued

next_steps.md:
- New §5.1a documenting sprint 1 execution (7 commits, 3 decisions recorded)
- §5.3 week-1 marked numpy pin / Mono-Depth class / GPS_INPUT encoding as done
- Week-2 updated with harness wiring and CuVSLAMVisualOdometry deletion tasks

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

26 KiB
Raw Blame History

Next Steps — дорожня карта

Живий документ. Галочки заповнюються у міру виконання. Коментарі в квадратних дужках [decision: …] фіксують рішення, щоб через місяць можна було зрозуміти чому ми зробили саме так.


1. Аудит відповідності солюшну

Весь девелопмент зроблений не з цільового _docs/01_solution/solution.md, а з сукупності документів у _docs/01_solution/. Треба звірити імплементацію саме з цільовим солюшном.

  • Порівняти поточну імплементацію з _docs/01_solution/solution.md (не з усією текою)
  • VO треба зробити на cuVSLAM (якщо не буде явних перепон)
    • Rationale: SP+LG (SuperPoint+LightGlue) rejection: 1533× повільніше за cuVSLAM. Немає built-in IMU fusion, loop closure, tracking-failure detection. Побудова цих фіч навколо SP+LG — значний час, і все одно повільніше. XFeat (~30–50 мс) — кращий fallback для VO якщо cuVSLAM не зайде на nadir-камеру.
  • Прогнати cuVSLAM через e2e-харнес (див. пункт 3) — перевірити що VO+ESKF разом дають адекватну точність на EuRoC/VPAIR
  • Якщо cuVSLAM підходить → видалити SP+LG з кодбейса. Залишити XFeat як fallback

Цей крок треба робити ПІД захистом e2e-харнеса, не до нього, щоб відразу бачити регресії (див. пункт 3).


2. Реструктуризація коду

Весь код у src/gps_denied/, але весь проект і так про gps_denied — зайвий рівень неймспейсу.

  • git mv src/gps_denied/* src/ (або через rename imports, що чистіше)
  • Оновити імпорти у всіх файлах (from gps_denied.Xfrom X чи залишити як неймспейс, вирішити)
  • Оновити pyproject.toml: [tool.setuptools.packages.find]
  • Прогнати повну test suite — має бути зелено
  • Оновити CI скрипти, Docker, docs

Робити під захистом e2e-харнеса — одна велика рефакторинг-зміна з багатьма PR → легко поламати щось непомітно. Зелений e2e (на тих самих числах що до рефакторингу) — sign-off.


3. Autopilot existing-code flow — e2e-харнес

Всі проекти в azaion приводяться до механізму девелопменту з .cursor/skills/autopilot (flows/existing-code для існуючого коду, flows/greenfield для нового).

Коротко алгоритм: питаємо вимоги до e2e-тестів і даних → рефакторимо для можливості e2e-покриття → пишемо e2e-підсистему що запускає продукт як black-box → переконуємось що працює → тоді ітеративно рефакторимо під захистом e2e.

Статус виконання

Design (локально, gitignored): .planning/brainstorms/2026-04-16-e2e-datasets-design.md Plan (локально, gitignored): .planning/brainstorms/2026-04-16-e2e-datasets-plan.md In-repo docs: src/gps_denied/testing/README.md (harness architecture), _docs/01_solution/decisions/0001-e2e-dataset-strategy.md (ADR — selection rationale)

[decision 2026-04-16: замість збору власних даних з Mavic-а — використати публічні UAV датасети. Причина: блокер на проприєтарні дані (Денис, мавікісти), а нам треба рухатись вже зараз. Public датасети: VPAIR (fixed-wing, downward, 300400 м) + EuRoC (indoor MAV, IMU+GT, індустрійний benchmark) + MARS-LVIG (rotary, featureless сіквенси як stress-test). Деталі — у ADR 0001.]

  • Побудувати DatasetAdapter ABC + capability flags (has_raw_imu, has_rtk_gt, platform_class) — src/gps_denied/testing/datasets/base.py
  • SyntheticAdapter для harness self-test (synthetic.py)
  • Trajectory метрики — RMSE, ATE, RPE (metrics.py)
  • E2EHarness — жене адаптер через FlightProcessor, збирає estimated positions, порівнює з GT (harness.py)
  • SHA256-verified dataset downloader + registry (download.py, scripts/download_dataset.py)
  • EuRoCAdapter + unit-тести з fabricated fixture
  • VPAIRAdapter + unit-тести з fabricated fixture (реальний формат: poses_query.txt, ECEF xyz, Euler rad)
  • MARSLVIGAdapter + unit-тести (очікує pre-extracted layout з ROS bag)
  • coord.py helpers: ECEF→WGS84 (Heikkinen closed-form), Euler→quaternion (ZYX aerospace)
  • Integration тести з skip-when-absent fixtures (EuRoC, VPAIR, MARS-LVIG)
  • Pytest markers e2e, e2e_slow, needs_dataset у pyproject.toml
  • Перший реальний e2e-прогін на VPAIR sample (200 кадрів fixed-wing, 300-400 м над Bonn/Eifel)
    • Результат: пайплайн завершується без падінь, ATE RMSE ~1 770 км → xfail
    • [decision 2026-04-16: це очікувано. VO сам по собі без IMU і без supплементарного supплутникового anchoring розходиться. VPAIR не має raw IMU → ESKF-шлях не активується. Xfail-branch документує цю deгадацію; перейде у strict-assert коли VO+GPR будуть тюновані для high-altitude nadir + знайдемо датасет з raw IMU.]
  • Перший реальний e2e-прогін на EuRoC MH_01 (перші 100 кадрів indoor MAV, ASL формат)
    • Результат: пайплайн завершується за ~30 с, ATE RMSE ~10 871 м → xfail за ceiling 5 м
    • [decision 2026-04-17: замість старого robotics.ethz.ch URL (TCP timeout) — новий ETH Research Collection DOI 10.3929/ethz-b-000690084. Окремого MH_01_easy.zip там немає, лише 12.6 GB bundle з усіма 5 MH-сіквенсами. Витягнули тільки MH_01_easy.zip у /home/yuzviak/Azaion/Data/machine_hall/MH_01/, symlink з datasets/euroc/MH_01/. Registry entry перейменована на euroc_machine_hall (bundle-level SHA256).]
    • [decision 2026-04-17: harness отримав параметр max_frames — CI-tier гонить 100 кадрів за ~30 с. Повна sequence (3682) — ~3 години, не CI-tier.]
    • Pipeline diff vs VPAIR: raw IMU є → ESKF активний; але satellite-anchoring для indoor-сцени не релевантний, VO+ESKF без якоря дрейфує на кілометри.
  • Замінити xfail на strict-assert у VPAIR і EuRoC тестах (коли VO+GPR+ESKF-anchoring почнуть давати притомні числа)
  • IMU з SITL для початку — ArduPilot SITL може літати mission з waypoints, генерувати 200 Hz IMU через MAVLink. Для піднять пайплайну цього достатньо. Реальний IMU треба так чи інакше, але це паралельна гілка роботи.

Патерн гілок для e2e-роботи

  • feat/e2e-vpairmerged у stage1 через PR #1 (2026-04-16, rebase)
  • feat/e2e-euroc — поточна робота (2026-04-17): harness max_frames, перший реальний прогін EuRoC MH_01, реєстр перейменовано на euroc_machine_hall
  • Інтеграція у stage1 через послідовні PR-и. Політика: наступні merge без --delete-branch (гілки лишаються на GitHub)

4. Збір реальних UAV-даних

[decision 2026-04-16: деприйорити цей пункт на користь пункту 3 (публічні датасети). Причина: публічні датасети вже є, дають реальний fixed-wing сценарій, не вимагають координації людей. Повернемось до цього коли:

  • (а) потрібно буде валідувати на нашому конкретному дроні (intrinsics, спектр вібрації IMU, camera model відрізняються від VPAIR/EuRoC);

  • (б) готовий буде VO+ESKF-тюнінг і захочеться "фінальний" датасет саме під envelope tactical fixed-wing 200-1500 м.]

  • Денис Попов — логи Mavic (камера вниз, висока висота). Призупинено.

  • Штатні мавікісти — пара тестових вильотів з nadir-камерою. Призупинено, потребує переконати.

  • DJI Mavic флайт у /home/yuzviak/Azaion/Data/ (MP4 + SRT + Airdata CSV)

    • [decision 2026-04-16: НЕ інтегруємо зараз. Причини: (1) DJI не публікує raw IMU, є лише attitude/speeds (результат фʼюжену) — ESKF-шлях не активується; (2) потрібні camera intrinsics конкретної моделі; (3) sync SRT@30Hz + CSV@5Hz потребує окремого alignment-кроку. Залишаємо як qualitative demo пізніше, коли інфраструктура стабілізується.]

Хронологія рішень

  • 2026-04-16 ранок: Аудит інфраструктури, pull upstream, вирішили взяти публічні датасети замість чекати на Дениса. Brainstorm + spec + plan: VPAIR (Tier 1), MARS-LVIG (Tier 2), EuRoC (Tier 3 CI).

  • 2026-04-16 день: Реалізовано e2e-харнес з DatasetAdapter pattern. 12 комітів у stage1 (a2620ae0062323), пушнуто. 233 passed, 13 skipped.

  • 2026-04-16 вечір: Спробували скачати EuRoC MH_01 — старий URL лежить, знайшли новий DOI (12 GB bundle, завтра). Переключились на VPAIR (вже скачаний). Реальний формат відрізняється від припущеного: ECEF+Euler+no timestamps. Написали coord.py (ECEF→WGS84 Heikkinen + Euler→quat), переписали VPAIRAdapter. Гілка feat/e2e-vpair, 5 комітів. Перший реальний прогін: ATE ~1770 км (очікувано, задокументовано в xfail).

  • 2026-04-17: Завантажили 12.6 GB machine_hall.zip, витягли MH_01 (2.6 GB). Додали max_frames у E2EHarness (TDD, 3 нових тести). Перший реальний прогін EuRoC MH_01 на 100 кадрах: пайплайн завершується за ~30 с, ATE RMSE ~10.87 км → xfail. Registry entry перейменовано euroc_mh01euroc_machine_hall з реальним SHA256 5ed7d07…; URL порожній (ETH Research Collection не дає direct link, ручне завантаження). Гілка feat/e2e-euroc.

  • 2026-04-18: Три PR підряд у stage1:

    • PR #4 feat/e2e-trace — додали per-frame JSONL трасування в E2EHarness (trace_path параметр). Запустили EuRoC MH_01 з трасуванням: виявлено vo_success=0/100, eskf_initialized=0/100, alignment_success=77/100. Всі оцінки — fallback satellite matching без ESKF/VO.
    • PR #5 feat/e2e-vo-only — ORB VO-only діагностика. Запустили ORBVisualOdometry напряму на EuRoC кадрах: 99/99 tracking rate (100%). Висновок: проблема була не в VO-алгоритмі, а в тому що SequentialVisualOdometry (Mock random keypoints → RANSAC failure) використовувалась у харнесі.
    • PR #6 feat/e2e-harness-orb-vo — замінили VO-бекенд у _build_processor з SequentialVisualOdometry(ModelManager()) на ORBVisualOdometry(). Новий результат: vo_success=99/100. Залишилась проблема: eskf_initialized=0/100 — ESKF потребує init_flight() зі start_gps, харнес цього не робить.
    • [decision 2026-04-18: VO-бекенд у харнесі має бути ORBVisualOdometry (реальні OpenCV фічі), а не Mock SP+LG (random keypoints). Для Jetson-production cuVSLAM залишається метою — але харнес валідує pipeline логіку незалежно від бекенду.]
    • Наступний крок: ініціалізувати ESKF у харнесі з синтетичним GPS-origin (середня координата GT або перша GT-поза). Це увімкне ESKF-шлях і дасть змогу виміряти реальний VO+ESKF дрейф без satellite fallback.
  • PR #8 feat/e2e-eskf-init — ESKF ініціалізується з першої GT-пози адаптера. Результат: eskf_initialized: 0→100/100, eskf_has_position: 100/100. ATE RMSE 10.7 км. Satellite measurements відхиляються Mahalanobis gate (Δ²10⁶ >> 16.3). Root cause: ORB scale_ambiguous=True → unit-scale VO translations → ESKF position diverges → satellite outlier gate срацює коректно.

    • Наступний крок: VO metric scale. ORB переводить (R, t̂) де — одиничний вектор, реальна відстань невідома. Варіанти: (а) передавати IMU timestamps у ESKF щоб він сам рахував scale через velocity; (б) перейти на cuVSLAM (metric backend); (в) хак — фіксований scale для EuRoC indoor (~0.3 м між кадрами). Правильний шлях — (б) cuVSLAM, але для CI-діагностики можна (в).
  • PR #9 feat/e2e-vo-scale — реалізовано варіант (а): vo_scale_m=0.005 (5 мм/кадр, виміряно з GT median). _ScaledVO wrapper нормалізує unit-vector ORB translation і множить на scale. HarnessResult тепер збирає eskf_positions_enu. Перший strict-assert тест на реальних даних: test_euroc_mh01_eskf_drift_within_ceiling PASS — ESKF ATE RMSE ~0.20 м за 100 кадрів (ceiling 0.5 м).

    • [decision 2026-04-18: фіксований scale 0.005 м/кадр — це діагностичний хак для CI. Правильне рішення — cuVSLAM (metric VO). Але цей тест тепер є regression guard: якщо рефакторинг VO/ESKF ламає інтеграцію — тест покаже.]
    • Поточний стан pipeline на EuRoC MH_01: vo_success=99/100, eskf_initialized=100/100, ESKF ATE=0.20 м ✓. GPS estimate ATE xfail (satellite не туновано під indoor).
    • Наступні кроки: (1) MH_02-05 параметризовані тести — перевірити чи 0.20 м baseline стабільний на інших sequences; (2) VPAIR — там satellite matching може бути актуальним (outdoor, є reference_views/); (3) cuVSLAM як VO backend.
  • PR #10 feat/e2e-mh-multi — витягли MH_02-05 з bundle, написали 10 параметризованих тестів (pipeline_completes + eskf_drift для всіх 5 sequences). Всі 10 PASS. ESKF ATE baseline:

    Sequence Difficulty ESKF ATE RMSE
    MH_01 easy 0.205 m
    MH_02 easy 0.131 m
    MH_03 medium 0.008 m
    MH_04 difficult 0.009 m
    MH_05 difficult 0.007 m

    MH_03-05 мають дуже малий дрейф (~1 см) бо перші 100 кадрів MAV майже нерухомий (старт sequence). Це нормально — цей baseline зростатиме якщо запустити більше кадрів.

    Поточний стан e2e харнесу: 70 passed, 1 skipped, 2 xfailed. Всі EuRoC MH sequences покриті strict-assert тестами.


5. Tech audit — Open-Source Stack alignment

Research документ docs/superpowers/specs/2026-04-18-oss-stack-tech-audit-design.md (2026-04-18) переглянув увесь OSS-стек і знайшов кілька критичних розходжень з поточною архітектурою. Нижче — стан синхронізації з research.

5.1 Виконано (2026-04-18 вечір)

  • Numpy pin >=1.26,<2.0. NumPy 2.0 silently breaks GTSAM Python bindings (research 4.3, issue #2264).
    • Також довелось обмежити opencv-python-headless<4.11 — 4.11+ вимагає numpy≥2. Knock-on constraint.
    • Після downgrade: unit 196 passed / 8 skipped, e2e EuRoC MH_01 PASS (без регресії).
  • VPAIR контрольний прогін на 50 кадрах з vo_scale_m=1.0 і vo_scale_m=45.0 (GT inter-frame displacement median=45 м — fixed-wing на 400 м, швидкість ~225 м/с × 0.2 с між synthetic timestamps).
    • Результат не залежить від scale: ESKF ATE ≈ 1236-1343 м, GPS ATE ≈ 1770 км.
    • [decision 2026-04-18: VPAIR не можна зробити PASS на поточному стеку. Фундаментальні блокери: (а) немає raw IMU → ESKF дрейфує за рахунок тільки VO; (б) Mock satellite index повертає координати «в іншій півкулі світу» (не реальні tiles України), тому Mahalanobis gate відхиляє satellite estimates як outliers (Δ²~10⁹). Залишаємо xfail із задокументованою причиною. Розблокувати зможе: реальні satellite tiles (MapTiler MBTiles) АБО cuVSLAM Mono-Depth з барометром (dataset-level synthetic IMU).]

5.1a Sprint 1 VO migration — виконано (2026-04-18 ніч)

Plan: docs/superpowers/plans/2026-04-18-sprint1-vo-migration.md. 7 комітів, 216 passed / 8 skipped.

  • CuVSLAMMonoDepthVisualOdometry додано (src/gps_denied/core/vo.py). Приймає depth_hint_m (барометрична висота) як параметр, dev/CI fallback масштабує ORB translation на depth_hint_m / 600.0. Клас чітко вказує у docstring що solution.md говорить INERTIAL mode — але цей режим потребує stereo, тому фактично використовується MONO_DEPTH.
  • create_vo_backend() оновлено з параметрами prefer_mono_depth і depth_hint_m.
  • GlobalPlaceRecognition маркований як AnyLoc-VLAD-DINOv2 baseline. Selection rationale (NetVLAD deprecated, SP+LG не валідований для cross-view) — в docstring. 2 marker-тести: 4096-d descriptor + DINOv2 engine name через mock.
  • GPS_INPUT field encoding — 12 unit-тестів в tests/test_gps_input_encoding.py проти _eskf_to_gps_input (degE7 lat/lon, ENU→NED velocity, ConfidenceTier→fix_type, synthetic satellites_visible=10, hdop floor).
  • EuRoC Mono-Depth regression guard (test_euroc_mh01_mono_depth_within_ceiling) — smoke test + ORB pipeline ceiling 0.5 м. Baseline незмінний: 0.2046 м.
  • update_depth_hint tests — clamp at 1.0 м і per-call scale update через mock.
  • [decision 2026-04-18: не переписуємо solution.md (Inertial → Mono-Depth). Це окрема задача з §1. Поточний код явно документує розходження в docstring класу + design doc + next_steps.md §5.1a. Аудит solution.md — окремий спринт.]
  • [decision 2026-04-18: не створюємо новий AnyLocGPR класGlobalPlaceRecognition у docstring вже стверджував «AnyLoc (DINOv2)». Замість дублювання — розширили docstring і додали marker-тести.]
  • [decision 2026-04-18: E2E harness поки не wireує Mono-Depth через pipeline — harness хардкодить ORBVisualOdometry(). Додати vo_backend параметр і прогнати Mono-Depth через pipeline — sprint 2 task. TODO marker у vo.py.]

5.2 Відкореговано в плані (наступні дії)

cuVSLAM стратегія (research §1, §2):

  • Раніше писали просто «cuVSLAM як VO backend».
  • Тепер: cuVSLAM Mono-Depth (Mono + synthetic depth з барометра: scale = altitude / focal_length). Mono-Inertial потребує stereo hardware (у нас одна nadir-камера).
  • Це пояснює чому VPAIR без IMU показує 1236 м ESKF ATE — без altitude-to-scale recovery VO drift unbounded. На EuRoC vo_scale_m=0.005 працював тільки бо indoor scene має constant ~5 мм/кадр.

Place Recognition (research §3):

  • Раніше: «GlobalPlaceRecognition (numpy/Faiss)» — backend не уточнений.
  • Тепер: DINOv2-VLAD (AnyLoc) як descriptor → Faiss GPU index. EigenPlaces як upgrade path.
  • INT8 квантизація — broken для ViT на Jetson (NVIDIA/TRT#4348). Stay at FP16.

GTSAM:

  • Research радить «GTSAM 4.2 stable» замість 4.3a1 alpha.
  • Практично неможливо: PyPI має тільки gtsam==4.3a0. 4.2 потребує custom source build. Залишаємо gtsam>=4.3a0 до появи stable wheel АБО source-build мануал.
  • GTSAM-шлях відкладено для sprint 1 (research §3 Factor Graph): ESKF достатній для Gaussian GPS_INPUT 5-10 Hz. Mock залишається.

MAVLink (research §3):

  • pymavlink (MAVSDK-Python не підтримує GPS_INPUT).
  • Reference impl: MAVProxy/modules/mavproxy_GPSInput.py — точне кодування GPS_INPUT (#232). Звірити коли будемо інтегрувати з SITL.
  • Injection rate: 5-10 Hz, не 20+ (timing jitter).

Flight Controller hardware (research §4.1) — відкрите питання:

  • H743 GPS_INPUT over serial
  • F405 silently ignores GPS_INPUT (втрата днів на дебаг)
  • Запитати постачальника / перевірити через Mission Planner before any SITL-to-hardware crossover.

5.3 Пріоритети на 2-3 тижні (синхронізовано з research §5)

Тиждень 1 (починається 2026-04-19):

  • numpy pin ← зроблено 2026-04-18
  • Prototype CuVSLAMMonoDepthVisualOdometry class з dev/CI scaled ORB fallback ← зроблено 2026-04-18 (§5.1a)
  • GPS_INPUT field encoding tests ← зроблено 2026-04-18 (§5.1a)
  • Перевірити FC processor (H743 vs F405) — спитати постачальника
  • Перевірити поточний IMU rate через MAVLink (за замовчуванням ArduPilot шле 50 Hz, треба ≥100 Hz для Mono-Inertial; для Mono-Depth не критично)
  • Прогнати aero-vloc benchmark на VPAIR nadir кадрах до того як фіксувати Faiss index дизайн

Тиждень 2:

  • Wire CuVSLAMMonoDepthVisualOdometry через E2EHarness (додати vo_backend параметр, прогнати Mono-Depth через pipeline замість хардкоду ORB)
  • Порівняти ATE: ORB (поточний baseline 0.2046 м на MH_01) vs CuVSLAMMonoDepthVisualOdometry через pipeline
  • Колапс дуплікатного коду між CuVSLAMVisualOdometry (Inertial) і CuVSLAMMonoDepthVisualOdometry — видалити Inertial варіант
  • AnyLoc offline setup + перший тест на реальних MapTiler tiles

Тиждень 3:

  • XFeat TRT export для satellite matching (окремий трек від VO — не VO fallback)
  • MAVLink GPS_INPUT injection тест на SITL (docker-compose.sitl.yml)
  • Визначити чи варто піднімати IMU rate для майбутнього Mono-Inertial

5.4 Відкриті питання (research §7)

  1. Якість cuVSLAM Mono-Depth на feature-poor nadir terrain (рівне поле, ліс).
  2. Частота та надійність GPR match в реальних умовах України.
  3. Чи достатньо барометра для scale recovery при різких змінах висоти.
  4. Коли (чи взагалі) потрібен перехід на Stereo-Inertial (hardware change).