feat(perception): add lossless lidar replay v2

This commit is contained in:
DCCONSTRUCTIONS
2026-07-25 01:35:03 +03:00
parent 3f549f91f1
commit e4fdd9fa20
18 changed files with 2538 additions and 11 deletions
+12
View File
@@ -33,6 +33,7 @@ from k1link.sessions import (
SessionStore,
)
from k1link.web.device_plugin_composition import load_installed_device_plugins
from k1link.web.lidar_api import build_lidar_router
from k1link.web.plugin_catalog import DevicePluginCatalog, PluginCatalogError
from k1link.web.plugin_runtime import (
STATE_READ_ACTION_ID,
@@ -398,6 +399,17 @@ app.include_router(
)
)
app.include_router(build_polygon_router())
app.include_router(
build_lidar_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "lidar-replay-v2"
/ "packs"
)
)
)
frontend_dist = REPOSITORY_ROOT / "apps" / "control-station" / "dist"
+110
View File
@@ -0,0 +1,110 @@
from __future__ import annotations
import os
import re
from collections.abc import Callable
from pathlib import Path
from typing import Any, Final
from fastapi import APIRouter, HTTPException, Query
from k1link.compute import (
LidarReplayError,
LidarReplayPackV2,
lidar_pack_catalog_item,
lidar_pack_detail,
)
LIDAR_CATALOG_SCHEMA: Final = "missioncore.lidar-replay-pack-catalog/v1"
_PACK_ID = re.compile(r"^lidar-replay-pack-[a-f0-9]{64}$")
RootProvider = Callable[[], Path | None]
def configured_lidar_replay_root() -> Path | None:
value = os.environ.get("MISSIONCORE_LIDAR_REPLAY_ROOT", "").strip()
return Path(value).expanduser().absolute() if value else None
def build_lidar_router(
*,
root_provider: RootProvider = configured_lidar_replay_root,
) -> APIRouter:
router = APIRouter(prefix="/api/v1/lidar", tags=["lidar"])
@router.get("/replay-packs")
def list_lidar_replay_packs(
limit: int = Query(default=20, ge=1, le=100),
) -> dict[str, Any]:
root = root_provider()
if root is None or not root.is_dir():
return {
"schema_version": LIDAR_CATALOG_SCHEMA,
"configured": root is not None,
"items": [],
"valid_total": 0,
"invalid_total": 0,
"duplicate_total": 0,
"access": "read-only",
}
items: list[dict[str, object]] = []
invalid_total = 0
duplicate_total = 0
seen_logical_content: set[str] = set()
candidates = sorted(
(
candidate
for candidate in root.iterdir()
if candidate.is_dir() and _PACK_ID.fullmatch(candidate.name) is not None
),
key=lambda candidate: candidate.stat().st_mtime_ns,
reverse=True,
)
for candidate in candidates:
try:
pack = LidarReplayPackV2(candidate)
try:
item = lidar_pack_catalog_item(pack)
logical_content = str(item["logical_content_sha256"])
if logical_content in seen_logical_content:
duplicate_total += 1
continue
seen_logical_content.add(logical_content)
item["created_at_utc"] = pack.manifest.get("created_at_utc")
items.append(item)
finally:
pack.close()
except (LidarReplayError, OSError):
invalid_total += 1
return {
"schema_version": LIDAR_CATALOG_SCHEMA,
"configured": True,
"items": items[:limit],
"valid_total": len(items),
"invalid_total": invalid_total,
"duplicate_total": duplicate_total,
"access": "read-only",
}
@router.get("/replay-packs/{pack_id}")
def get_lidar_replay_pack(pack_id: str) -> dict[str, object]:
if _PACK_ID.fullmatch(pack_id) is None:
raise HTTPException(status_code=404, detail="LiDAR replay pack не найден")
root = root_provider()
if root is None or not root.is_dir():
raise HTTPException(status_code=503, detail="LiDAR replay storage не настроен")
candidate = root / pack_id
if not candidate.is_dir():
raise HTTPException(status_code=404, detail="LiDAR replay pack не найден")
try:
pack = LidarReplayPackV2(candidate)
try:
return lidar_pack_detail(pack)
finally:
pack.close()
except (LidarReplayError, OSError) as exc:
raise HTTPException(
status_code=409,
detail="LiDAR replay pack не прошёл проверку целостности",
) from exc
return router