feat(perception): add lossless lidar replay v2
This commit is contained in:
@@ -50,6 +50,7 @@ from .lab_instances import (
|
||||
)
|
||||
from .lidar_contract import (
|
||||
K1_LAB_LIDAR_PACK_V1_PROFILE,
|
||||
K1_LIDAR_PACK_V2_PROFILE,
|
||||
K1_LIVE_LIDAR_PROFILE,
|
||||
LIDAR_EVIDENCE_PROFILE_SCHEMA,
|
||||
LIDAR_READINESS_SCHEMA,
|
||||
@@ -68,6 +69,19 @@ from .lidar_contract import (
|
||||
lidar_readiness_document,
|
||||
sensor_frame_xyzi,
|
||||
)
|
||||
from .lidar_replay import (
|
||||
LIDAR_EQUIVALENCE_REPORT_SCHEMA,
|
||||
LIDAR_QUALITY_REPORT_SCHEMA,
|
||||
LIDAR_REPLAY_PACK_SCHEMA,
|
||||
LidarReplayError,
|
||||
LidarReplayPackV2,
|
||||
LidarReplayPointFrame,
|
||||
LidarReplayPoseFrame,
|
||||
build_lidar_replay_pack_v2,
|
||||
lidar_pack_catalog_item,
|
||||
lidar_pack_detail,
|
||||
verify_lidar_replay_equivalence,
|
||||
)
|
||||
from .live_perception import (
|
||||
LIVE_INGRESS_SCHEMA,
|
||||
LIVE_INGRESS_WIRE_SCHEMA,
|
||||
@@ -139,7 +153,10 @@ __all__ = [
|
||||
"EvaluationPackFrame",
|
||||
"LatestWinsQueue",
|
||||
"LIDAR_EVIDENCE_PROFILE_SCHEMA",
|
||||
"LIDAR_EQUIVALENCE_REPORT_SCHEMA",
|
||||
"LIDAR_QUALITY_REPORT_SCHEMA",
|
||||
"LIDAR_READINESS_SCHEMA",
|
||||
"LIDAR_REPLAY_PACK_SCHEMA",
|
||||
"LIVE_INGRESS_SCHEMA",
|
||||
"LIVE_INGRESS_WIRE_SCHEMA",
|
||||
"LiveIngressEvent",
|
||||
@@ -152,6 +169,10 @@ __all__ = [
|
||||
"LidarPoseStatus",
|
||||
"LidarQualityMonitor",
|
||||
"LidarReadiness",
|
||||
"LidarReplayError",
|
||||
"LidarReplayPackV2",
|
||||
"LidarReplayPointFrame",
|
||||
"LidarReplayPoseFrame",
|
||||
"LidarRepresentation",
|
||||
"LidarStageAssessment",
|
||||
"LidarTimeBasis",
|
||||
@@ -167,6 +188,7 @@ __all__ = [
|
||||
"MultiratePerceptionQualificationResult",
|
||||
"QUALIFICATION_POLICY",
|
||||
"K1_LAB_LIDAR_PACK_V1_PROFILE",
|
||||
"K1_LIDAR_PACK_V2_PROFILE",
|
||||
"K1_LIVE_LIDAR_PROFILE",
|
||||
"QueueSnapshot",
|
||||
"RecordedCalibratedFusion",
|
||||
@@ -201,6 +223,7 @@ __all__ = [
|
||||
"validate_multirate_perception_qualification_result",
|
||||
"prepare_recorded_qualification_slice",
|
||||
"assess_lidar_profile",
|
||||
"build_lidar_replay_pack_v2",
|
||||
"DetectionFrame",
|
||||
"ObjectDetection",
|
||||
"RecordedPerceptionOverlayError",
|
||||
@@ -221,5 +244,8 @@ __all__ = [
|
||||
"validate_tracked_fusion_qualification_result",
|
||||
"validate_annotation_workspace",
|
||||
"lidar_readiness_document",
|
||||
"lidar_pack_catalog_item",
|
||||
"lidar_pack_detail",
|
||||
"sensor_frame_xyzi",
|
||||
"verify_lidar_replay_equivalence",
|
||||
]
|
||||
|
||||
@@ -621,3 +621,17 @@ K1_LAB_LIDAR_PACK_V1_PROFILE: Final = LidarEvidenceProfile(
|
||||
lidar_imu_extrinsic_available=False,
|
||||
camera_extrinsic_available=True,
|
||||
)
|
||||
|
||||
K1_LIDAR_PACK_V2_PROFILE: Final = LidarEvidenceProfile(
|
||||
profile_id="xgrids-k1-lidar-replay-pack/v2",
|
||||
representation=LidarRepresentation.VENDOR_MAP_INCREMENT,
|
||||
coordinate_space=LidarCoordinateSpace.MAP,
|
||||
coordinate_frame="map",
|
||||
frame_time_basis=LidarTimeBasis.HOST_ARRIVAL,
|
||||
point_fields=(LidarPointField.XYZ, LidarPointField.INTENSITY),
|
||||
pose_status=LidarPoseStatus.BEST_EFFORT,
|
||||
scan_geometry_known=False,
|
||||
imu_samples_available=False,
|
||||
lidar_imu_extrinsic_available=False,
|
||||
camera_extrinsic_available=True,
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user