feat(lab): seal full RAVNOVES004TREE semantic review
This commit is contained in:
@@ -7,7 +7,9 @@ import hashlib
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tarfile
|
||||
import tempfile
|
||||
import zipfile
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
@@ -15,6 +17,8 @@ from typing import Any
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
from k1link.compute.jobs import validate_camera_compute_job
|
||||
|
||||
from k1link.laboratory.vegetation_shadow_lab import (
|
||||
LAB_SCHEMA,
|
||||
RESULT_PREFIX,
|
||||
@@ -45,11 +49,20 @@ TGS_COLORS = {
|
||||
2: (235, 112, 122),
|
||||
3: (150, 154, 163),
|
||||
}
|
||||
FULL_ROUTE_SOURCE_ID = "RAVNOVES004TREE"
|
||||
FULL_ROUTE_FRAME_COUNT = 6830
|
||||
FULL_ROUTE_JOB_ID = "recorded-camera-eb2783c5480d56bda07c8af0"
|
||||
FULL_ROUTE_INPUT_SHA256 = (
|
||||
"eb2783c5480d56bda07c8af008dff5344d19dc550ef70fe2075d6f098f7cc715"
|
||||
)
|
||||
FULL_ROUTE_STREAM_SHA256 = (
|
||||
"e5eb017e2cc0f546736eda5235ca157b501913093cb64af5e548e335417e1bac"
|
||||
)
|
||||
|
||||
|
||||
def _read_json(path: Path, label: str) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
value = json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise VegetationShadowLabError(f"{label} is invalid") from exc
|
||||
if not isinstance(value, dict):
|
||||
@@ -86,6 +99,126 @@ def _image_proof(descriptor: dict[str, object]) -> dict[str, object]:
|
||||
return {"path": descriptor["path"], "sha256": descriptor["sha256"]}
|
||||
|
||||
|
||||
def _mask_archive_descriptor(
|
||||
path: Path,
|
||||
relative: str,
|
||||
artifacts: list[dict[str, object]],
|
||||
*,
|
||||
role: str,
|
||||
) -> dict[str, object]:
|
||||
descriptor = {
|
||||
"role": role,
|
||||
"path": relative,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": sha256_path(path),
|
||||
"media_type": "application/zip",
|
||||
}
|
||||
artifacts.append(descriptor)
|
||||
return descriptor
|
||||
|
||||
|
||||
def _repack_eomt_masks(source: Path, destination: Path, frame_count: int) -> None:
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
expected = [f"semantic-masks/frame-{sequence + 1:06d}.png" for sequence in range(frame_count)]
|
||||
try:
|
||||
with (
|
||||
tarfile.open(source, mode="r:gz") as archive,
|
||||
zipfile.ZipFile(
|
||||
destination,
|
||||
mode="x",
|
||||
compression=zipfile.ZIP_STORED,
|
||||
allowZip64=True,
|
||||
) as output,
|
||||
):
|
||||
members = [member for member in archive.getmembers() if member.isfile()]
|
||||
if [member.name.removeprefix("./") for member in members] != expected:
|
||||
raise VegetationShadowLabError("full-route EoMT mask sequence changed")
|
||||
for member, expected_name in zip(members, expected, strict=True):
|
||||
if member.size < 8 or member.size > 1024 * 1024:
|
||||
raise VegetationShadowLabError("full-route EoMT mask size changed")
|
||||
stream = archive.extractfile(member)
|
||||
if stream is None:
|
||||
raise VegetationShadowLabError("full-route EoMT mask is unavailable")
|
||||
output.writestr(
|
||||
f"masks/{Path(expected_name).name}",
|
||||
stream.read(),
|
||||
)
|
||||
except (OSError, tarfile.TarError, zipfile.BadZipFile) as exc:
|
||||
destination.unlink(missing_ok=True)
|
||||
raise VegetationShadowLabError("full-route EoMT archive is invalid") from exc
|
||||
|
||||
|
||||
def _validate_zip_masks(path: Path, frame_count: int) -> None:
|
||||
expected = [f"masks/frame-{sequence + 1:06d}.png" for sequence in range(frame_count)]
|
||||
try:
|
||||
with zipfile.ZipFile(path) as archive:
|
||||
members = archive.infolist()
|
||||
if (
|
||||
[member.filename for member in members] != expected
|
||||
or any(
|
||||
member.is_dir() or member.file_size < 8 or member.file_size > 1024 * 1024
|
||||
for member in members
|
||||
)
|
||||
):
|
||||
raise VegetationShadowLabError("full-route semantic mask sequence changed")
|
||||
except (OSError, zipfile.BadZipFile) as exc:
|
||||
raise VegetationShadowLabError("full-route semantic archive is invalid") from exc
|
||||
|
||||
|
||||
def _full_route_frame_times(media: dict[str, Any], frame_count: int) -> list[int]:
|
||||
epochs = media.get("epochs")
|
||||
start = media.get("timeline_start_seconds")
|
||||
end = media.get("timeline_end_seconds")
|
||||
if (
|
||||
not isinstance(epochs, list)
|
||||
or len(epochs) != 1
|
||||
or not isinstance(start, (int, float))
|
||||
or not isinstance(end, (int, float))
|
||||
):
|
||||
raise VegetationShadowLabError("recorded media timeline changed")
|
||||
epoch = epochs[0]
|
||||
segments = epoch.get("segments") if isinstance(epoch, dict) else None
|
||||
if not isinstance(segments, list) or len(segments) != frame_count:
|
||||
raise VegetationShadowLabError("recorded media segment count changed")
|
||||
starts = [float(start)]
|
||||
previous_end = 0.0
|
||||
for sequence, raw in enumerate(segments, start=1):
|
||||
if (
|
||||
not isinstance(raw, dict)
|
||||
or raw.get("sequence") != sequence
|
||||
or not isinstance(raw.get("end_time_seconds"), (int, float))
|
||||
or float(raw["end_time_seconds"]) <= previous_end
|
||||
):
|
||||
raise VegetationShadowLabError("recorded media segment timeline changed")
|
||||
if sequence < frame_count:
|
||||
starts.append(float(start) + float(raw["end_time_seconds"]))
|
||||
previous_end = float(raw["end_time_seconds"])
|
||||
if abs((float(start) + previous_end) - float(end)) > 0.001:
|
||||
raise VegetationShadowLabError("recorded media duration changed")
|
||||
return [round(value * 1_000_000_000) for value in starts]
|
||||
|
||||
|
||||
def _eomt_taxonomy(profile: dict[str, Any]) -> dict[str, object]:
|
||||
taxonomy = profile.get("target_taxonomy")
|
||||
if not isinstance(taxonomy, dict) or set(taxonomy) != {str(index) for index in range(16)}:
|
||||
raise VegetationShadowLabError("EoMT target taxonomy changed")
|
||||
classes = []
|
||||
for class_id in range(16):
|
||||
digest = hashlib.sha256(f"mission-core-segment-{class_id}".encode()).digest()
|
||||
classes.append(
|
||||
{
|
||||
"class_id": class_id,
|
||||
"label": taxonomy[str(class_id)],
|
||||
"color_rgb": [64 + digest[index] % 176 for index in range(3)],
|
||||
"disposition": "undefined" if class_id == 0 else "prediction",
|
||||
}
|
||||
)
|
||||
return {
|
||||
"schema_version": "missioncore.recorded-eomt-taxonomy/v1",
|
||||
"classes": classes,
|
||||
}
|
||||
|
||||
|
||||
def _render_tgs_costmaps(tgs_root: Path, destination: Path) -> list[Path]:
|
||||
result = _read_json(tgs_root / "result.json", "mixed-route TGS result")
|
||||
evidence = result.get("evidence")
|
||||
@@ -392,6 +525,338 @@ def seal_mixed_route_vegetation_review(
|
||||
raise
|
||||
|
||||
|
||||
def seal_mixed_route_full_video_review(
|
||||
*,
|
||||
base_lab_root: Path,
|
||||
job_root: Path,
|
||||
recorded_media_preparation_path: Path,
|
||||
eomt_root: Path,
|
||||
eomt_profile_path: Path,
|
||||
ddrnet_root: Path,
|
||||
output_root: Path,
|
||||
) -> Path:
|
||||
"""Publish the complete 004 city/nature pass in the existing M4.7 LAB."""
|
||||
|
||||
base_root = base_lab_root.resolve(strict=True)
|
||||
base = _read_json(base_root / "result.json", "base vegetation LAB")
|
||||
base_identity = base.get("identity")
|
||||
if (
|
||||
base.get("schema_version") != LAB_SCHEMA
|
||||
or not isinstance(base_identity, dict)
|
||||
or hashlib.sha256(canonical_json(base_identity)).hexdigest()
|
||||
!= base.get("identity_sha256")
|
||||
or base.get("result_id") != base_root.name
|
||||
or not base_root.name.startswith(RESULT_PREFIX)
|
||||
or base.get("authority", {}).get("commands_enabled") is not False
|
||||
):
|
||||
raise VegetationShadowLabError("base vegetation LAB proof changed")
|
||||
|
||||
job = validate_camera_compute_job(job_root)
|
||||
if (
|
||||
job.job_id != FULL_ROUTE_JOB_ID
|
||||
or job.input_sha256 != FULL_ROUTE_INPUT_SHA256
|
||||
or job.session_id != "20260828T130511Z_viewer_live"
|
||||
or job.source_id != "sensor.camera.right"
|
||||
or job.segment_count != FULL_ROUTE_FRAME_COUNT
|
||||
):
|
||||
raise VegetationShadowLabError("full-route camera job changed")
|
||||
|
||||
eomt = _read_json(eomt_root / "result.json", "full-route EoMT result")
|
||||
eomt_report = _read_json(eomt_root / "run-report.json", "full-route EoMT report")
|
||||
decode_repair = _read_json(
|
||||
eomt_root / "decode-repair.json",
|
||||
"full-route video decode repair",
|
||||
)
|
||||
eomt_input = eomt_report.get("input")
|
||||
eomt_metrics = eomt_report.get("metrics")
|
||||
if (
|
||||
eomt.get("schema_version") != "missioncore.recorded-perception-result/v2"
|
||||
or eomt.get("ground_truth") is not False
|
||||
or eomt.get("frames_processed") != FULL_ROUTE_FRAME_COUNT
|
||||
or not isinstance(eomt_input, dict)
|
||||
or eomt_input.get("job_id") != job.job_id
|
||||
or eomt_input.get("input_sha256") != job.input_sha256
|
||||
or eomt_input.get("frames_admitted") != FULL_ROUTE_FRAME_COUNT
|
||||
or not isinstance(eomt_metrics, dict)
|
||||
or eomt_metrics.get("frames_processed") != FULL_ROUTE_FRAME_COUNT
|
||||
):
|
||||
raise VegetationShadowLabError("full-route EoMT contract changed")
|
||||
if (
|
||||
decode_repair.get("schema_version")
|
||||
!= "missioncore.recorded-video-decode-repair/v1"
|
||||
or decode_repair.get("decoder") != "ffmpeg-h264_cuvid-output-corrupt"
|
||||
or decode_repair.get("packets_requested") != FULL_ROUTE_FRAME_COUNT
|
||||
or decode_repair.get("frames_decoded") != FULL_ROUTE_FRAME_COUNT - 1
|
||||
or decode_repair.get("repaired_frame_count") != 1
|
||||
or decode_repair.get("repairs")
|
||||
!= [
|
||||
{
|
||||
"sequence": 6092,
|
||||
"packet_pts": 55656450,
|
||||
"method": "duplicate-previous-decoded-frame",
|
||||
}
|
||||
]
|
||||
):
|
||||
raise VegetationShadowLabError("full-route video decode repair changed")
|
||||
eomt_artifacts = {
|
||||
item.get("kind"): item
|
||||
for item in eomt.get("artifacts", [])
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
eomt_archive_proof = eomt_artifacts.get("panoptic-mask-archive")
|
||||
if not isinstance(eomt_archive_proof, dict):
|
||||
raise VegetationShadowLabError("full-route EoMT mask proof is missing")
|
||||
eomt_archive = eomt_root / str(eomt_archive_proof.get("path"))
|
||||
if (
|
||||
not eomt_archive.is_file()
|
||||
or eomt_archive.stat().st_size != eomt_archive_proof.get("byte_length")
|
||||
or sha256_path(eomt_archive) != eomt_archive_proof.get("sha256")
|
||||
):
|
||||
raise VegetationShadowLabError("full-route EoMT mask proof changed")
|
||||
|
||||
ddrnet = _read_json(ddrnet_root / "result.json", "full-route DDRNet result")
|
||||
ddrnet_decode_repair = _read_json(
|
||||
ddrnet_root / "decode-repair.json",
|
||||
"full-route DDRNet video decode repair",
|
||||
)
|
||||
ddrnet_source = ddrnet.get("source")
|
||||
ddrnet_video = ddrnet.get("video_semantics")
|
||||
if (
|
||||
ddrnet.get("schema_version") != "missioncore.lab-v1-goose-vegetation-run/v1"
|
||||
or ddrnet.get("mode") != "ravnoves-video"
|
||||
or ddrnet.get("candidate", {}).get("candidate_key") != "ddrnet"
|
||||
or not isinstance(ddrnet_source, dict)
|
||||
or ddrnet_source.get("source_id")
|
||||
!= f"{FULL_ROUTE_SOURCE_ID}/right-{FULL_ROUTE_STREAM_SHA256}"
|
||||
or ddrnet_source.get("input_count") != FULL_ROUTE_FRAME_COUNT
|
||||
or ddrnet_source.get("ground_truth_available") is not False
|
||||
or not isinstance(ddrnet_video, dict)
|
||||
or ddrnet_video.get("base_m4_result_id") is not None
|
||||
or ddrnet.get("authority", {}).get("navigation_accepted") is not False
|
||||
or ddrnet.get("authority", {}).get("actuation_accepted") is not False
|
||||
):
|
||||
raise VegetationShadowLabError("full-route DDRNet contract changed")
|
||||
if ddrnet_decode_repair != decode_repair:
|
||||
raise VegetationShadowLabError("full-route model decoders disagree")
|
||||
ddrnet_archive_proof = ddrnet_video.get("mask_archive")
|
||||
ddrnet_taxonomy = ddrnet_video.get("taxonomy")
|
||||
if (
|
||||
not isinstance(ddrnet_archive_proof, dict)
|
||||
or ddrnet_archive_proof.get("frame_count") != FULL_ROUTE_FRAME_COUNT
|
||||
or not isinstance(ddrnet_taxonomy, dict)
|
||||
):
|
||||
raise VegetationShadowLabError("full-route DDRNet mask proof changed")
|
||||
ddrnet_archive = ddrnet_root / str(ddrnet_archive_proof.get("path"))
|
||||
if (
|
||||
not ddrnet_archive.is_file()
|
||||
or ddrnet_archive.stat().st_size != ddrnet_archive_proof.get("byte_length")
|
||||
or sha256_path(ddrnet_archive) != ddrnet_archive_proof.get("sha256")
|
||||
):
|
||||
raise VegetationShadowLabError("full-route DDRNet archive changed")
|
||||
_validate_zip_masks(ddrnet_archive, FULL_ROUTE_FRAME_COUNT)
|
||||
|
||||
media_document = _read_json(
|
||||
recorded_media_preparation_path.resolve(strict=True),
|
||||
"recorded media preparation",
|
||||
)
|
||||
media = media_document.get("manifest")
|
||||
if (
|
||||
media_document.get("schema_version") != "missioncore.recorded-media-preparation/v3"
|
||||
or media_document.get("session_id") != job.session_id
|
||||
or media_document.get("artifact_id") != "recorded-video-6a3945242828a038"
|
||||
or media_document.get("checksum_sha256")
|
||||
!= "557e61f2839140dc9f97b5aea855c576b0616573080dff5d2852ab1df0558665"
|
||||
or not isinstance(media, dict)
|
||||
or media.get("source_id") != "recorded.camera.6a3945242828a038"
|
||||
or media.get("generation_sha256")
|
||||
!= "b073ea1e7babf1c77a664e1a5b95e3702d0e05b0e34c1e85a7c67a6f8b392ded"
|
||||
or media.get("byte_length") != 551674491
|
||||
or media.get("timeline_start_seconds") != job.timeline_start_seconds
|
||||
or media.get("timeline_end_seconds") != job.timeline_end_seconds
|
||||
or media.get("synchronization") != "host-arrival-best-effort"
|
||||
):
|
||||
raise VegetationShadowLabError("recorded media preparation changed")
|
||||
frame_times_ns = _full_route_frame_times(media, FULL_ROUTE_FRAME_COUNT)
|
||||
eomt_profile = _read_json(eomt_profile_path.resolve(strict=True), "EoMT profile")
|
||||
eomt_taxonomy = _eomt_taxonomy(eomt_profile)
|
||||
|
||||
output_root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
temporary = Path(tempfile.mkdtemp(prefix=".mixed-route-full-video-", dir=output_root))
|
||||
artifacts: list[dict[str, object]] = []
|
||||
try:
|
||||
eomt_destination = temporary / "video" / "eomt-semantic-masks.zip"
|
||||
_repack_eomt_masks(eomt_archive, eomt_destination, FULL_ROUTE_FRAME_COUNT)
|
||||
_validate_zip_masks(eomt_destination, FULL_ROUTE_FRAME_COUNT)
|
||||
eomt_descriptor = _mask_archive_descriptor(
|
||||
eomt_destination,
|
||||
"video/eomt-semantic-masks.zip",
|
||||
artifacts,
|
||||
role="full-route-eomt-semantic-mask-archive",
|
||||
)
|
||||
ddrnet_descriptor = _artifact(
|
||||
ddrnet_archive,
|
||||
temporary,
|
||||
"video/ddrnet-semantic-masks.zip",
|
||||
artifacts,
|
||||
role="full-route-ddrnet-semantic-mask-archive",
|
||||
media_type="application/zip",
|
||||
)
|
||||
_validate_zip_masks(
|
||||
temporary / "video" / "ddrnet-semantic-masks.zip",
|
||||
FULL_ROUTE_FRAME_COUNT,
|
||||
)
|
||||
proof_descriptors: dict[str, dict[str, object]] = {}
|
||||
for key, path in (
|
||||
("base", base_root / "result.json"),
|
||||
("job", job.manifest_path),
|
||||
("media", recorded_media_preparation_path.resolve(strict=True)),
|
||||
("eomt", eomt_root / "result.json"),
|
||||
("eomt_report", eomt_root / "run-report.json"),
|
||||
("decode_repair", eomt_root / "decode-repair.json"),
|
||||
("ddrnet", ddrnet_root / "result.json"),
|
||||
("ddrnet_decode_repair", ddrnet_root / "decode-repair.json"),
|
||||
):
|
||||
descriptor = _artifact(
|
||||
path,
|
||||
temporary,
|
||||
f"proofs/{key}.json",
|
||||
artifacts,
|
||||
role="full-route-proof",
|
||||
media_type="application/json",
|
||||
)
|
||||
proof_descriptors[key] = _image_proof(descriptor)
|
||||
|
||||
full_route = {
|
||||
"source_id": FULL_ROUTE_SOURCE_ID,
|
||||
"session_id": job.session_id,
|
||||
"source_job_id": job.job_id,
|
||||
"source_job_input_sha256": job.input_sha256,
|
||||
"source_stream_sha256": FULL_ROUTE_STREAM_SHA256,
|
||||
"recorded_media_source_id": media["source_id"],
|
||||
"recorded_media_generation_sha256": media["generation_sha256"],
|
||||
"frame_count": FULL_ROUTE_FRAME_COUNT,
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"timeline_start_seconds": job.timeline_start_seconds,
|
||||
"timeline_end_seconds": job.timeline_end_seconds,
|
||||
"frame_source_times_ns": frame_times_ns,
|
||||
"ground_truth": False,
|
||||
"decode_repair": {
|
||||
"repaired_frame_count": 1,
|
||||
"sequence": 6092,
|
||||
"method": "duplicate-previous-decoded-frame",
|
||||
"proofs": {
|
||||
"eomt": proof_descriptors["decode_repair"],
|
||||
"ddrnet": proof_descriptors["ddrnet_decode_repair"],
|
||||
},
|
||||
},
|
||||
"layers": {
|
||||
"city": {
|
||||
"name": "EoMT Cityscapes",
|
||||
"result_id": eomt["result_id"],
|
||||
"frame_count": FULL_ROUTE_FRAME_COUNT,
|
||||
"taxonomy": eomt_taxonomy,
|
||||
"mask_archive": {
|
||||
"path": eomt_descriptor["path"],
|
||||
"sha256": eomt_descriptor["sha256"],
|
||||
"byte_length": eomt_descriptor["byte_length"],
|
||||
},
|
||||
"inference_fps": eomt_metrics["inference_frames_per_second"],
|
||||
"latency_p95_ms": eomt_metrics["latency_ms"]["end_to_end_ms"]["p95"],
|
||||
"peak_reserved_vram_bytes": int(
|
||||
float(eomt_metrics["cuda_peak_memory_reserved_mib"]) * 1024 * 1024
|
||||
),
|
||||
},
|
||||
"vegetation": {
|
||||
"name": ddrnet["candidate"]["loaded_model_name"],
|
||||
"result_id": ddrnet["result_id"],
|
||||
"frame_count": FULL_ROUTE_FRAME_COUNT,
|
||||
"taxonomy": ddrnet_taxonomy,
|
||||
"mask_archive": {
|
||||
"path": ddrnet_descriptor["path"],
|
||||
"sha256": ddrnet_descriptor["sha256"],
|
||||
"byte_length": ddrnet_descriptor["byte_length"],
|
||||
},
|
||||
"inference_fps": ddrnet["timing"]["throughput_fps_from_mean_inference"],
|
||||
"latency_p95_ms": ddrnet["timing"]["latency_ms_p95"],
|
||||
"peak_reserved_vram_bytes": ddrnet["resource"]["peak_reserved_vram_bytes"],
|
||||
},
|
||||
},
|
||||
"proofs": proof_descriptors,
|
||||
"limitations": [
|
||||
"RAVNOVES004TREE has no manual route truth.",
|
||||
"One corrupt H.264 packet at sequence 6092 was represented by the previous decoded frame; the repair is sealed as evidence.",
|
||||
"EoMT and DDRNet were executed sequentially, not as a concurrent realtime stack.",
|
||||
"DDRNet vegetation subtypes remain prediction-only and are not planner authority.",
|
||||
"This full-video pass does not add full-route TGS, ditch or negative-obstacle proof.",
|
||||
"People and vehicles still require an independent fail-safe detector and STOP path.",
|
||||
],
|
||||
}
|
||||
authority = {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
"actuation_accepted": False,
|
||||
"camera_semantics_can_clear_rigid_geometry": False,
|
||||
}
|
||||
identity = {
|
||||
"lab_id": "lab-v1-vegetation-mission-policy",
|
||||
"base_result_id": base["result_id"],
|
||||
"selected_candidate": base_identity["selected_candidate"],
|
||||
"candidate_metrics": base_identity["candidate_metrics"],
|
||||
"source": {
|
||||
"shadow_session": FULL_ROUTE_SOURCE_ID,
|
||||
"shadow_camera": job.source_id,
|
||||
"shadow_frame_count": FULL_ROUTE_FRAME_COUNT,
|
||||
"video_shadow_frame_count": FULL_ROUTE_FRAME_COUNT,
|
||||
},
|
||||
"route_full_review": full_route,
|
||||
"authority": authority,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(canonical_json(identity)).hexdigest()
|
||||
result_id = f"{RESULT_PREFIX}{identity_sha256}"
|
||||
manifest = {
|
||||
"schema_version": LAB_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": datetime.now(UTC).isoformat(),
|
||||
"ground_truth": False,
|
||||
"status": "visual-shadow-ready-policy-not-authorized",
|
||||
"identity": identity,
|
||||
"source": identity["source"],
|
||||
"route_video": None,
|
||||
"route_review": None,
|
||||
"route_full_review": full_route,
|
||||
"method": {
|
||||
"completeness": "complete",
|
||||
"execution_class": "ai-inference",
|
||||
"pipeline_id": "ravnoves004tree-full-eomt-ddrnet-recorded-review/v1",
|
||||
},
|
||||
"metrics": {"candidates": base["metrics"]["candidates"]},
|
||||
"decision": {
|
||||
"selected_candidate": base_identity["selected_candidate"],
|
||||
"visual_shadow_ready": True,
|
||||
"full_video_shadow_ready": True,
|
||||
"mission_policy_ready_for_configuration": True,
|
||||
"multilayer_policy_review_ready": True,
|
||||
"navigation_accepted": False,
|
||||
"production_accepted": False,
|
||||
},
|
||||
"limitations": full_route["limitations"],
|
||||
"authority": authority,
|
||||
"catalogs": {"goose": [], "ravnoves": []},
|
||||
"artifacts": artifacts,
|
||||
}
|
||||
(temporary / "result.json").write_bytes(canonical_json(manifest) + b"\n")
|
||||
destination = output_root / result_id
|
||||
if destination.exists():
|
||||
raise VegetationShadowLabError("immutable full-route LAB result already exists")
|
||||
os.replace(temporary, destination)
|
||||
return destination
|
||||
except Exception:
|
||||
shutil.rmtree(temporary, ignore_errors=True)
|
||||
raise
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--base-lab-root", type=Path, required=True)
|
||||
|
||||
@@ -179,9 +179,77 @@ def _build_vegetation_lab_router(
|
||||
},
|
||||
)
|
||||
|
||||
@router.get("/{result_id}/route-masks/{layer}/{sequence}")
|
||||
def get_full_route_mask(result_id: str, layer: str, sequence: int) -> Response:
|
||||
candidate = _resolve_candidate(root_provider, definition, result_id)
|
||||
manifest = _read_verified(candidate, definition)
|
||||
route = manifest.get("route_full_review")
|
||||
layers = route.get("layers") if isinstance(route, dict) else None
|
||||
frame_count = route.get("frame_count") if isinstance(route, dict) else None
|
||||
selected = layers.get(layer) if isinstance(layers, dict) else None
|
||||
archive = selected.get("mask_archive") if isinstance(selected, dict) else None
|
||||
archive_relative = archive.get("path") if isinstance(archive, dict) else None
|
||||
if (
|
||||
layer not in {"city", "vegetation"}
|
||||
or not isinstance(frame_count, int)
|
||||
or not 0 <= sequence < frame_count
|
||||
or not isinstance(archive_relative, str)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="Full-route semantic mask not found")
|
||||
relative = PurePosixPath(archive_relative)
|
||||
artifacts = manifest.get("artifacts")
|
||||
if (
|
||||
relative.is_absolute()
|
||||
or str(relative) != archive_relative
|
||||
or any(part in {"", ".", ".."} for part in relative.parts)
|
||||
or relative.suffix != ".zip"
|
||||
or not isinstance(artifacts, list)
|
||||
or not any(
|
||||
isinstance(item, dict)
|
||||
and item.get("path") == archive_relative
|
||||
and item.get("media_type") == "application/zip"
|
||||
for item in artifacts
|
||||
)
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="Full-route semantic mask not found")
|
||||
return _zip_mask_response(candidate.joinpath(*relative.parts), sequence)
|
||||
|
||||
return router
|
||||
|
||||
|
||||
def _zip_mask_response(archive_path: Path, sequence: int) -> Response:
|
||||
member = f"masks/frame-{sequence + 1:06d}.png"
|
||||
try:
|
||||
before = archive_path.stat()
|
||||
with zipfile.ZipFile(archive_path) as frozen:
|
||||
info = frozen.getinfo(member)
|
||||
if info.is_dir() or info.file_size < 8 or info.file_size > 1024 * 1024:
|
||||
raise ValueError("Semantic mask member is invalid")
|
||||
payload = frozen.read(info)
|
||||
after = archive_path.stat()
|
||||
if (
|
||||
before.st_size != after.st_size
|
||||
or before.st_mtime_ns != after.st_mtime_ns
|
||||
or len(payload) != info.file_size
|
||||
):
|
||||
raise ValueError("Semantic mask archive changed during read")
|
||||
except (KeyError, OSError, ValueError, zipfile.BadZipFile):
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail="Semantic mask failed verification",
|
||||
) from None
|
||||
digest = hashlib.sha256(payload).hexdigest()
|
||||
return Response(
|
||||
content=payload,
|
||||
media_type="image/png",
|
||||
headers={
|
||||
"Cache-Control": "private, max-age=31536000, immutable",
|
||||
"ETag": f'"{digest}"',
|
||||
"X-Content-Type-Options": "nosniff",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _configured_root(provider: RootProvider) -> Path | None:
|
||||
candidate = provider()
|
||||
if candidate is None:
|
||||
|
||||
Reference in New Issue
Block a user