fix(m4): retain compact obstacle evidence
This commit is contained in:
+20
-18
@@ -31,6 +31,7 @@ from k1link.laboratory import (
|
||||
)
|
||||
from k1link.sessions import (
|
||||
MaterializedRecording,
|
||||
RecordedCameraFrameService,
|
||||
RecordedMediaInspector,
|
||||
RecordedMediaManifest,
|
||||
RecordingPreparationQueueFull,
|
||||
@@ -191,6 +192,16 @@ session_recorded_media_inspector = RecordedMediaInspector(
|
||||
)
|
||||
_ffmpeg = _resolve_media_tool("ffmpeg")
|
||||
_ffprobe = _resolve_media_tool("ffprobe")
|
||||
session_recorded_camera_frame_service = (
|
||||
RecordedCameraFrameService(
|
||||
session_store,
|
||||
session_recorded_media_inspector,
|
||||
ffmpeg_path=_ffmpeg,
|
||||
cache_root=session_store.data_dir / "camera-frame-cache",
|
||||
)
|
||||
if _ffmpeg is not None
|
||||
else None
|
||||
)
|
||||
session_legacy_perception_overlay_store = (
|
||||
RecordedPerceptionOverlayStore(
|
||||
jobs_root=REPOSITORY_ROOT / ".runtime" / "compute-jobs",
|
||||
@@ -586,9 +597,7 @@ app.include_router(
|
||||
app.include_router(
|
||||
build_advanced_laboratory_router(
|
||||
evidence_registry=LABORATORY_EVIDENCE_REGISTRY,
|
||||
evidence_runtime_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments"
|
||||
),
|
||||
evidence_runtime_root_provider=lambda: REPOSITORY_ROOT / ".runtime" / "compute-experiments",
|
||||
e31_root_provider=lambda: (
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e31" / "source-qualifications"
|
||||
),
|
||||
@@ -756,22 +765,19 @@ app.include_router(
|
||||
app.include_router(
|
||||
build_m4_threat_replay_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "m4"
|
||||
/ "replay-threat"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "m4" / "replay-threat"
|
||||
),
|
||||
camera_frame_provider=(
|
||||
session_recorded_camera_frame_service.extract
|
||||
if session_recorded_camera_frame_service is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
)
|
||||
app.include_router(
|
||||
build_e46e_ready_stack_router(
|
||||
root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46e"
|
||||
/ "ready-stack-results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46e" / "ready-stack-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
@@ -785,11 +791,7 @@ app.include_router(
|
||||
/ "dashcam-bakeoff-results"
|
||||
),
|
||||
e46e_root_provider=lambda: (
|
||||
REPOSITORY_ROOT
|
||||
/ ".runtime"
|
||||
/ "compute-experiments"
|
||||
/ "e46e"
|
||||
/ "ready-stack-results"
|
||||
REPOSITORY_ROOT / ".runtime" / "compute-experiments" / "e46e" / "ready-stack-results"
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Final
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
from fastapi import APIRouter, HTTPException, Query, Response
|
||||
|
||||
from k1link.perception.threat_replay import (
|
||||
THREAT_REPLAY_FRAME_SCHEMA,
|
||||
@@ -22,20 +22,21 @@ from k1link.perception.threat_replay import (
|
||||
ThreatReplayResult,
|
||||
read_threat_replay_result,
|
||||
)
|
||||
from k1link.sessions import RecordedCameraFrame, SessionIntegrityError
|
||||
|
||||
M4_THREAT_CATALOG_SCHEMA: Final = "missioncore.m4-threat-replay-catalog/v1"
|
||||
M4_THREAT_VIEW_SCHEMA: Final = "missioncore.m4-threat-replay-view/v1"
|
||||
M4_THREAT_VIDEO_SCHEMA: Final = "missioncore.m4-threat-video-overlay/v1"
|
||||
M4_THREAT_VISUAL_CATALOG_SCHEMA: Final = (
|
||||
"missioncore.m4-threat-visual-catalog/v1"
|
||||
)
|
||||
M4_THREAT_VISUAL_CATALOG_SCHEMA: Final = "missioncore.m4-threat-visual-catalog/v1"
|
||||
_RESULT_ID = re.compile(rf"^{THREAT_REPLAY_RESULT_PREFIX}[a-f0-9]{{64}}$")
|
||||
RootProvider = Callable[[], Path | None]
|
||||
CameraFrameProvider = Callable[[str, int], RecordedCameraFrame]
|
||||
|
||||
|
||||
def build_m4_threat_replay_router(
|
||||
*,
|
||||
root_provider: RootProvider = lambda: None,
|
||||
camera_frame_provider: CameraFrameProvider | None = None,
|
||||
) -> APIRouter:
|
||||
router = APIRouter(prefix="/api/v1/laboratory/m4-threat", tags=["laboratory"])
|
||||
|
||||
@@ -109,10 +110,52 @@ def build_m4_threat_replay_router(
|
||||
**copy.deepcopy(frames[ordinal - 1]),
|
||||
"result_id": result_id,
|
||||
"ordinal": ordinal,
|
||||
"camera_url": (
|
||||
f"/api/v1/laboratory/m4-threat/results/{result_id}/visuals/{ordinal}/camera"
|
||||
),
|
||||
"ground_truth": False,
|
||||
"access": "read-only-replay-simulated",
|
||||
}
|
||||
|
||||
@router.get("/results/{result_id}/visuals/{ordinal}/camera")
|
||||
def get_visual_camera(result_id: str, ordinal: int) -> Response:
|
||||
if camera_frame_provider is None:
|
||||
raise HTTPException(status_code=503, detail="M4.6 camera decoder недоступен")
|
||||
frozen = result(result_id)
|
||||
if not 1 <= ordinal <= 32:
|
||||
raise HTTPException(status_code=404, detail="M4.6 visual frame не найден")
|
||||
frames = _read_jsonl(frozen.result_root / "visual-frames.jsonl")
|
||||
if len(frames) != 32:
|
||||
raise HTTPException(status_code=404, detail="M4.6 visual frame не найден")
|
||||
identity = frozen.manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise HTTPException(status_code=404, detail="M4.6 source identity не найдена")
|
||||
session_id = identity.get("source_session_id")
|
||||
sequence = frames[ordinal - 1].get("sequence")
|
||||
if not isinstance(session_id, str) or not isinstance(sequence, int):
|
||||
raise HTTPException(status_code=404, detail="M4.6 camera identity не найдена")
|
||||
try:
|
||||
camera = camera_frame_provider(session_id, sequence)
|
||||
except (OSError, SessionIntegrityError, ValueError):
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="M4.6 exact camera frame недоступен",
|
||||
) from None
|
||||
if camera.width != 800 or camera.height != 600:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="M4.6 camera frame нарушил размерный контракт",
|
||||
)
|
||||
return Response(
|
||||
content=camera.payload,
|
||||
media_type=camera.media_type,
|
||||
headers={
|
||||
"Cache-Control": "private, max-age=31536000, immutable",
|
||||
"ETag": f'"{camera.sha256}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
@router.get("/results/{result_id}/video-overlay")
|
||||
def get_video_overlay(result_id: str) -> dict[str, object]:
|
||||
frozen = result(result_id)
|
||||
@@ -160,9 +203,7 @@ def _cached_video_overlay(
|
||||
frames.append(
|
||||
{
|
||||
"frame_index": expected_sequence,
|
||||
"session_seconds": _nonnegative_int(
|
||||
row.get("source_time_ns"), "source time"
|
||||
)
|
||||
"session_seconds": _nonnegative_int(row.get("source_time_ns"), "source time")
|
||||
/ 1_000_000_000,
|
||||
"source_available": row["source_available"],
|
||||
"camera_proposals": copy.deepcopy(row["camera_proposals"]),
|
||||
@@ -209,9 +250,7 @@ def _project_result(result: ThreatReplayResult) -> dict[str, object]:
|
||||
},
|
||||
"metrics": copy.deepcopy(result.metrics),
|
||||
"configuration": copy.deepcopy(result.report["configuration"]),
|
||||
"acceptance_requirements": copy.deepcopy(
|
||||
result.report["acceptance_requirements"]
|
||||
),
|
||||
"acceptance_requirements": copy.deepcopy(result.report["acceptance_requirements"]),
|
||||
"limitations": copy.deepcopy(result.report["limitations"]),
|
||||
"accepted": result.accepted,
|
||||
"ground_truth": False,
|
||||
@@ -271,9 +310,7 @@ def _candidates(provider: RootProvider) -> list[Path]:
|
||||
(
|
||||
item
|
||||
for item in root.iterdir()
|
||||
if item.is_dir()
|
||||
and not item.is_symlink()
|
||||
and _RESULT_ID.fullmatch(item.name)
|
||||
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
|
||||
),
|
||||
key=lambda item: item.stat().st_mtime_ns,
|
||||
reverse=True,
|
||||
|
||||
Reference in New Issue
Block a user