feat(perception): add full camera-first shadow

This commit is contained in:
DCCONSTRUCTIONS
2026-07-30 23:15:06 +03:00
parent dee6d57133
commit dc6f9b66da
12 changed files with 1932 additions and 29 deletions
@@ -0,0 +1,116 @@
# LAB E53 · полный camera-first shadow
Дата: 2026-07-30
Статус: принят технический диагностический shadow; camera detector не
повышен до operational runtime
## Цель
Проверить на всех 4 489 кадрах RAVNOVES00 восстановленный архитектурный
принцип:
```text
camera detection and semantic class
→ canonical E29 camera↔LiDAR association
→ LiDAR metric support and range
```
LiDAR не создаёт семантический класс. Camera-объект без квалифицированной
LiDAR-поддержки остаётся camera-only объектом без выдуманной дальности.
## Неизменяемые входы
- E10 LiDAR pack:
`e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b`;
- local surface:
`k1-local-surface-23762244c8bdb97de26fb721ac957d7a00bc9a63571ac4cfa4be19c4effc7d55`;
- camera candidate:
`result-f4cebdea8a82698a5b8a65d2c3fbdb0428b88b9dc49fe45f8cb37d740ed83d02`;
- accepted E29:
`e29-camera-geometry-421a9d930638bef12cd5eb10979a477917fa4a389e655ed95f73ba4bd62e13dc`;
- rig geometry:
`ravnoves00-portable-k1/unbound-v1`.
Rig profile намеренно не содержит корпуса, трансформации LiDAR→body или
физических размеров: RAVNOVES00 записан переносным K1 и не доказывает
установку на транспортное средство.
## Метод
E53 потоково читает detector и E29 metadata, один раз материализует compressed
source arrays и для каждого source-aligned кадра:
1. оставляет camera-классы `person`, `bicycle`, `car`, `motorcycle`, `bus`,
`truck` с score не ниже 0.5;
2. отбрасывает bbox больше 25% изображения;
3. применяет без изменения профиль
`camera-first-local-surface-validation/v1`;
4. сохраняет camera semantic ownership;
5. публикует LiDAR range только при связанном occupied support;
6. измеряет adjacent-frame family+IoU continuity и source-bound overlap с E29;
7. оставляет collision unavailable.
Сравнение с E29 и adjacent IoU не являются ground truth или persistent
tracking truth.
## Отвергнутый первый проход
Первый immutable результат:
```text
e53-camera-first-shadow-94eb57cce8dbfb0ea679486ed55aec32c2f38c93ae67e1c606787587cd3a0da5
```
Он обработал все 4 489 кадров, но был отвергнут: p95 составил 210.7093 ms
против заранее заданных 50 ms. Причина — обращение к compressed NPZ member
`cloud_points_map` внутри frame loop, из-за чего массив 9.2 млн точек
распаковывался повторно.
Исправление не меняло входы, пороги или ассоциацию: source arrays
материализуются один раз до цикла.
## Принятый результат
```text
e53-camera-first-shadow-e6f03cf8bfb15db86100239b060e13f914532618b7b99e811866e4e6a555186c
```
| Измерение | Результат |
| --- | ---: |
| Кадры | 4 489 / 4 489 |
| Кадры с LiDAR | 3 928 |
| Кадры с camera candidates | 4 471 |
| Принятые camera candidates | 48 944 |
| Pathological large boxes отклонено | 946 |
| Camera + LiDAR agree | 7 686 |
| Camera-only | 34 940 |
| Source unavailable | 6 318 |
| Объекты с LiDAR range | 10 183 |
| Доля объектов с range | 20.8054% |
| E29 current family+IoU overlap | 18 199 / 19 625 = 92.7338% |
| Adjacent family+IoU continuity | 31 476 / 48 933 = 64.3247% |
| Frame latency p50 / p95 / max | 0.9881 / 1.7993 / 9.4521 ms |
| Полный elapsed | 4 962.34 ms |
| Peak-RSS growth | 36.8594 MiB |
Распределение camera candidates: 44 498 `car`, 2 341 `person`, 1 876
`truck`, 142 `bicycle`, 66 `motorcycle`, 21 `bus`.
## Решение
Полный source-bound camera-first shadow технически принят:
- accounting и binding закрыты;
- E29 association переиспользована;
- latency и memory gate пройдены;
- camera semantic ownership сохранён;
- LiDAR-native class, false free space и collision не публикуются.
Mask R-CNN не принят как operational detector. Большое число camera candidates,
64.32% adjacent continuity и 92.73% overlap с E29 требуют независимой
source-bound quality review. E29 также не является ground truth.
Physical vehicle-body и LiDAR mount/extrinsic остаются отдельным измерительным
gate. До их квалификации `recent-collision`, navigation, safety и commands
остаются недоступны.
@@ -0,0 +1,56 @@
{
"schema_version": "missioncore.e53-camera-first-shadow-profile/v1",
"profile_id": "e53-ravnoves00-camera-first-full-shadow/v1",
"expected": {
"source_pack_id": "e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b",
"local_surface_model_id": "k1-local-surface-23762244c8bdb97de26fb721ac957d7a00bc9a63571ac4cfa4be19c4effc7d55",
"detector_result_id": "result-f4cebdea8a82698a5b8a65d2c3fbdb0428b88b9dc49fe45f8cb37d740ed83d02",
"baseline_e29_result_id": "e29-camera-geometry-421a9d930638bef12cd5eb10979a477917fa4a389e655ed95f73ba4bd62e13dc"
},
"camera_candidate": {
"target_labels": [
"person",
"bicycle",
"car",
"motorcycle",
"bus",
"truck"
],
"label_groups": {
"person": [
"person"
],
"two-wheel": [
"bicycle",
"motorcycle"
],
"vehicle": [
"car",
"bus",
"truck"
]
},
"minimum_score": 0.5,
"maximum_bbox_image_fraction": 0.25
},
"temporal": {
"minimum_adjacent_family_iou": 0.25,
"minimum_baseline_family_iou": 0.25,
"interpretation": "adjacent-image-space-continuity-diagnostic-not-track-truth"
},
"acceptance": {
"maximum_frame_latency_p95_ms": 50.0,
"maximum_rss_growth_mib": 512.0,
"require_complete_frame_accounting": true,
"require_canonical_e29_association": true,
"require_camera_semantic_ownership": true,
"require_no_lidar_native_semantics": true,
"require_no_false_free_space": true,
"require_no_collision_authority": true,
"require_upstream_immutability": true
},
"authority": {
"commands_enabled": false,
"navigation_or_safety_accepted": false
}
}
@@ -0,0 +1,20 @@
{
"schema_version": "missioncore.rig-geometry/v1",
"profile_id": "ravnoves00-portable-k1/unbound-v1",
"rig_kind": "portable",
"qualification": {
"state": "unbound",
"reason": "portable-k1-source-has-no-vehicle-body-or-measured-lidar-to-body-mount",
"evidence_sha256": []
},
"coordinate_frames": {
"lidar_frame": "k1-lidar",
"body_frame": null,
"body_from_lidar": null
},
"vehicle_body": null,
"authority": {
"commands_enabled": false,
"navigation_or_safety_accepted": false
}
}
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Build the immutable full-source E53 camera-first shadow."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
from k1link.compute.e53_camera_first_shadow import (
build_e53_camera_first_shadow,
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source-pack", type=Path, required=True)
parser.add_argument("--local-surface", type=Path, required=True)
parser.add_argument("--detector-result", type=Path, required=True)
parser.add_argument("--baseline-e29", type=Path, required=True)
parser.add_argument("--rig-geometry", type=Path, required=True)
parser.add_argument("--profile", type=Path, required=True)
parser.add_argument("--output-root", type=Path, required=True)
args = parser.parse_args()
result = build_e53_camera_first_shadow(
source_pack_root=args.source_pack,
local_surface_root=args.local_surface,
detector_result_root=args.detector_result,
baseline_e29_root=args.baseline_e29,
rig_geometry_path=args.rig_geometry,
profile_path=args.profile,
output_root=args.output_root,
)
print(
json.dumps(
{
"result_id": result.result_id,
"result_root": str(result.result_root),
"accepted": result.report["acceptance"]["accepted"],
"metrics": result.report["metrics"],
"decision": result.report["decision"],
},
ensure_ascii=False,
sort_keys=True,
)
)
return 0
if __name__ == "__main__":
raise SystemExit(main())