feat(perception): add recorded replay maturation labs
This commit is contained in:
@@ -0,0 +1,279 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46E_RUNTIME_SCHEMA,
|
||||
E46EReadyStackError,
|
||||
analyze_e46e_frames,
|
||||
build_e46e_ready_stack,
|
||||
read_e46e_ready_stack,
|
||||
)
|
||||
|
||||
|
||||
def test_e46e_projects_stock_deepstream_output_without_custom_tracking(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
source, profile = _source_and_profile(tmp_path, frame_count=6)
|
||||
raw = _raw_output(tmp_path, profile, frame_count=6)
|
||||
_detector(raw, 0, "car", (10, 20, 40, 60), 0.91)
|
||||
_tracker(raw, 0, 7, "car", (10, 20, 40, 60), 0.88)
|
||||
_tracker(raw, 1, 7, "car", (12, 20, 42, 60), 0.73)
|
||||
_detector(raw, 5, "person", (100, 80, 130, 160), 0.93)
|
||||
_tracker(raw, 5, 7, "person", (100, 80, 130, 160), 0.79)
|
||||
|
||||
result = build_e46e_ready_stack(
|
||||
source_job_root=source,
|
||||
raw_root=raw,
|
||||
profile_path=profile,
|
||||
output_root=tmp_path / "results",
|
||||
)
|
||||
metrics = result["report"]["metrics"]
|
||||
assert metrics["frame_count"] == 6
|
||||
assert metrics["detection_observation_count"] == 2
|
||||
assert metrics["track_observation_count"] == 3
|
||||
assert metrics["detection_box_clipped_count"] == 0
|
||||
assert metrics["track_box_clipped_count"] == 0
|
||||
assert metrics["tracker_recovered_frame_count"] == 1
|
||||
assert metrics["full_layer_blackout_event_count"] == 1
|
||||
assert metrics["route_id_gap_event_count"] == 1
|
||||
assert metrics["track_class_switch_count"] == 1
|
||||
assert result["frames"][1]["objects"][0]["object_id"] == "nvdcf-7"
|
||||
assert result["frames"][1]["objects"][0]["bbox"] == [12.0, 20.0, 30.0, 40.0]
|
||||
assert [
|
||||
(component["kind"], component["role"])
|
||||
for component in result["report"]["method"]["components"]
|
||||
] == [
|
||||
("source", "exact recorded camera evidence"),
|
||||
("model", "framewise traffic-object detection"),
|
||||
("tool", "official RT-DETR output decoding"),
|
||||
("algorithm", "route-local temporal association"),
|
||||
("runtime", "GPU inference and media pipeline"),
|
||||
]
|
||||
assert result["report"]["decision"]["custom_temporal_logic_used"] is False
|
||||
assert result["manifest"]["authority"]["navigation_or_safety_accepted"] is False
|
||||
|
||||
repeated = build_e46e_ready_stack(
|
||||
source_job_root=source,
|
||||
raw_root=raw,
|
||||
profile_path=profile,
|
||||
output_root=tmp_path / "results",
|
||||
)
|
||||
assert repeated["result_id"] == result["result_id"]
|
||||
|
||||
|
||||
def test_e46e_clips_only_display_geometry_at_the_source_plane(tmp_path: Path) -> None:
|
||||
source, profile = _source_and_profile(tmp_path, frame_count=1)
|
||||
raw = _raw_output(tmp_path, profile, frame_count=1)
|
||||
_tracker(raw, 0, 31, "person", (447, 547, 527, 608), 0.69)
|
||||
|
||||
result = build_e46e_ready_stack(
|
||||
source_job_root=source,
|
||||
raw_root=raw,
|
||||
profile_path=profile,
|
||||
output_root=tmp_path / "results",
|
||||
)
|
||||
|
||||
item = result["frames"][0]["objects"][0]
|
||||
assert item["bbox"] == [447.0, 547.0, 80.0, 53.0]
|
||||
assert item["source_bbox_ltrb"] == [447.0, 547.0, 527.0, 608.0]
|
||||
assert item["source_plane_clipped"] is True
|
||||
assert result["report"]["metrics"]["track_box_clipped_count"] == 1
|
||||
assert result["report"]["decision"]["custom_temporal_logic_used"] is False
|
||||
|
||||
|
||||
def test_e46e_rejects_tampered_immutable_artifact(tmp_path: Path) -> None:
|
||||
source, profile = _source_and_profile(tmp_path, frame_count=1)
|
||||
raw = _raw_output(tmp_path, profile, frame_count=1)
|
||||
result = build_e46e_ready_stack(
|
||||
source_job_root=source,
|
||||
raw_root=raw,
|
||||
profile_path=profile,
|
||||
output_root=tmp_path / "results",
|
||||
)
|
||||
(result["result_root"] / "overlay.mp4").write_bytes(b"changed")
|
||||
with pytest.raises(E46EReadyStackError, match="artifact changed"):
|
||||
read_e46e_ready_stack(result["result_root"])
|
||||
|
||||
|
||||
def test_e46e_frame_analyzer_requires_contiguous_source_order() -> None:
|
||||
with pytest.raises(E46EReadyStackError, match="not contiguous"):
|
||||
analyze_e46e_frames(
|
||||
[
|
||||
{
|
||||
"frame_index": 1,
|
||||
"session_seconds": 1.0,
|
||||
"detections": [],
|
||||
"objects": [],
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _source_and_profile(tmp_path: Path, *, frame_count: int) -> tuple[Path, Path]:
|
||||
source = tmp_path / "source-job"
|
||||
camera = source / "input" / "camera" / "sensor.camera.right" / "epoch-1"
|
||||
camera.mkdir(parents=True)
|
||||
index_rows = [
|
||||
{
|
||||
"schema_version": "missioncore.camera-recording-index/v1",
|
||||
"kind": "media",
|
||||
"sequence": sequence,
|
||||
"session_monotonic_ns": 1_000_000_000 + (sequence - 1) * 100_000_000,
|
||||
"sha256": hashlib.sha256(f"frame-{sequence}".encode()).hexdigest(),
|
||||
}
|
||||
for sequence in range(1, frame_count + 1)
|
||||
]
|
||||
index_path = camera / "index.jsonl"
|
||||
index_path.write_text(
|
||||
"".join(json.dumps(row, sort_keys=True) + "\n" for row in index_rows),
|
||||
encoding="utf-8",
|
||||
)
|
||||
stream_sha = "1" * 64
|
||||
summary_path = camera / "summary.json"
|
||||
summary_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": "missioncore.camera-recording/v1",
|
||||
"stream_sha256": stream_sha,
|
||||
"segment_count": frame_count,
|
||||
},
|
||||
indent=2,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
index_sha = _sha(index_path)
|
||||
summary_sha = _sha(summary_path)
|
||||
job = {
|
||||
"schema_version": "missioncore.compute-job/v1",
|
||||
"job_id": "recorded-test",
|
||||
"input": {
|
||||
"session_id": "test-session",
|
||||
"source_id": "sensor.camera.right",
|
||||
"segment_count": frame_count,
|
||||
"archive_index_sha256": index_sha,
|
||||
"archive_summary_sha256": summary_sha,
|
||||
"timeline": {"start_seconds": 10.0, "end_seconds": 11.0},
|
||||
},
|
||||
}
|
||||
(source / "job.json").write_text(json.dumps(job, indent=2) + "\n", encoding="utf-8")
|
||||
profile_value = {
|
||||
"schema_version": "missioncore.e46e-ready-stack-profile/v1",
|
||||
"profile_id": "e46e-test/v1",
|
||||
"source": {
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"job_id": "recorded-test",
|
||||
"session_id": "test-session",
|
||||
"segment_count": frame_count,
|
||||
"stream_sha256": stream_sha,
|
||||
"archive_index_sha256": index_sha,
|
||||
"archive_summary_sha256": summary_sha,
|
||||
},
|
||||
"runtime": {
|
||||
"container_image": "nvcr.io/nvidia/deepstream:9.1-samples-multiarch@sha256:"
|
||||
+ "2" * 64,
|
||||
"deepstream_version": "9.1",
|
||||
},
|
||||
"detector": {
|
||||
"name": "NVIDIA TrafficCamNet Transformer Lite",
|
||||
"version": "test",
|
||||
"model_sha256": "3" * 64,
|
||||
"custom_postprocessing": False,
|
||||
},
|
||||
"parser": {
|
||||
"name": "NVIDIA DeepStream TAO custom bounding-box parser",
|
||||
"repository": "https://github.com/NVIDIA/DeepStream.git",
|
||||
"commit": "8" * 40,
|
||||
"symbol": "NvDsInferParseCustomDDETRTAO",
|
||||
"library_sha256": "9" * 64,
|
||||
"custom_mission_core_logic": False,
|
||||
},
|
||||
"tracker": {
|
||||
"name": "NVIDIA NvDCF",
|
||||
"configuration": "stock-accuracy",
|
||||
"custom_association": False,
|
||||
"custom_hold_or_stitch": False,
|
||||
},
|
||||
"output": {"frame_width": 800, "frame_height": 600},
|
||||
"authority": {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
}
|
||||
profile = tmp_path / "profile.json"
|
||||
profile.write_text(json.dumps(profile_value, indent=2) + "\n", encoding="utf-8")
|
||||
return source, profile
|
||||
|
||||
|
||||
def _raw_output(tmp_path: Path, profile_path: Path, *, frame_count: int) -> Path:
|
||||
raw = tmp_path / "raw"
|
||||
detections = raw / "detections"
|
||||
tracks = raw / "tracks"
|
||||
detections.mkdir(parents=True)
|
||||
tracks.mkdir()
|
||||
for frame in range(frame_count):
|
||||
(detections / f"00_000_{frame:06d}.txt").write_text("", encoding="utf-8")
|
||||
(tracks / f"00_000_{frame:06d}.txt").write_text("", encoding="utf-8")
|
||||
overlay = raw / "overlay.mp4"
|
||||
overlay.write_bytes(b"synthetic-overlay")
|
||||
(raw / "deepstream.log").write_text("synthetic success\n", encoding="utf-8")
|
||||
profile = json.loads(profile_path.read_text(encoding="utf-8"))
|
||||
image = profile["runtime"]["container_image"]
|
||||
runtime = {
|
||||
"schema_version": E46E_RUNTIME_SCHEMA,
|
||||
"status": "completed",
|
||||
"worker_host": "TEST-WORKER-006",
|
||||
"gpu_name": "Synthetic RTX",
|
||||
"container_image": image,
|
||||
"container_image_digest": image.rsplit("@sha256:", 1)[1],
|
||||
"model_sha256": profile["detector"]["model_sha256"],
|
||||
"model_engine_sha256": "4" * 64,
|
||||
"deepstream_config_sha256": "5" * 64,
|
||||
"detector_config_sha256": "6" * 64,
|
||||
"parser_library_sha256": profile["parser"]["library_sha256"],
|
||||
"tracker_config_sha256": "7" * 64,
|
||||
"input_stream_sha256": profile["source"]["stream_sha256"],
|
||||
"overlay_sha256": _sha(overlay),
|
||||
}
|
||||
(raw / "runtime.json").write_text(json.dumps(runtime, indent=2) + "\n", encoding="utf-8")
|
||||
return raw
|
||||
|
||||
|
||||
def _detector(
|
||||
raw: Path,
|
||||
frame: int,
|
||||
label: str,
|
||||
box: tuple[int, int, int, int],
|
||||
confidence: float,
|
||||
) -> None:
|
||||
left, top, right, bottom = box
|
||||
(raw / "detections" / f"00_000_{frame:06d}.txt").write_text(
|
||||
f"{label} 0.0 0 0.0 {left} {top} {right} {bottom} 0 0 0 0 0 0 0 {confidence}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _tracker(
|
||||
raw: Path,
|
||||
frame: int,
|
||||
track_id: int,
|
||||
label: str,
|
||||
box: tuple[int, int, int, int],
|
||||
confidence: float,
|
||||
) -> None:
|
||||
left, top, right, bottom = box
|
||||
(raw / "tracks" / f"00_000_{frame:06d}.txt").write_text(
|
||||
f"{label} {track_id} 0.0 0 0.0 {left} {top} {right} {bottom} 0 0 0 0 0 0 0 {confidence}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _sha(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
Reference in New Issue
Block a user