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>
26 KiB
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: 15–33× повільніше за 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.X→from 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, 300–400 м) + EuRoC (indoor MAV, IMU+GT, індустрійний benchmark) + MARS-LVIG (rotary, featureless сіквенси як stress-test). Деталі — у ADR 0001.]
- Побудувати
DatasetAdapterABC + 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 fixtureVPAIRAdapter+ unit-тести з fabricated fixture (реальний формат: poses_query.txt, ECEF xyz, Euler rad)MARSLVIGAdapter+ unit-тести (очікує pre-extracted layout з ROS bag)coord.pyhelpers: 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.chURL (TCP timeout) — новий ETH Research Collection DOI10.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-vpair— merged у stage1 через PR #1 (2026-04-16, rebase)feat/e2e-euroc— поточна робота (2026-04-17): harnessmax_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-харнес з
DatasetAdapterpattern. 12 комітів у stage1 (a2620ae→0062323), пушнуто. 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_mh01→euroc_machine_hallз реальним SHA2565ed7d07…; 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 #4
-
PR #8
feat/e2e-eskf-init— ESKF ініціалізується з першої GT-пози адаптера. Результат:eskf_initialized: 0→100/100,eskf_has_position: 100/100. ATE RMSE10.7 км. Satellite measurements відхиляються Mahalanobis gate (Δ²10⁶ >> 16.3). Root cause: ORBscale_ambiguous=True→ unit-scale VO translations → ESKF position diverges → satellite outlier gate срацює коректно.- Наступний крок: VO metric scale. ORB переводить
(R, t̂)деt̂— одиничний вектор, реальна відстань невідома. Варіанти: (а) передавати IMU timestamps у ESKF щоб він сам рахував scale через velocity; (б) перейти на cuVSLAM (metric backend); (в) хак — фіксований scale для EuRoC indoor (~0.3 м між кадрами). Правильний шлях — (б) cuVSLAM, але для CI-діагностики можна (в).
- Наступний крок: VO metric scale. ORB переводить
-
PR #9
feat/e2e-vo-scale— реалізовано варіант (а):vo_scale_m=0.005(5 мм/кадр, виміряно з GT median)._ScaledVOwrapper нормалізує unit-vector ORB translation і множить на scale.HarnessResultтепер збираєeskf_positions_enu. Перший strict-assert тест на реальних даних:test_euroc_mh01_eskf_drift_within_ceilingPASS — 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_hinttests — 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)
- Якість cuVSLAM Mono-Depth на feature-poor nadir terrain (рівне поле, ліс).
- Частота та надійність GPR match в реальних умовах України.
- Чи достатньо барометра для scale recovery при різких змінах висоти.
- Коли (чи взагалі) потрібен перехід на Stereo-Inertial (hardware change).