feat(perception): add recorded replay maturation labs
This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
"""Freeze one Mission Core LAB annotation session as an E48 review submission."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46_detector_truth_island import (
|
||||
E46_REFERENCES_NAME,
|
||||
E46DetectorTruthIslandError,
|
||||
read_e46_detector_truth_island,
|
||||
)
|
||||
from k1link.compute.e48_detector_truth_seal import (
|
||||
E48_REVIEW_SCHEMA,
|
||||
E48DetectorTruthSealError,
|
||||
validate_e48_detector_review_submission,
|
||||
)
|
||||
|
||||
E46_LAB_REVIEW_SCHEMA: Final = "missioncore.e46-lab-review-submission/v1"
|
||||
E46_LAB_REVIEW_MANIFEST: Final = "manifest.json"
|
||||
E46_LAB_REVIEW_DOCUMENT: Final = "review-submission.json"
|
||||
_SESSION_SCHEMA: Final = "missioncore.l34-annotation-session/v3"
|
||||
_RESULT_ID = re.compile(r"^e46-lab-review-submission-[a-f0-9]{64}$")
|
||||
_REVIEWER_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{1,63}$")
|
||||
_BLINDNESS: Final = {
|
||||
"candidate_identity_seen": False,
|
||||
"model_prelabels_seen": False,
|
||||
"model_predictions_seen": False,
|
||||
"model_scores_seen": False,
|
||||
}
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46LabReviewSubmissionError(ValueError):
|
||||
"""Raised when an E46 LAB session cannot become an E48 review input."""
|
||||
|
||||
|
||||
def build_e46_lab_review_submission(
|
||||
*,
|
||||
truth_island_root: Path,
|
||||
annotation_session_path: Path,
|
||||
reviewer_id: str,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
try:
|
||||
truth = read_e46_detector_truth_island(truth_island_root)
|
||||
except (E46DetectorTruthIslandError, OSError) as exc:
|
||||
raise E46LabReviewSubmissionError("E46 truth island invalid") from exc
|
||||
reviewer = reviewer_id.strip()
|
||||
if _REVIEWER_ID.fullmatch(reviewer) is None:
|
||||
raise E46LabReviewSubmissionError("reviewer identity invalid")
|
||||
session_path = annotation_session_path.resolve(strict=True)
|
||||
if not session_path.is_file() or session_path.is_symlink():
|
||||
raise E46LabReviewSubmissionError("annotation session unavailable")
|
||||
session = _read_json(session_path)
|
||||
references = tuple(_read_jsonl(truth.result_root / E46_REFERENCES_NAME))
|
||||
_validate_session(session, truth.result_id, references)
|
||||
frames = {int(item["truth_island_sequence"]): item for item in session["frames"]}
|
||||
images: list[dict[str, Any]] = []
|
||||
for reference in references:
|
||||
sequence = int(reference["truth_island_sequence"])
|
||||
frame = frames[sequence]
|
||||
objects: list[dict[str, Any]] = []
|
||||
for item in frame["objects"]:
|
||||
category = item.get("category")
|
||||
if category == "unmapped" or item.get("proposed_label") is not None:
|
||||
raise E46LabReviewSubmissionError("E46 review class invalid")
|
||||
objects.append(
|
||||
{
|
||||
"object_id": item["object_id"],
|
||||
"category": category,
|
||||
"box_xyxy": item["box_xyxy"],
|
||||
"occluded": item["occluded"],
|
||||
"truncated": item["truncated"],
|
||||
"notes": None,
|
||||
}
|
||||
)
|
||||
images.append(
|
||||
{
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": reference["image_id"],
|
||||
"frame_index": reference["frame_index"],
|
||||
"session_seconds": reference["session_seconds"],
|
||||
"role": reference["role"],
|
||||
"group_id": reference["group_id"],
|
||||
"source_path": reference["source_path"],
|
||||
"source_sha256": reference["sha256"],
|
||||
"review_state": "reviewed",
|
||||
"hard_negative": frame["hard_negative"],
|
||||
"objects": objects,
|
||||
"notes": None,
|
||||
}
|
||||
)
|
||||
review = {
|
||||
"schema_version": E48_REVIEW_SCHEMA,
|
||||
"truth_island_id": truth.result_id,
|
||||
"state": "completed-independent-no-model-assistance",
|
||||
"reviewer_id": reviewer,
|
||||
"review_round": 1,
|
||||
"blindness": _BLINDNESS,
|
||||
"images": images,
|
||||
"acceptance": {
|
||||
"all_images_reviewed": True,
|
||||
"independent": True,
|
||||
"submitted_at_utc": session["updated_at_utc"],
|
||||
},
|
||||
}
|
||||
review_sha256 = hashlib.sha256(_canonical_json(review)).hexdigest()
|
||||
identity = {
|
||||
"schema_version": E46_LAB_REVIEW_SCHEMA,
|
||||
"truth_island_id": truth.result_id,
|
||||
"annotation_session_id": session["session_id"],
|
||||
"annotation_session_sha256": _sha256(session_path),
|
||||
"reviewer_id": reviewer,
|
||||
"review_sha256": review_sha256,
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"blindness": _BLINDNESS,
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46-lab-review-submission-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46_lab_review_submission(destination, truth_island_root=truth.result_root)
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700)
|
||||
try:
|
||||
_write_json(staging / E46_LAB_REVIEW_DOCUMENT, review)
|
||||
validate_e48_detector_review_submission(
|
||||
truth_island_root=truth.result_root,
|
||||
review_path=staging / E46_LAB_REVIEW_DOCUMENT,
|
||||
)
|
||||
_write_json(
|
||||
staging / E46_LAB_REVIEW_MANIFEST,
|
||||
{
|
||||
"schema_version": E46_LAB_REVIEW_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": session["updated_at_utc"],
|
||||
"state": "completed-e48-review-input-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
{
|
||||
"path": E46_LAB_REVIEW_DOCUMENT,
|
||||
"role": "e48-review-submission",
|
||||
"byte_length": (staging / E46_LAB_REVIEW_DOCUMENT).stat().st_size,
|
||||
"sha256": _sha256(staging / E46_LAB_REVIEW_DOCUMENT),
|
||||
}
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46_lab_review_submission(destination, truth_island_root=truth.result_root)
|
||||
|
||||
|
||||
def read_e46_lab_review_submission(
|
||||
root: Path,
|
||||
*,
|
||||
truth_island_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / E46_LAB_REVIEW_MANIFEST)
|
||||
identity = manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise E46LabReviewSubmissionError("review identity invalid")
|
||||
digest = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46-lab-review-submission-{digest}"
|
||||
artifact = resolved / E46_LAB_REVIEW_DOCUMENT
|
||||
if (
|
||||
manifest.get("schema_version") != E46_LAB_REVIEW_SCHEMA
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or manifest.get("result_id") != result_id
|
||||
or resolved.name != result_id
|
||||
or _RESULT_ID.fullmatch(result_id) is None
|
||||
or manifest.get("state") != "completed-e48-review-input-not-truth"
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or not artifact.is_file()
|
||||
or artifact.is_symlink()
|
||||
or hashlib.sha256(_canonical_json(_read_json(artifact))).hexdigest()
|
||||
!= identity.get("review_sha256")
|
||||
):
|
||||
raise E46LabReviewSubmissionError("review submission changed")
|
||||
artifacts = manifest.get("artifacts")
|
||||
artifact_row = artifacts[0] if isinstance(artifacts, list) and len(artifacts) == 1 else None
|
||||
if (
|
||||
not isinstance(artifact_row, dict)
|
||||
or artifact_row.get("path") != E46_LAB_REVIEW_DOCUMENT
|
||||
or artifact_row.get("role") != "e48-review-submission"
|
||||
or artifact_row.get("byte_length") != artifact.stat().st_size
|
||||
or artifact_row.get("sha256") != _sha256(artifact)
|
||||
):
|
||||
raise E46LabReviewSubmissionError("review artifact changed")
|
||||
try:
|
||||
review = validate_e48_detector_review_submission(
|
||||
truth_island_root=truth_island_root,
|
||||
review_path=artifact,
|
||||
)
|
||||
except (E48DetectorTruthSealError, OSError) as exc:
|
||||
raise E46LabReviewSubmissionError("E48 review submission invalid") from exc
|
||||
return {
|
||||
"result_id": result_id,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"review": review,
|
||||
}
|
||||
|
||||
|
||||
def _validate_session(
|
||||
session: dict[str, Any],
|
||||
truth_island_id: str,
|
||||
references: tuple[dict[str, Any], ...],
|
||||
) -> None:
|
||||
frames = session.get("frames")
|
||||
if (
|
||||
session.get("schema_version") != _SESSION_SCHEMA
|
||||
or session.get("result_id") != truth_island_id
|
||||
or session.get("truth_island_id") != truth_island_id
|
||||
or session.get("contract_id") != "e46-detector-blind-review/v1"
|
||||
or session.get("state") != "saved"
|
||||
or session.get("blindness") != _BLINDNESS
|
||||
or session.get("assistance")
|
||||
!= {"mode": "prediction-free-manual", "independent_truth_eligible": False}
|
||||
or not isinstance(frames, list)
|
||||
or len(frames) != len(references)
|
||||
):
|
||||
raise E46LabReviewSubmissionError("annotation session incomplete")
|
||||
by_sequence = {int(item["truth_island_sequence"]): item for item in frames}
|
||||
if len(by_sequence) != len(frames):
|
||||
raise E46LabReviewSubmissionError("annotation frame duplicated")
|
||||
for reference in references:
|
||||
frame = by_sequence.get(int(reference["truth_island_sequence"]))
|
||||
if (
|
||||
frame is None
|
||||
or frame.get("reviewed") is not True
|
||||
or frame.get("image_id") != reference["image_id"]
|
||||
or frame.get("frame_index") != reference["frame_index"]
|
||||
or frame.get("source_sha256") != reference["sha256"]
|
||||
or frame.get("hard_negative") != (len(frame.get("objects", [])) == 0)
|
||||
):
|
||||
raise E46LabReviewSubmissionError("annotation source identity changed")
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode()
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise E46LabReviewSubmissionError("expected JSON object")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
if any(not isinstance(row, dict) for row in rows):
|
||||
raise E46LabReviewSubmissionError("expected JSONL objects")
|
||||
return rows
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
@@ -0,0 +1,766 @@
|
||||
"""Freeze an AI-audited engineering preannotation for the 32 E46 frames.
|
||||
|
||||
E46A is deliberately derived from the candidate-visible L3.4F engineering
|
||||
reference. It is useful as an editable starting point and visual evidence,
|
||||
but it is neither an independent review nor ground truth.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46_detector_truth_island import (
|
||||
E46_MANIFEST_NAME,
|
||||
E46_REFERENCES_NAME,
|
||||
read_e46_detector_truth_island,
|
||||
)
|
||||
from k1link.compute.e47_detector_candidate_freeze import (
|
||||
E47_MANIFEST_NAME,
|
||||
E47_PREDICTIONS_NAME,
|
||||
read_e47_detector_candidate_freeze,
|
||||
)
|
||||
from k1link.compute.l34f_adjudicated_reference import (
|
||||
L34F_MANIFEST_NAME,
|
||||
read_l34f_adjudicated_reference,
|
||||
)
|
||||
|
||||
E46A_RESULT_SCHEMA: Final = "missioncore.e46a-ai-engineering-preannotation/v1"
|
||||
E46A_REPORT_SCHEMA: Final = "missioncore.e46a-ai-engineering-preannotation-report/v1"
|
||||
E46A_CASE_SCHEMA: Final = "missioncore.e46a-ai-engineering-preannotation-case/v1"
|
||||
E46A_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46A_REPORT_NAME: Final = "ai-engineering-preannotation-report.json"
|
||||
E46A_CASES_NAME: Final = "ai-engineering-preannotations.jsonl"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46a-ai-engineering-preannotation-[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
_CUSTOM_CLASS_BY_PROPOSED_LABEL: Final = {
|
||||
"Детская коляска": "stroller",
|
||||
"Ноутбук": "laptop",
|
||||
}
|
||||
_VEHICLE_CATEGORIES: Final = frozenset({"car", "heavy_vehicle"})
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class E46AVisualAuditProfile:
|
||||
"""Source-scoped object QA applied to the candidate-visible seed."""
|
||||
|
||||
profile_id: str
|
||||
geometry_candidate_id: str
|
||||
candidate_nms_iou: float
|
||||
maximum_match_cost: float
|
||||
expected_source_object_count: int
|
||||
expected_final_object_count: int
|
||||
expected_geometry_snapped_count: int
|
||||
delete_object_ids: tuple[str, ...]
|
||||
category_overrides: tuple[tuple[str, str], ...]
|
||||
|
||||
def to_dict(self) -> dict[str, object]:
|
||||
return {
|
||||
"profile_id": self.profile_id,
|
||||
"geometry_candidate_id": self.geometry_candidate_id,
|
||||
"candidate_nms_iou": self.candidate_nms_iou,
|
||||
"maximum_match_cost": self.maximum_match_cost,
|
||||
"expected_source_object_count": self.expected_source_object_count,
|
||||
"expected_final_object_count": self.expected_final_object_count,
|
||||
"expected_geometry_snapped_count": (
|
||||
self.expected_geometry_snapped_count
|
||||
),
|
||||
"delete_object_ids": list(self.delete_object_ids),
|
||||
"category_overrides": dict(self.category_overrides),
|
||||
}
|
||||
|
||||
|
||||
RAVNOVES00_E46A_VISUAL_AUDIT_V2: Final = E46AVisualAuditProfile(
|
||||
profile_id="ravnoves00-right-e46a-object-qa/v2",
|
||||
geometry_candidate_id="maskrcnn-kb4-valid-fov-fill",
|
||||
candidate_nms_iou=0.3,
|
||||
maximum_match_cost=1.3,
|
||||
expected_source_object_count=260,
|
||||
expected_final_object_count=245,
|
||||
expected_geometry_snapped_count=217,
|
||||
delete_object_ids=(
|
||||
"self-03-06",
|
||||
"self-04-09",
|
||||
"self-09-05",
|
||||
"self-29-08",
|
||||
"self-29-09",
|
||||
"self-29-10",
|
||||
"self-30-08",
|
||||
"self-30-09",
|
||||
"self-30-10",
|
||||
"self-31-08",
|
||||
"self-31-09",
|
||||
"self-31-10",
|
||||
"self-32-08",
|
||||
"self-32-09",
|
||||
"self-32-10",
|
||||
),
|
||||
category_overrides=(("self-20-06", "car"),),
|
||||
)
|
||||
|
||||
|
||||
class E46AAiEngineeringPreannotationError(ValueError):
|
||||
"""Raised when E46A input binding or immutable output is invalid."""
|
||||
|
||||
|
||||
def build_e46a_ai_engineering_preannotation(
|
||||
*,
|
||||
e46_root: Path,
|
||||
l34f_root: Path,
|
||||
output_root: Path,
|
||||
geometry_candidate_root: Path | None = None,
|
||||
audit_profile: E46AVisualAuditProfile = RAVNOVES00_E46A_VISUAL_AUDIT_V2,
|
||||
) -> dict[str, Any]:
|
||||
"""Build a path-free, hash-bound 32-frame engineering preannotation."""
|
||||
|
||||
e46 = read_e46_detector_truth_island(e46_root)
|
||||
l34f = read_l34f_adjudicated_reference(l34f_root)
|
||||
e46_references = tuple(_read_jsonl(e46.result_root / E46_REFERENCES_NAME))
|
||||
if len(e46_references) != 32 or len(l34f["cases"]) != 32:
|
||||
raise E46AAiEngineeringPreannotationError("E46A requires exactly 32 frames")
|
||||
e46_by_sequence = {
|
||||
int(row["truth_island_sequence"]): row for row in e46_references
|
||||
}
|
||||
cases: list[dict[str, Any]] = []
|
||||
relabeled_count = 0
|
||||
for source in sorted(
|
||||
l34f["cases"], key=lambda row: int(row["truth_island_sequence"])
|
||||
):
|
||||
sequence = int(source["truth_island_sequence"])
|
||||
e46_reference = e46_by_sequence.get(sequence)
|
||||
if e46_reference is None or any(
|
||||
source.get(key) != e46_reference.get(target)
|
||||
for key, target in (
|
||||
("image_id", "image_id"),
|
||||
("frame_index", "frame_index"),
|
||||
("group_id", "group_id"),
|
||||
("source_image_sha256", "sha256"),
|
||||
)
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
f"L3.4F frame {sequence} is not bound to E46"
|
||||
)
|
||||
objects: list[dict[str, Any]] = []
|
||||
for raw in source.get("references", []):
|
||||
if not isinstance(raw, dict):
|
||||
raise E46AAiEngineeringPreannotationError("E46A object is invalid")
|
||||
item = copy.deepcopy(raw)
|
||||
category = item.get("category")
|
||||
if category == "unmapped":
|
||||
category = _CUSTOM_CLASS_BY_PROPOSED_LABEL.get(
|
||||
str(item.get("proposed_label"))
|
||||
)
|
||||
if category is None:
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A contains an unresolved custom class"
|
||||
)
|
||||
relabeled_count += 1
|
||||
if not isinstance(category, str) or not category:
|
||||
raise E46AAiEngineeringPreannotationError("E46A class is invalid")
|
||||
item["category"] = category
|
||||
item["origin"] = "ai_engineering_preannotation"
|
||||
item["source_origin"] = raw.get("origin")
|
||||
objects.append(item)
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": E46A_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": int(source["image_id"]),
|
||||
"frame_index": int(source["frame_index"]),
|
||||
"group_id": str(source["group_id"]),
|
||||
"session_seconds": float(source["session_seconds"]),
|
||||
"source_image_sha256": str(source["source_image_sha256"]),
|
||||
"objects": objects,
|
||||
"object_count": len(objects),
|
||||
"hard_negative": len(objects) == 0,
|
||||
"visual_audit_state": "derived-reference-not-object-audited",
|
||||
}
|
||||
)
|
||||
|
||||
geometry_audit: dict[str, Any] | None = None
|
||||
geometry_candidate: dict[str, Any] | None = None
|
||||
if geometry_candidate_root is not None:
|
||||
geometry_candidate = read_e47_detector_candidate_freeze(
|
||||
geometry_candidate_root
|
||||
)
|
||||
truth_island = geometry_candidate["manifest"]["identity"].get(
|
||||
"truth_island"
|
||||
)
|
||||
if (
|
||||
not isinstance(truth_island, dict)
|
||||
or truth_island.get("result_id") != e46.result_id
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A geometry candidate is not bound to E46"
|
||||
)
|
||||
prediction_rows = tuple(
|
||||
_read_jsonl(
|
||||
geometry_candidate["result_root"] / E47_PREDICTIONS_NAME
|
||||
)
|
||||
)
|
||||
geometry_audit = _apply_visual_audit(
|
||||
cases=cases,
|
||||
prediction_rows=prediction_rows,
|
||||
profile=audit_profile,
|
||||
)
|
||||
|
||||
class_counts = Counter(
|
||||
str(item["category"])
|
||||
for case in cases
|
||||
for item in case["objects"]
|
||||
)
|
||||
source_object_count = (
|
||||
int(geometry_audit["source_object_count"])
|
||||
if geometry_audit is not None
|
||||
else sum(len(case["objects"]) for case in cases)
|
||||
)
|
||||
metrics = {
|
||||
"frame_count": len(cases),
|
||||
"reviewed_frame_count": len(cases) if geometry_audit is not None else 0,
|
||||
"source_object_count": source_object_count,
|
||||
"object_count": sum(len(case["objects"]) for case in cases),
|
||||
"custom_class_relabel_count": relabeled_count,
|
||||
"hard_negative_frame_count": sum(bool(case["hard_negative"]) for case in cases),
|
||||
"class_counts": dict(sorted(class_counts.items())),
|
||||
"independent_review_submission_count": 0,
|
||||
}
|
||||
if geometry_audit is not None:
|
||||
metrics.update(
|
||||
{
|
||||
"deleted_false_box_count": geometry_audit[
|
||||
"deleted_false_box_count"
|
||||
],
|
||||
"geometry_snapped_object_count": geometry_audit[
|
||||
"geometry_snapped_object_count"
|
||||
],
|
||||
"source_geometry_retained_object_count": geometry_audit[
|
||||
"source_geometry_retained_object_count"
|
||||
],
|
||||
"category_corrected_object_count": geometry_audit[
|
||||
"category_corrected_object_count"
|
||||
],
|
||||
}
|
||||
)
|
||||
visual_review_complete = geometry_audit is not None
|
||||
report_basis = {
|
||||
"schema_version": E46A_REPORT_SCHEMA,
|
||||
"status": "completed-ai-engineering-preannotation-not-independent-not-truth",
|
||||
"metrics": metrics,
|
||||
"assistance": {
|
||||
"candidate_identity_seen": True,
|
||||
"model_predictions_seen": True,
|
||||
"model_scores_seen": geometry_audit is not None,
|
||||
"derived_reference_seen": True,
|
||||
"independent_truth_eligible": False,
|
||||
},
|
||||
"object_level_audit": copy.deepcopy(geometry_audit),
|
||||
"taxonomy": {
|
||||
"classes": sorted(class_counts),
|
||||
"custom_classes": ["laptop", "stroller"],
|
||||
"unresolved_class_count": 0,
|
||||
},
|
||||
"decision": {
|
||||
"preannotation_available": True,
|
||||
"visual_review_complete": visual_review_complete,
|
||||
"independent_review_count_affected": False,
|
||||
"e48_truth_seal_open": False,
|
||||
"l35_acceptance_open": False,
|
||||
"next_action": (
|
||||
"use E46A only for assisted correction/approval; keep Reviewer A and "
|
||||
"Reviewer B prediction-free and independent"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
(
|
||||
"the labels derive from a candidate-visible engineering reference; "
|
||||
"the frozen Mask R-CNN candidate is used only to refine geometry"
|
||||
if geometry_audit is not None
|
||||
else "the derived reference has not completed object-level visual QA"
|
||||
),
|
||||
(
|
||||
"E46A is an assisted preannotation and cannot occupy either "
|
||||
"independent E46 reviewer slot"
|
||||
),
|
||||
(
|
||||
"no AP, recall, miss, false-positive, detector acceptance, live, "
|
||||
"LiDAR-range, command, navigation, or safety claim is made"
|
||||
),
|
||||
(
|
||||
"the evidence is limited to recorded sensor.camera.right frames "
|
||||
"from one RAVNOVES00 route"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46A_RESULT_SCHEMA,
|
||||
"e46_source": {
|
||||
"result_id": e46.result_id,
|
||||
"manifest_sha256": _sha256(e46.result_root / E46_MANIFEST_NAME),
|
||||
},
|
||||
"l34f_engineering_reference": {
|
||||
"result_id": l34f["result_id"],
|
||||
"manifest_sha256": _sha256(l34f["result_root"] / L34F_MANIFEST_NAME),
|
||||
},
|
||||
"audit_profile": (
|
||||
audit_profile.to_dict()
|
||||
if geometry_audit is not None
|
||||
else "derived-reference-no-object-level-audit/v1"
|
||||
),
|
||||
"custom_class_mapping": _CUSTOM_CLASS_BY_PROPOSED_LABEL,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
if geometry_candidate is not None:
|
||||
identity["e47_geometry_candidate"] = {
|
||||
"result_id": geometry_candidate["result_id"],
|
||||
"manifest_sha256": _sha256(
|
||||
geometry_candidate["result_root"] / E47_MANIFEST_NAME
|
||||
),
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46a-ai-engineering-preannotation-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46a_ai_engineering_preannotation(destination)
|
||||
created_at_utc = _utc_now()
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46A_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46A_CASES_NAME, cases)
|
||||
artifacts = [
|
||||
_artifact(staging / E46A_REPORT_NAME, "preannotation-report"),
|
||||
_artifact(staging / E46A_CASES_NAME, "preannotation-cases"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46A_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46A_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "ai-engineering-preannotation-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46a_ai_engineering_preannotation(destination)
|
||||
|
||||
|
||||
def read_e46a_ai_engineering_preannotation(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / E46A_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise E46AAiEngineeringPreannotationError("E46A identity is invalid")
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if (
|
||||
manifest.get("schema_version") != E46A_RESULT_SCHEMA
|
||||
or manifest.get("identity_sha256") != identity_sha256
|
||||
or manifest.get("result_id")
|
||||
!= f"e46a-ai-engineering-preannotation-{identity_sha256}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("acceptance_state")
|
||||
!= "ai-engineering-preannotation-not-truth"
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError("E46A identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise E46AAiEngineeringPreannotationError("E46A artifacts are invalid")
|
||||
by_role = {item.get("role"): item for item in artifacts if isinstance(item, dict)}
|
||||
report = _read_json(_validated_artifact(resolved, by_role.get("preannotation-report")))
|
||||
cases = tuple(_read_jsonl(_validated_artifact(resolved, by_role.get("preannotation-cases"))))
|
||||
if (
|
||||
report.get("schema_version") != E46A_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("status")
|
||||
!= "completed-ai-engineering-preannotation-not-independent-not-truth"
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != E46A_CASE_SCHEMA for case in cases)
|
||||
or any(
|
||||
item.get("category") == "unmapped"
|
||||
for case in cases
|
||||
for item in case.get("objects", [])
|
||||
if isinstance(item, dict)
|
||||
)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest()
|
||||
!= identity.get("cases_sha256")
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError("E46A result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _apply_visual_audit(
|
||||
*,
|
||||
cases: list[dict[str, Any]],
|
||||
prediction_rows: tuple[dict[str, Any], ...],
|
||||
profile: E46AVisualAuditProfile,
|
||||
) -> dict[str, Any]:
|
||||
source_object_ids = {
|
||||
str(item.get("object_id"))
|
||||
for case in cases
|
||||
for item in case.get("objects", [])
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
source_object_count = sum(len(case.get("objects", [])) for case in cases)
|
||||
delete_object_ids = set(profile.delete_object_ids)
|
||||
category_overrides = dict(profile.category_overrides)
|
||||
if (
|
||||
source_object_count != profile.expected_source_object_count
|
||||
or len(source_object_ids) != source_object_count
|
||||
or not delete_object_ids <= source_object_ids
|
||||
or not set(category_overrides) <= source_object_ids - delete_object_ids
|
||||
or not 0.0 < profile.candidate_nms_iou < 1.0
|
||||
or profile.maximum_match_cost <= 0.0
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A visual-audit profile is not bound to the source objects"
|
||||
)
|
||||
|
||||
rows = {
|
||||
int(row["truth_island_sequence"]): row
|
||||
for row in prediction_rows
|
||||
if row.get("candidate_id") == profile.geometry_candidate_id
|
||||
}
|
||||
if set(rows) != set(range(1, 33)):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A geometry candidate coverage is incomplete"
|
||||
)
|
||||
|
||||
snapped_count = 0
|
||||
retained_count = 0
|
||||
category_corrections: list[dict[str, str]] = []
|
||||
for case in cases:
|
||||
sequence = int(case["truth_island_sequence"])
|
||||
row = rows[sequence]
|
||||
if row.get("source_image_sha256") != case.get("source_image_sha256"):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
f"E46A geometry frame {sequence} changed"
|
||||
)
|
||||
raw_objects = case.get("objects")
|
||||
raw_predictions = row.get("predictions")
|
||||
if not isinstance(raw_objects, list) or not isinstance(raw_predictions, list):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A visual-audit payload is invalid"
|
||||
)
|
||||
objects = [
|
||||
item
|
||||
for item in raw_objects
|
||||
if str(item.get("object_id")) not in delete_object_ids
|
||||
]
|
||||
for item in objects:
|
||||
object_id = str(item["object_id"])
|
||||
override = category_overrides.get(object_id)
|
||||
if override is not None and override != item.get("category"):
|
||||
category_corrections.append(
|
||||
{
|
||||
"object_id": object_id,
|
||||
"before": str(item.get("category")),
|
||||
"after": override,
|
||||
}
|
||||
)
|
||||
item["source_category"] = item.get("category")
|
||||
item["category"] = override
|
||||
|
||||
predictions = _candidate_nms(
|
||||
tuple(_validated_prediction(item) for item in raw_predictions),
|
||||
threshold=profile.candidate_nms_iou,
|
||||
)
|
||||
pairs: list[tuple[float, int, int]] = []
|
||||
for object_index, item in enumerate(objects):
|
||||
category = str(item["category"])
|
||||
if category in {"stroller", "laptop"}:
|
||||
continue
|
||||
source_box = _box(item.get("box_xyxy"))
|
||||
source_center = _center(source_box)
|
||||
source_diagonal = max(
|
||||
12.0,
|
||||
math.hypot(
|
||||
source_box[2] - source_box[0],
|
||||
source_box[3] - source_box[1],
|
||||
),
|
||||
)
|
||||
for prediction_index, prediction in enumerate(predictions):
|
||||
if _category_group(category) != _category_group(
|
||||
str(prediction["category"])
|
||||
):
|
||||
continue
|
||||
candidate_box = _box(prediction["box_xyxy"])
|
||||
candidate_center = _center(candidate_box)
|
||||
normalized_distance = math.hypot(
|
||||
source_center[0] - candidate_center[0],
|
||||
source_center[1] - candidate_center[1],
|
||||
) / source_diagonal
|
||||
overlap = _iou(source_box, candidate_box)
|
||||
if normalized_distance > 1.3 and overlap < 0.05:
|
||||
continue
|
||||
area_ratio = abs(
|
||||
math.log(
|
||||
max(_area(candidate_box), 1.0)
|
||||
/ max(_area(source_box), 1.0)
|
||||
)
|
||||
)
|
||||
cost = (
|
||||
normalized_distance
|
||||
+ 0.12 * area_ratio
|
||||
- 0.35 * overlap
|
||||
- 0.03 * float(prediction["score"])
|
||||
)
|
||||
pairs.append((cost, object_index, prediction_index))
|
||||
|
||||
used_objects: set[int] = set()
|
||||
used_predictions: set[int] = set()
|
||||
for cost, object_index, prediction_index in sorted(pairs):
|
||||
if (
|
||||
cost > profile.maximum_match_cost
|
||||
or object_index in used_objects
|
||||
or prediction_index in used_predictions
|
||||
):
|
||||
continue
|
||||
used_objects.add(object_index)
|
||||
used_predictions.add(prediction_index)
|
||||
item = objects[object_index]
|
||||
prediction = predictions[prediction_index]
|
||||
source_box = _box(item["box_xyxy"])
|
||||
item["source_box_xyxy"] = list(source_box)
|
||||
item["box_xyxy"] = [
|
||||
round(value, 3) for value in _box(prediction["box_xyxy"])
|
||||
]
|
||||
item["geometry_origin"] = "maskrcnn_valid_fov_visual_snap"
|
||||
item["geometry_candidate_id"] = profile.geometry_candidate_id
|
||||
item["geometry_candidate_score"] = round(
|
||||
float(prediction["score"]), 9
|
||||
)
|
||||
snapped_count += 1
|
||||
|
||||
for object_index, item in enumerate(objects):
|
||||
if object_index not in used_objects:
|
||||
item["geometry_origin"] = "l34f_engineering_reference_retained"
|
||||
retained_count += 1
|
||||
case["objects"] = objects
|
||||
case["object_count"] = len(objects)
|
||||
case["hard_negative"] = len(objects) == 0
|
||||
case["visual_audit_state"] = "ai-object-level-audited-v2"
|
||||
|
||||
final_object_count = sum(len(case["objects"]) for case in cases)
|
||||
if (
|
||||
final_object_count != profile.expected_final_object_count
|
||||
or snapped_count != profile.expected_geometry_snapped_count
|
||||
or snapped_count + retained_count != final_object_count
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A visual-audit result does not match the frozen profile"
|
||||
)
|
||||
return {
|
||||
"profile_id": profile.profile_id,
|
||||
"geometry_candidate_id": profile.geometry_candidate_id,
|
||||
"source_object_count": source_object_count,
|
||||
"deleted_false_box_count": len(delete_object_ids),
|
||||
"deleted_false_box_ids": sorted(delete_object_ids),
|
||||
"geometry_snapped_object_count": snapped_count,
|
||||
"source_geometry_retained_object_count": retained_count,
|
||||
"category_corrected_object_count": len(category_corrections),
|
||||
"category_corrections": category_corrections,
|
||||
"reviewed_frame_count": len(cases),
|
||||
"ground_truth": False,
|
||||
}
|
||||
|
||||
|
||||
def _validated_prediction(raw: object) -> dict[str, Any]:
|
||||
if not isinstance(raw, dict):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A geometry candidate row is invalid"
|
||||
)
|
||||
category = raw.get("category")
|
||||
score = raw.get("score")
|
||||
box = _box(raw.get("box_xyxy"))
|
||||
if (
|
||||
not isinstance(category, str)
|
||||
or not category
|
||||
or not isinstance(score, (int, float))
|
||||
or isinstance(score, bool)
|
||||
or not math.isfinite(float(score))
|
||||
or not 0.0 <= float(score) <= 1.0
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError(
|
||||
"E46A geometry candidate row is invalid"
|
||||
)
|
||||
return {"category": category, "score": float(score), "box_xyxy": box}
|
||||
|
||||
|
||||
def _candidate_nms(
|
||||
predictions: tuple[dict[str, Any], ...], *, threshold: float
|
||||
) -> tuple[dict[str, Any], ...]:
|
||||
kept: list[dict[str, Any]] = []
|
||||
for prediction in sorted(
|
||||
predictions, key=lambda item: float(item["score"]), reverse=True
|
||||
):
|
||||
if all(
|
||||
_category_group(str(prediction["category"]))
|
||||
!= _category_group(str(other["category"]))
|
||||
or _iou(
|
||||
_box(prediction["box_xyxy"]),
|
||||
_box(other["box_xyxy"]),
|
||||
)
|
||||
< threshold
|
||||
for other in kept
|
||||
):
|
||||
kept.append(prediction)
|
||||
return tuple(kept)
|
||||
|
||||
|
||||
def _category_group(category: str) -> str:
|
||||
return "vehicle" if category in _VEHICLE_CATEGORIES else category
|
||||
|
||||
|
||||
def _box(raw: object) -> tuple[float, float, float, float]:
|
||||
if (
|
||||
not isinstance(raw, (list, tuple))
|
||||
or len(raw) != 4
|
||||
or any(
|
||||
not isinstance(value, (int, float))
|
||||
or isinstance(value, bool)
|
||||
or not math.isfinite(float(value))
|
||||
for value in raw
|
||||
)
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError("E46A box is invalid")
|
||||
box = tuple(float(value) for value in raw)
|
||||
if (
|
||||
box[0] < 0.0
|
||||
or box[1] < 0.0
|
||||
or box[2] > 800.0
|
||||
or box[3] > 600.0
|
||||
or box[2] <= box[0]
|
||||
or box[3] <= box[1]
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError("E46A box is invalid")
|
||||
return box
|
||||
|
||||
|
||||
def _area(box: tuple[float, float, float, float]) -> float:
|
||||
return (box[2] - box[0]) * (box[3] - box[1])
|
||||
|
||||
|
||||
def _center(box: tuple[float, float, float, float]) -> tuple[float, float]:
|
||||
return ((box[0] + box[2]) / 2.0, (box[1] + box[3]) / 2.0)
|
||||
|
||||
|
||||
def _iou(
|
||||
first: tuple[float, float, float, float],
|
||||
second: tuple[float, float, float, float],
|
||||
) -> float:
|
||||
width = max(0.0, min(first[2], second[2]) - max(first[0], second[0]))
|
||||
height = max(0.0, min(first[3], second[3]) - max(first[1], second[1]))
|
||||
intersection = width * height
|
||||
if intersection == 0.0:
|
||||
return 0.0
|
||||
return intersection / (_area(first) + _area(second) - intersection)
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, Any]:
|
||||
return {
|
||||
"role": role,
|
||||
"path": path.name,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, raw: object) -> Path:
|
||||
if not isinstance(raw, dict) or not isinstance(raw.get("path"), str):
|
||||
raise E46AAiEngineeringPreannotationError("E46A artifact is invalid")
|
||||
path = (root / str(raw["path"])).resolve(strict=True)
|
||||
if (
|
||||
path.parent != root
|
||||
or path.is_symlink()
|
||||
or path.stat().st_size != raw.get("byte_length")
|
||||
or _sha256(path) != raw.get("sha256")
|
||||
):
|
||||
raise E46AAiEngineeringPreannotationError("E46A artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise E46AAiEngineeringPreannotationError("E46A JSON object expected")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
values = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
if any(not isinstance(value, dict) for value in values):
|
||||
raise E46AAiEngineeringPreannotationError("E46A JSONL object expected")
|
||||
return values
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, values: list[dict[str, Any]]) -> None:
|
||||
path.write_bytes(b"".join(_canonical_json(value) + b"\n" for value in values))
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(UTC).isoformat().replace("+00:00", "Z")
|
||||
@@ -0,0 +1,526 @@
|
||||
"""Freeze source-scoped temporal tracks and conservative motion state for E46A."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter, defaultdict
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46a_ai_engineering_preannotation import (
|
||||
E46A_MANIFEST_NAME,
|
||||
read_e46a_ai_engineering_preannotation,
|
||||
)
|
||||
|
||||
E46B_RESULT_SCHEMA: Final = "missioncore.e46b-temporal-motion/v1"
|
||||
E46B_REPORT_SCHEMA: Final = "missioncore.e46b-temporal-motion-report/v1"
|
||||
E46B_CASE_SCHEMA: Final = "missioncore.e46b-temporal-motion-case/v1"
|
||||
E46B_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46B_REPORT_NAME: Final = "temporal-motion-report.json"
|
||||
E46B_CASES_NAME: Final = "temporal-motion-cases.jsonl"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46b-temporal-motion-[a-f0-9]{64}$")
|
||||
_TEMPORAL_GROUPS: Final = (
|
||||
"clip-stroller-person",
|
||||
"clip-close-car",
|
||||
"clip-vehicle-occlusion",
|
||||
"clip-near-structure",
|
||||
)
|
||||
_EXPECTED_COUNTS: Final = {
|
||||
"clip-stroller-person": {"person": 2, "stroller": 1, "car": 6},
|
||||
"clip-close-car": {"car": 9, "heavy_vehicle": 1},
|
||||
"clip-vehicle-occlusion": {"car": 6, "heavy_vehicle": 1},
|
||||
"clip-near-structure": {"car": 7, "laptop": 1},
|
||||
}
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46BTemporalMotionError(ValueError):
|
||||
"""Raised when an E46B input binding or immutable output is invalid."""
|
||||
|
||||
|
||||
def build_e46b_temporal_motion(
|
||||
*,
|
||||
e46a_root: Path,
|
||||
e26_root: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Build 16 source-scoped temporal frames with stable IDs and motion evidence."""
|
||||
|
||||
e46a = read_e46a_ai_engineering_preannotation(e46a_root)
|
||||
source_cases = [
|
||||
copy.deepcopy(case)
|
||||
for case in e46a["cases"]
|
||||
if case.get("group_id") in _TEMPORAL_GROUPS
|
||||
]
|
||||
source_cases.sort(key=lambda case: int(case["truth_island_sequence"]))
|
||||
_validate_source_cases(source_cases)
|
||||
target_frames = {int(case["frame_index"]) for case in source_cases}
|
||||
e26 = _read_e26_motion_source(e26_root, target_frames)
|
||||
|
||||
track_observations: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for group_id in _TEMPORAL_GROUPS:
|
||||
group_cases = [case for case in source_cases if case["group_id"] == group_id]
|
||||
for case in group_cases:
|
||||
by_category: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for item in case["objects"]:
|
||||
by_category[str(item["category"])].append(item)
|
||||
for category, objects in by_category.items():
|
||||
objects.sort(key=lambda item: _center(item["box_xyxy"])[0])
|
||||
for rank, item in enumerate(objects, start=1):
|
||||
track_id = f"{group_id}/{category}-{rank:02d}"
|
||||
item["track_id"] = track_id
|
||||
item["track_index"] = rank
|
||||
track_observations[track_id].append(
|
||||
{
|
||||
"sequence": int(case["truth_island_sequence"]),
|
||||
"frame_index": int(case["frame_index"]),
|
||||
"center_xy": list(_center(item["box_xyxy"])),
|
||||
"object": item,
|
||||
}
|
||||
)
|
||||
|
||||
matched_total = 0
|
||||
for case in source_cases:
|
||||
motion_objects = e26["frames"][int(case["frame_index"])]
|
||||
associations = _associate(case["objects"], motion_objects)
|
||||
for item in case["objects"]:
|
||||
match = associations.get(str(item["object_id"]))
|
||||
if match is None:
|
||||
item["motion_observation"] = None
|
||||
continue
|
||||
matched_total += 1
|
||||
item["motion_observation"] = {
|
||||
"source_track_id": match.get("source_track_id"),
|
||||
"track_id": match.get("track_id"),
|
||||
"motion_state": match.get("motion_state"),
|
||||
"motion_confidence": match.get("motion_confidence"),
|
||||
"motion_status": match.get("motion_status"),
|
||||
"camera_motion_state": match.get("camera_motion_state"),
|
||||
"lidar_motion_state": match.get("lidar_motion_state"),
|
||||
"bbox_xyxy": copy.deepcopy(match.get("bbox_xyxy")),
|
||||
}
|
||||
|
||||
track_summaries: dict[str, dict[str, Any]] = {}
|
||||
for track_id, observations in track_observations.items():
|
||||
decisive = [
|
||||
item["object"]["motion_observation"]
|
||||
for item in observations
|
||||
if isinstance(item["object"].get("motion_observation"), dict)
|
||||
and item["object"]["motion_observation"].get("motion_state")
|
||||
in {"static", "dynamic"}
|
||||
]
|
||||
states = {str(item["motion_state"]) for item in decisive}
|
||||
state = next(iter(states)) if len(states) == 1 else "unknown"
|
||||
confidence = (
|
||||
round(sum(float(item["motion_confidence"]) for item in decisive) / len(decisive), 6)
|
||||
if state != "unknown" and decisive
|
||||
else 0.0
|
||||
)
|
||||
track_summaries[track_id] = {
|
||||
"track_id": track_id,
|
||||
"category": str(observations[0]["object"]["category"]),
|
||||
"motion_state": state,
|
||||
"motion_confidence": confidence,
|
||||
"motion_evidence_observation_count": len(decisive),
|
||||
"observation_count": len(observations),
|
||||
"source_track_ids": sorted(
|
||||
{
|
||||
int(item["source_track_id"])
|
||||
for item in decisive
|
||||
if isinstance(item.get("source_track_id"), int)
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
for source in source_cases:
|
||||
sequence = int(source["truth_island_sequence"])
|
||||
objects: list[dict[str, Any]] = []
|
||||
for raw in source["objects"]:
|
||||
item = copy.deepcopy(raw)
|
||||
summary = track_summaries[str(item["track_id"])]
|
||||
history = [
|
||||
observation["center_xy"]
|
||||
for observation in track_observations[str(item["track_id"])]
|
||||
if int(observation["sequence"]) <= sequence
|
||||
]
|
||||
item.update(
|
||||
{
|
||||
"motion_state": summary["motion_state"],
|
||||
"motion_confidence": summary["motion_confidence"],
|
||||
"motion_evidence_observation_count": summary[
|
||||
"motion_evidence_observation_count"
|
||||
],
|
||||
"trail_centers_xy": history,
|
||||
}
|
||||
)
|
||||
objects.append(item)
|
||||
frame_counts = Counter(str(item["motion_state"]) for item in objects)
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": E46B_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": int(source["image_id"]),
|
||||
"frame_index": int(source["frame_index"]),
|
||||
"group_id": str(source["group_id"]),
|
||||
"session_seconds": float(source["session_seconds"]),
|
||||
"source_image_sha256": str(source["source_image_sha256"]),
|
||||
"objects": objects,
|
||||
"object_count": len(objects),
|
||||
"track_count": len(objects),
|
||||
"motion_counts": {
|
||||
key: int(frame_counts.get(key, 0))
|
||||
for key in ("dynamic", "static", "unknown")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
track_counts = Counter(
|
||||
str(summary["motion_state"]) for summary in track_summaries.values()
|
||||
)
|
||||
metrics = {
|
||||
"frame_count": len(cases),
|
||||
"temporal_group_count": len(_TEMPORAL_GROUPS),
|
||||
"object_observation_count": sum(len(case["objects"]) for case in cases),
|
||||
"track_count": len(track_summaries),
|
||||
"matched_motion_observation_count": matched_total,
|
||||
"unmatched_motion_observation_count": 136 - matched_total,
|
||||
"dynamic_track_count": int(track_counts.get("dynamic", 0)),
|
||||
"static_track_count": int(track_counts.get("static", 0)),
|
||||
"unknown_track_count": int(track_counts.get("unknown", 0)),
|
||||
"visually_reviewed_frame_count": 16,
|
||||
}
|
||||
if metrics["object_observation_count"] != 136 or metrics["track_count"] != 34:
|
||||
raise E46BTemporalMotionError("E46B source-scoped accounting changed")
|
||||
|
||||
report_basis = {
|
||||
"schema_version": E46B_REPORT_SCHEMA,
|
||||
"status": "completed-source-scoped-temporal-motion-engineering-evidence",
|
||||
"metrics": metrics,
|
||||
"tracks": [track_summaries[key] for key in sorted(track_summaries)],
|
||||
"decision": {
|
||||
"stable_ids_available": True,
|
||||
"motion_state_available": True,
|
||||
"metric_velocity_available": False,
|
||||
"next_action": (
|
||||
"use the 34 tracks as the recorded RIGHT-camera temporal object "
|
||||
"layer; keep unknown where E26 has no decisive evidence"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
(
|
||||
"track IDs are source-scoped to four fixed four-frame clips and "
|
||||
"are not route-global identities"
|
||||
),
|
||||
(
|
||||
"motion state is inherited from accepted E26 KB4 ego-motion/LiDAR "
|
||||
"engineering evidence; unmatched or conflicting evidence remains unknown"
|
||||
),
|
||||
(
|
||||
"camera-only motion has no metric velocity and no AP, live, command, "
|
||||
"navigation, or safety claim is made"
|
||||
),
|
||||
(
|
||||
"E46A is candidate-visible assisted engineering material and is not "
|
||||
"independent ground truth"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46B_RESULT_SCHEMA,
|
||||
"e46a_source": {
|
||||
"result_id": e46a["result_id"],
|
||||
"manifest_sha256": _sha256(e46a["result_root"] / E46A_MANIFEST_NAME),
|
||||
},
|
||||
"e46_source": copy.deepcopy(e46a["manifest"]["identity"]["e46_source"]),
|
||||
"e26_motion_source": e26["identity"],
|
||||
"association_profile": {
|
||||
"profile_id": "e46b-source-scoped-category-x-rank-plus-e26-bbox/v1",
|
||||
"track_scope": "four-frame-group",
|
||||
"motion_conflict_state": "unknown",
|
||||
"maximum_match_cost": 1.4,
|
||||
},
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46b-temporal-motion-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46b_temporal_motion(destination)
|
||||
created_at_utc = datetime.now(UTC).isoformat().replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46B_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46B_CASES_NAME, cases)
|
||||
_write_json(
|
||||
staging / E46B_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46B_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "source-scoped-temporal-motion-engineering-evidence",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / E46B_REPORT_NAME, "temporal-motion-report"),
|
||||
_artifact(staging / E46B_CASES_NAME, "temporal-motion-cases"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46b_temporal_motion(destination)
|
||||
|
||||
|
||||
def read_e46b_temporal_motion(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / E46B_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise E46BTemporalMotionError("E46B identity is invalid")
|
||||
digest = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if (
|
||||
manifest.get("schema_version") != E46B_RESULT_SCHEMA
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or manifest.get("result_id") != f"e46b-temporal-motion-{digest}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46BTemporalMotionError("E46B identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise E46BTemporalMotionError("E46B artifacts are invalid")
|
||||
by_role = {item.get("role"): item for item in artifacts if isinstance(item, dict)}
|
||||
report = _read_json(_validated_artifact(resolved, by_role.get("temporal-motion-report")))
|
||||
cases = tuple(_read_jsonl(_validated_artifact(resolved, by_role.get("temporal-motion-cases"))))
|
||||
if (
|
||||
report.get("schema_version") != E46B_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or len(cases) != 16
|
||||
or any(case.get("schema_version") != E46B_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest() != identity.get("cases_sha256")
|
||||
):
|
||||
raise E46BTemporalMotionError("E46B result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _validate_source_cases(cases: list[dict[str, Any]]) -> None:
|
||||
if len(cases) != 16:
|
||||
raise E46BTemporalMotionError("E46B requires exactly 16 temporal frames")
|
||||
for group_id in _TEMPORAL_GROUPS:
|
||||
selected = [case for case in cases if case.get("group_id") == group_id]
|
||||
if len(selected) != 4:
|
||||
raise E46BTemporalMotionError(f"E46B group {group_id} changed")
|
||||
for case in selected:
|
||||
counts = Counter(str(item.get("category")) for item in case.get("objects", []))
|
||||
if dict(counts) != _EXPECTED_COUNTS[group_id]:
|
||||
raise E46BTemporalMotionError(f"E46B object accounting changed in {group_id}")
|
||||
|
||||
|
||||
def _read_e26_motion_source(root: Path, target_frames: set[int]) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
result = _read_json(resolved / "result.json")
|
||||
identity = result.get("identity")
|
||||
if (
|
||||
result.get("schema_version") != "missioncore.e10-integrated-perception-result/v1"
|
||||
or not isinstance(identity, dict)
|
||||
or identity.get("source_id") != "sensor.camera.right"
|
||||
or identity.get("configuration", {}).get("pipeline")
|
||||
!= "kb4-multiview-static-hypothesis-lidar-fusion/v1"
|
||||
or result.get("acceptance_state") != "accepted"
|
||||
):
|
||||
raise E46BTemporalMotionError("E26 motion source contract is invalid")
|
||||
artifact = next(
|
||||
(
|
||||
item
|
||||
for item in result.get("artifacts", [])
|
||||
if isinstance(item, dict) and item.get("path") == "fusion-frames.jsonl"
|
||||
),
|
||||
None,
|
||||
)
|
||||
if not isinstance(artifact, dict):
|
||||
raise E46BTemporalMotionError("E26 fusion artifact is missing")
|
||||
path = _validated_artifact(resolved, artifact)
|
||||
frames: dict[int, list[dict[str, Any]]] = {}
|
||||
for row in _read_jsonl(path):
|
||||
frame_index = row.get("source_frame_index")
|
||||
if frame_index in target_frames:
|
||||
objects = row.get("objects")
|
||||
if not isinstance(objects, list):
|
||||
raise E46BTemporalMotionError("E26 fusion objects are invalid")
|
||||
frames[int(frame_index)] = objects
|
||||
if set(frames) != target_frames:
|
||||
raise E46BTemporalMotionError("E26 temporal coverage is incomplete")
|
||||
return {
|
||||
"frames": frames,
|
||||
"identity": {
|
||||
"result_id": result.get("result_id"),
|
||||
"result_sha256": _sha256(resolved / "result.json"),
|
||||
"fusion_frames_sha256": artifact.get("sha256"),
|
||||
"pipeline": identity["configuration"]["pipeline"],
|
||||
"profile_sha256": identity["configuration"].get("profile_sha256"),
|
||||
"implementation_sha256": identity["configuration"].get("implementation_sha256"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _associate(
|
||||
source: list[dict[str, Any]],
|
||||
candidates: list[dict[str, Any]],
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
pairs: list[tuple[float, str, int]] = []
|
||||
for item in source:
|
||||
group = (
|
||||
"vehicle"
|
||||
if item.get("category") in {"car", "heavy_vehicle"}
|
||||
else item.get("category")
|
||||
)
|
||||
for index, candidate in enumerate(candidates):
|
||||
if candidate.get("association_group") != group:
|
||||
continue
|
||||
box = candidate.get("bbox_xyxy")
|
||||
if not isinstance(box, list) or len(box) != 4:
|
||||
continue
|
||||
source_box = item["box_xyxy"]
|
||||
sx, sy = _center(source_box)
|
||||
cx, cy = _center(box)
|
||||
distance = math.hypot(sx - cx, sy - cy) / 1000.0
|
||||
area_ratio = abs(math.log(max(_area(source_box), 1.0) / max(_area(box), 1.0)))
|
||||
cost = distance + 0.25 * area_ratio + (1.0 - _iou(source_box, box))
|
||||
if cost <= 1.4:
|
||||
pairs.append((cost, str(item["object_id"]), index))
|
||||
output: dict[str, dict[str, Any]] = {}
|
||||
used: set[int] = set()
|
||||
for _, object_id, index in sorted(pairs):
|
||||
if object_id in output or index in used:
|
||||
continue
|
||||
output[object_id] = candidates[index]
|
||||
used.add(index)
|
||||
return output
|
||||
|
||||
|
||||
def _center(box: list[float]) -> tuple[float, float]:
|
||||
return ((float(box[0]) + float(box[2])) / 2.0, (float(box[1]) + float(box[3])) / 2.0)
|
||||
|
||||
|
||||
def _area(box: list[float]) -> float:
|
||||
return max(0.0, float(box[2]) - float(box[0])) * max(0.0, float(box[3]) - float(box[1]))
|
||||
|
||||
|
||||
def _iou(left: list[float], right: list[float]) -> float:
|
||||
x1, y1 = max(left[0], right[0]), max(left[1], right[1])
|
||||
x2, y2 = min(left[2], right[2]), min(left[3], right[3])
|
||||
intersection = max(0.0, x2 - x1) * max(0.0, y2 - y1)
|
||||
union = _area(left) + _area(right) - intersection
|
||||
return intersection / union if union > 0.0 else 0.0
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, object]:
|
||||
return {
|
||||
"role": role,
|
||||
"path": path.name,
|
||||
"size_bytes": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, raw: object) -> Path:
|
||||
if not isinstance(raw, dict) or not isinstance(raw.get("path"), str):
|
||||
raise E46BTemporalMotionError("artifact metadata is invalid")
|
||||
path = (root / raw["path"]).resolve(strict=True)
|
||||
if path.parent != root or path.is_symlink() or not path.is_file():
|
||||
raise E46BTemporalMotionError("artifact path is invalid")
|
||||
expected_size = raw.get("size_bytes", raw.get("byte_length"))
|
||||
if path.stat().st_size != expected_size or _sha256(path) != raw.get("sha256"):
|
||||
raise E46BTemporalMotionError("artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode()
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise E46BTemporalMotionError("JSON document is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path):
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
if line.strip():
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise E46BTemporalMotionError("JSONL row is invalid")
|
||||
yield value
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, values: list[dict[str, Any]]) -> None:
|
||||
with path.open("wb") as handle:
|
||||
for value in values:
|
||||
handle.write(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
@@ -0,0 +1,571 @@
|
||||
"""Freeze the full recorded RIGHT route-track and world-state qualification."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46a_ai_engineering_preannotation import (
|
||||
E46A_MANIFEST_NAME,
|
||||
read_e46a_ai_engineering_preannotation,
|
||||
)
|
||||
|
||||
E46C_RESULT_SCHEMA: Final = "missioncore.e46c-full-replay-world-tracks/v1"
|
||||
E46C_REPORT_SCHEMA: Final = "missioncore.e46c-full-replay-world-tracks-report/v1"
|
||||
E46C_CASE_SCHEMA: Final = "missioncore.e46c-full-replay-world-track-case/v1"
|
||||
E46C_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46C_REPORT_NAME: Final = "full-replay-world-track-report.json"
|
||||
E46C_CASES_NAME: Final = "full-replay-world-track-cases.jsonl"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46c-full-replay-world-tracks-[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46CFullReplayWorldTracksError(ValueError):
|
||||
"""Raised when an E46C source or immutable result is invalid."""
|
||||
|
||||
|
||||
def build_e46c_full_replay_world_tracks(
|
||||
*, e46a_root: Path, e26_root: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
e46a = read_e46a_ai_engineering_preannotation(e46a_root)
|
||||
source_cases = sorted(
|
||||
(copy.deepcopy(case) for case in e46a["cases"]),
|
||||
key=lambda case: int(case["truth_island_sequence"]),
|
||||
)
|
||||
if len(source_cases) != 32:
|
||||
raise E46CFullReplayWorldTracksError("E46C requires all 32 E46A frames")
|
||||
target_frames = {int(case["frame_index"]) for case in source_cases}
|
||||
e26 = _read_e26(e26_root, target_frames)
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
matched_objects = 0
|
||||
sample_world_frames = 0
|
||||
for source in source_cases:
|
||||
frame_index = int(source["frame_index"])
|
||||
fusion = e26["sample_fusion"][frame_index]
|
||||
world = e26["sample_world"][frame_index]
|
||||
associations = _associate(source["objects"], fusion["objects"])
|
||||
objects: list[dict[str, Any]] = []
|
||||
for raw in source["objects"]:
|
||||
item = copy.deepcopy(raw)
|
||||
match = associations.get(str(item["object_id"]))
|
||||
if match is None:
|
||||
item.update(
|
||||
{
|
||||
"route_track_id": None,
|
||||
"world_track_id": None,
|
||||
"motion_state": "unknown",
|
||||
"motion_confidence": 0.0,
|
||||
"world_binding_state": "unmatched",
|
||||
}
|
||||
)
|
||||
else:
|
||||
matched_objects += 1
|
||||
source_track_id = match.get("source_track_id")
|
||||
world_track_id = match.get("track_id")
|
||||
world_bound = (
|
||||
isinstance(source_track_id, int)
|
||||
and isinstance(world_track_id, int)
|
||||
and world_track_id != source_track_id
|
||||
)
|
||||
item.update(
|
||||
{
|
||||
"route_track_id": source_track_id,
|
||||
"world_track_id": world_track_id if world_bound else None,
|
||||
"motion_state": match.get("motion_state", "unknown"),
|
||||
"motion_confidence": float(
|
||||
match.get("motion_confidence") or 0.0
|
||||
),
|
||||
"world_binding_state": (
|
||||
"world-track-bound" if world_bound else "camera-track-only"
|
||||
),
|
||||
}
|
||||
)
|
||||
objects.append(item)
|
||||
world_objects = [_world_projection(item) for item in world["objects"]]
|
||||
if world_objects:
|
||||
sample_world_frames += 1
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": E46C_CASE_SCHEMA,
|
||||
"truth_island_sequence": int(source["truth_island_sequence"]),
|
||||
"image_id": int(source["image_id"]),
|
||||
"frame_index": frame_index,
|
||||
"group_id": str(source["group_id"]),
|
||||
"session_seconds": float(source["session_seconds"]),
|
||||
"source_image_sha256": str(source["source_image_sha256"]),
|
||||
"fusion_state": str(fusion["fusion_state"]),
|
||||
"objects": objects,
|
||||
"object_count": len(objects),
|
||||
"matched_route_object_count": sum(
|
||||
item["route_track_id"] is not None for item in objects
|
||||
),
|
||||
"world_objects": world_objects,
|
||||
"world_object_count": len(world_objects),
|
||||
}
|
||||
)
|
||||
|
||||
route = e26["route_metrics"]
|
||||
metrics = {
|
||||
**route,
|
||||
"visual_sample_frame_count": 32,
|
||||
"object_audited_sample_frame_count": 32,
|
||||
"sample_object_count": sum(len(case["objects"]) for case in cases),
|
||||
"sample_matched_route_object_count": matched_objects,
|
||||
"sample_unmatched_object_count": (
|
||||
sum(len(case["objects"]) for case in cases) - matched_objects
|
||||
),
|
||||
"sample_world_frame_count": sample_world_frames,
|
||||
}
|
||||
report_basis = {
|
||||
"schema_version": E46C_REPORT_SCHEMA,
|
||||
"status": "completed-full-recorded-right-route-world-track-qualification",
|
||||
"metrics": metrics,
|
||||
"acceptance": {
|
||||
"e26_diagnostic_accepted": True,
|
||||
"benchmark_passed_events": e26["benchmark_passed_events"],
|
||||
"benchmark_total_events": e26["benchmark_total_events"],
|
||||
"route_accounting_complete": True,
|
||||
"visual_sample_available": True,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"route_track_layer_available": True,
|
||||
"world_occupied_layer_available": True,
|
||||
"unknown_remains_occupied": True,
|
||||
"free_space_available": False,
|
||||
"next_action": (
|
||||
"use E46C as the recorded full-route diagnostic object/world layer; "
|
||||
"qualify identity continuity and world-binding exceptions before any "
|
||||
"live or planner-facing promotion"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
(
|
||||
"route-track IDs are detector-tracker identities with a 2.5 second idle "
|
||||
"bound, not permanent physical identities"
|
||||
),
|
||||
(
|
||||
"world-state is available only where pose/LiDAR support passes the E26 "
|
||||
"sync and evidence gates; missing evidence remains unknown occupied"
|
||||
),
|
||||
(
|
||||
"the 32 exact E46A frames are a visual audit sample; they do not make all "
|
||||
"4489 frames independently human reviewed"
|
||||
),
|
||||
(
|
||||
"E26 is accepted diagnostic engineering evidence, not independent truth, "
|
||||
"free space, commands, navigation, or safety authority"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46C_RESULT_SCHEMA,
|
||||
"e46a_visual_sample": {
|
||||
"result_id": e46a["result_id"],
|
||||
"manifest_sha256": _sha256(e46a["result_root"] / E46A_MANIFEST_NAME),
|
||||
},
|
||||
"e46_source": copy.deepcopy(e46a["manifest"]["identity"]["e46_source"]),
|
||||
"e26_full_replay": e26["identity"],
|
||||
"projection_profile": {
|
||||
"profile_id": "e46c-full-route-plus-e46a-visual-sample/v1",
|
||||
"camera_object_binding": "same-frame-group-bbox-one-to-one/v1",
|
||||
"world_binding": "e26-source-track-to-world-track/v1",
|
||||
"unknown_policy": "occupied-no-free-space-claim",
|
||||
},
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
digest = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46c-full-replay-world-tracks-{digest}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46c_full_replay_world_tracks(destination)
|
||||
created_at_utc = datetime.now(UTC).isoformat().replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46C_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46C_CASES_NAME, cases)
|
||||
_write_json(
|
||||
staging / E46C_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46C_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": digest,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "full-recorded-route-diagnostic-world-tracks",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / E46C_REPORT_NAME, "world-track-report"),
|
||||
_artifact(staging / E46C_CASES_NAME, "world-track-cases"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46c_full_replay_world_tracks(destination)
|
||||
|
||||
|
||||
def read_e46c_full_replay_world_tracks(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / E46C_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise E46CFullReplayWorldTracksError("E46C identity is invalid")
|
||||
digest = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if (
|
||||
manifest.get("schema_version") != E46C_RESULT_SCHEMA
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or manifest.get("result_id") != f"e46c-full-replay-world-tracks-{digest}"
|
||||
or manifest.get("result_id") != resolved.name
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46CFullReplayWorldTracksError("E46C identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise E46CFullReplayWorldTracksError("E46C artifacts are invalid")
|
||||
by_role = {item.get("role"): item for item in artifacts if isinstance(item, dict)}
|
||||
report = _read_json(_validated_artifact(resolved, by_role.get("world-track-report")))
|
||||
cases = tuple(
|
||||
_read_jsonl(_validated_artifact(resolved, by_role.get("world-track-cases")))
|
||||
)
|
||||
if (
|
||||
report.get("schema_version") != E46C_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != E46C_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest()
|
||||
!= identity.get("cases_sha256")
|
||||
):
|
||||
raise E46CFullReplayWorldTracksError("E46C result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _read_e26(root: Path, target_frames: set[int]) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
result_path = resolved / "result.json"
|
||||
result = _read_json(result_path)
|
||||
identity = result.get("identity")
|
||||
if (
|
||||
result.get("schema_version") != "missioncore.e10-integrated-perception-result/v1"
|
||||
or result.get("acceptance_state") != "accepted"
|
||||
or not isinstance(identity, dict)
|
||||
or identity.get("source_id") != "sensor.camera.right"
|
||||
or identity.get("selection", {}).get("frame_count") != 4489
|
||||
or identity.get("configuration", {}).get("pipeline")
|
||||
!= "kb4-multiview-static-hypothesis-lidar-fusion/v1"
|
||||
):
|
||||
raise E46CFullReplayWorldTracksError("E26 full replay contract is invalid")
|
||||
artifacts = {
|
||||
item.get("path"): item
|
||||
for item in result.get("artifacts", [])
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
fusion_path = _validated_artifact(resolved, artifacts.get("fusion-frames.jsonl"))
|
||||
world_path = _validated_artifact(resolved, artifacts.get("world-state.jsonl"))
|
||||
report_path = _validated_artifact(resolved, artifacts.get("run-report.json"))
|
||||
run_report = _read_json(report_path)
|
||||
acceptance = run_report.get("acceptance")
|
||||
benchmark = run_report.get("metrics", {}).get("benchmark")
|
||||
if (
|
||||
not isinstance(acceptance, dict)
|
||||
or acceptance.get("accepted") is not True
|
||||
or not isinstance(benchmark, dict)
|
||||
or benchmark.get("passed") is not True
|
||||
):
|
||||
raise E46CFullReplayWorldTracksError("E26 diagnostic acceptance is invalid")
|
||||
|
||||
sample_fusion: dict[int, dict[str, Any]] = {}
|
||||
source_tracks: set[int] = set()
|
||||
world_tracks: set[int] = set()
|
||||
source_world_bound: set[int] = set()
|
||||
fusion_states: Counter[str] = Counter()
|
||||
motion_states: Counter[str] = Counter()
|
||||
labels: Counter[str] = Counter()
|
||||
fusion_observations = 0
|
||||
fusion_frames = 0
|
||||
for row in _read_jsonl(fusion_path):
|
||||
frame_index = int(row["source_frame_index"])
|
||||
fusion_frames += 1
|
||||
fusion_states[str(row["fusion_state"])] += 1
|
||||
raw_objects = row.get("objects")
|
||||
if not isinstance(raw_objects, list):
|
||||
raise E46CFullReplayWorldTracksError("E26 fusion objects are invalid")
|
||||
for item in raw_objects:
|
||||
fusion_observations += 1
|
||||
source_id, track_id = item.get("source_track_id"), item.get("track_id")
|
||||
if isinstance(source_id, int):
|
||||
source_tracks.add(source_id)
|
||||
if (
|
||||
isinstance(source_id, int)
|
||||
and isinstance(track_id, int)
|
||||
and track_id != source_id
|
||||
):
|
||||
source_world_bound.add(source_id)
|
||||
world_tracks.add(track_id)
|
||||
motion_states[str(item.get("motion_state", "unknown"))] += 1
|
||||
labels[str(item.get("label", "unknown"))] += 1
|
||||
if frame_index in target_frames:
|
||||
sample_fusion[frame_index] = row
|
||||
|
||||
sample_world: dict[int, dict[str, Any]] = {}
|
||||
world_frames_with_objects = 0
|
||||
world_observations = 0
|
||||
world_current = 0
|
||||
world_held = 0
|
||||
world_motion: Counter[str] = Counter()
|
||||
occupancy_cells = 0
|
||||
world_frames = 0
|
||||
unique_world_state_tracks: set[int] = set()
|
||||
for row in _read_jsonl(world_path):
|
||||
frame_index = int(row["source_frame_index"])
|
||||
world_frames += 1
|
||||
raw_objects = row.get("objects")
|
||||
if not isinstance(raw_objects, list):
|
||||
raise E46CFullReplayWorldTracksError("E26 world objects are invalid")
|
||||
if raw_objects:
|
||||
world_frames_with_objects += 1
|
||||
for item in raw_objects:
|
||||
world_observations += 1
|
||||
if isinstance(item.get("track_id"), int):
|
||||
unique_world_state_tracks.add(int(item["track_id"]))
|
||||
if item.get("occupancy_evidence_current") is True:
|
||||
world_current += 1
|
||||
else:
|
||||
world_held += 1
|
||||
occupancy_cells += int(item.get("occupancy_cell_count") or 0)
|
||||
world_motion[str(item.get("motion_state", "unknown"))] += 1
|
||||
if frame_index in target_frames:
|
||||
sample_world[frame_index] = row
|
||||
if (
|
||||
fusion_frames != 4489
|
||||
or world_frames != 4489
|
||||
or set(sample_fusion) != target_frames
|
||||
or set(sample_world) != target_frames
|
||||
):
|
||||
raise E46CFullReplayWorldTracksError("E26 full replay accounting changed")
|
||||
selection = identity["selection"]
|
||||
route_metrics = {
|
||||
"route_frame_count": fusion_frames,
|
||||
"route_span_seconds": round(
|
||||
float(selection["timeline_end_seconds"])
|
||||
- float(selection["timeline_start_seconds"]),
|
||||
6,
|
||||
),
|
||||
"fusion_observation_count": fusion_observations,
|
||||
"source_track_count": len(source_tracks),
|
||||
"world_track_candidate_count": len(world_tracks),
|
||||
"world_track_count": len(unique_world_state_tracks),
|
||||
"source_track_world_bound_count": len(source_world_bound),
|
||||
"world_frame_count": world_frames_with_objects,
|
||||
"world_observation_count": world_observations,
|
||||
"world_current_observation_count": world_current,
|
||||
"world_held_observation_count": world_held,
|
||||
"occupancy_cell_observation_count": occupancy_cells,
|
||||
"fusion_state_counts": dict(sorted(fusion_states.items())),
|
||||
"motion_observation_counts": dict(sorted(motion_states.items())),
|
||||
"world_motion_observation_counts": dict(sorted(world_motion.items())),
|
||||
"class_observation_counts": dict(sorted(labels.items())),
|
||||
}
|
||||
return {
|
||||
"sample_fusion": sample_fusion,
|
||||
"sample_world": sample_world,
|
||||
"route_metrics": route_metrics,
|
||||
"benchmark_passed_events": int(benchmark["passed_events"]),
|
||||
"benchmark_total_events": int(benchmark["total_events"]),
|
||||
"identity": {
|
||||
"result_id": result["result_id"],
|
||||
"result_sha256": _sha256(result_path),
|
||||
"fusion_frames_sha256": artifacts["fusion-frames.jsonl"]["sha256"],
|
||||
"world_state_sha256": artifacts["world-state.jsonl"]["sha256"],
|
||||
"run_report_sha256": artifacts["run-report.json"]["sha256"],
|
||||
"input_sha256": identity["input_sha256"],
|
||||
"timeline_sha256": selection["timeline_sha256"],
|
||||
"pipeline": identity["configuration"]["pipeline"],
|
||||
"profile_sha256": identity["configuration"]["profile_sha256"],
|
||||
"implementation_sha256": identity["configuration"][
|
||||
"implementation_sha256"
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _world_projection(item: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"world_track_id": item.get("track_id"),
|
||||
"route_track_id": item.get("source_track_id"),
|
||||
"source_track_aliases": copy.deepcopy(item.get("source_track_aliases", [])),
|
||||
"category": item.get("detector_label", item.get("class", "unknown")),
|
||||
"motion_state": item.get("motion_state", "unknown"),
|
||||
"motion_confidence": float(item.get("motion_confidence") or 0.0),
|
||||
"position_map_m": copy.deepcopy(item.get("position_map_m")),
|
||||
"size_m": copy.deepcopy(item.get("size_m")),
|
||||
"occupancy_footprint_map_xy": copy.deepcopy(
|
||||
item.get("occupancy_footprint_map_xy", [])
|
||||
),
|
||||
"occupancy_evidence_current": bool(item.get("occupancy_evidence_current")),
|
||||
"occupancy_observation_age_ms": item.get("occupancy_observation_age_ms"),
|
||||
"occupancy_cell_count": int(item.get("occupancy_cell_count") or 0),
|
||||
"temporal_status": item.get("temporal_status"),
|
||||
}
|
||||
|
||||
|
||||
def _associate(
|
||||
source: list[dict[str, Any]], candidates: list[dict[str, Any]]
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
pairs: list[tuple[float, str, int]] = []
|
||||
for item in source:
|
||||
category = item.get("category")
|
||||
group = "vehicle" if category in {"car", "heavy_vehicle"} else category
|
||||
for index, candidate in enumerate(candidates):
|
||||
if candidate.get("association_group") != group:
|
||||
continue
|
||||
box = candidate.get("bbox_xyxy")
|
||||
if not isinstance(box, list) or len(box) != 4:
|
||||
continue
|
||||
source_box = item["box_xyxy"]
|
||||
sx, sy = _center(source_box)
|
||||
cx, cy = _center(box)
|
||||
distance = math.hypot(sx - cx, sy - cy) / 1000.0
|
||||
area_ratio = abs(
|
||||
math.log(max(_area(source_box), 1.0) / max(_area(box), 1.0))
|
||||
)
|
||||
cost = distance + 0.25 * area_ratio + (1.0 - _iou(source_box, box))
|
||||
if cost <= 1.4:
|
||||
pairs.append((cost, str(item["object_id"]), index))
|
||||
output: dict[str, dict[str, Any]] = {}
|
||||
used: set[int] = set()
|
||||
for _, object_id, index in sorted(pairs):
|
||||
if object_id not in output and index not in used:
|
||||
output[object_id] = candidates[index]
|
||||
used.add(index)
|
||||
return output
|
||||
|
||||
|
||||
def _center(box: list[float]) -> tuple[float, float]:
|
||||
return ((float(box[0]) + float(box[2])) / 2, (float(box[1]) + float(box[3])) / 2)
|
||||
|
||||
|
||||
def _area(box: list[float]) -> float:
|
||||
return max(0.0, box[2] - box[0]) * max(0.0, box[3] - box[1])
|
||||
|
||||
|
||||
def _iou(left: list[float], right: list[float]) -> float:
|
||||
x1, y1 = max(left[0], right[0]), max(left[1], right[1])
|
||||
x2, y2 = min(left[2], right[2]), min(left[3], right[3])
|
||||
intersection = max(0.0, x2 - x1) * max(0.0, y2 - y1)
|
||||
union = _area(left) + _area(right) - intersection
|
||||
return intersection / union if union > 0 else 0.0
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, object]:
|
||||
return {
|
||||
"role": role,
|
||||
"path": path.name,
|
||||
"size_bytes": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, raw: object) -> Path:
|
||||
if not isinstance(raw, dict) or not isinstance(raw.get("path"), str):
|
||||
raise E46CFullReplayWorldTracksError("artifact metadata is invalid")
|
||||
path = (root / raw["path"]).resolve(strict=True)
|
||||
if path.parent != root or path.is_symlink() or not path.is_file():
|
||||
raise E46CFullReplayWorldTracksError("artifact path is invalid")
|
||||
expected_size = raw.get("size_bytes", raw.get("byte_length"))
|
||||
if path.stat().st_size != expected_size or _sha256(path) != raw.get("sha256"):
|
||||
raise E46CFullReplayWorldTracksError("artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode()
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise E46CFullReplayWorldTracksError("JSON document is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path):
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
if line.strip():
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise E46CFullReplayWorldTracksError("JSONL row is invalid")
|
||||
yield value
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, values: list[dict[str, Any]]) -> None:
|
||||
with path.open("wb") as handle:
|
||||
for value in values:
|
||||
handle.write(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
@@ -0,0 +1,993 @@
|
||||
"""Freeze a deterministic temporal-failure audit of the full E46C replay."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter, defaultdict
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46c_full_replay_world_tracks import (
|
||||
E46C_MANIFEST_NAME,
|
||||
read_e46c_full_replay_world_tracks,
|
||||
)
|
||||
|
||||
E46D_RESULT_SCHEMA: Final = "missioncore.e46d-temporal-failure-audit/v1"
|
||||
E46D_REPORT_SCHEMA: Final = "missioncore.e46d-temporal-failure-audit-report/v1"
|
||||
E46D_SIGNAL_SCHEMA: Final = "missioncore.e46d-temporal-failure-signal/v1"
|
||||
E46D_CLIP_SCHEMA: Final = "missioncore.e46d-temporal-review-clip/v1"
|
||||
E46D_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46D_REPORT_NAME: Final = "temporal-failure-audit-report.json"
|
||||
E46D_SIGNALS_NAME: Final = "temporal-failure-signals.jsonl"
|
||||
E46D_CLIPS_NAME: Final = "temporal-review-clips.jsonl"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46d-temporal-failure-audit-[a-f0-9]{64}$")
|
||||
_E26_RESULT_ID = re.compile(r"^e10-integrated-perception-[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
_PROFILE: Final = {
|
||||
"profile_id": "e46d-full-replay-temporal-failure-audit/v1",
|
||||
"layer_blackout_min_frames": 3,
|
||||
"detector_hold_min_frames": 3,
|
||||
"route_gap_min_frames": 3,
|
||||
"route_gap_max_frames": 25,
|
||||
"id_rebirth_min_iou": 0.5,
|
||||
"bbox_jump_max_iou": 0.1,
|
||||
"bbox_jump_min_area_px2": 300.0,
|
||||
"flap_window_seconds": 1.0,
|
||||
"flap_min_transitions": 3,
|
||||
"short_track_max_observations": 3,
|
||||
"short_track_burst_min_count": 4,
|
||||
"review_clip_lead_seconds": 2.0,
|
||||
"review_clip_tail_seconds": 2.0,
|
||||
"review_clip_separation_seconds": 1.5,
|
||||
"review_clip_limit": 48,
|
||||
}
|
||||
_PRIORITY = {"critical": 3, "high": 2, "medium": 1}
|
||||
|
||||
|
||||
class E46DTemporalFailureAuditError(ValueError):
|
||||
"""Raised when an E46D source or immutable result is invalid."""
|
||||
|
||||
|
||||
def build_e46d_temporal_failure_audit(
|
||||
*, e46c_root: Path, e26_results_root: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Audit all E46C temporal rows and freeze prioritized recorded-video clips."""
|
||||
|
||||
e46c = read_e46c_full_replay_world_tracks(e46c_root)
|
||||
e26_binding = e46c["manifest"]["identity"].get("e26_full_replay")
|
||||
if not isinstance(e26_binding, dict):
|
||||
raise E46DTemporalFailureAuditError("E46C E26 binding is invalid")
|
||||
e26_result_id = e26_binding.get("result_id")
|
||||
if not isinstance(e26_result_id, str) or _E26_RESULT_ID.fullmatch(e26_result_id) is None:
|
||||
raise E46DTemporalFailureAuditError("E46C E26 identity is invalid")
|
||||
e26_root = (e26_results_root.expanduser().absolute() / e26_result_id).resolve(strict=True)
|
||||
results_root = e26_results_root.expanduser().absolute().resolve(strict=True)
|
||||
if e26_root.parent != results_root or e26_root.is_symlink():
|
||||
raise E46DTemporalFailureAuditError("E26 source root is invalid")
|
||||
frames, source_identity = _read_e26_frames(e26_root, e26_binding)
|
||||
signals, clips, metrics = analyze_temporal_frames(frames)
|
||||
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "deterministic",
|
||||
"pipeline_id": "e46d-full-replay-temporal-failure-audit/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": e46c["result_id"],
|
||||
"version": "E46C full recorded RIGHT route/world tracks",
|
||||
"role": "admitted full-route diagnostic result and video binding",
|
||||
"identity_sha256": _sha256(e46c["result_root"] / E46C_MANIFEST_NAME),
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": e26_result_id,
|
||||
"version": "accepted E26 fusion frames",
|
||||
"role": "4489-frame temporal object, track, world and motion evidence",
|
||||
"identity_sha256": source_identity["fusion_frames_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "deterministic temporal exception scanner",
|
||||
"version": _PROFILE["profile_id"],
|
||||
"role": "detect, classify, rank and clip observable temporal discontinuities",
|
||||
"identity_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
},
|
||||
],
|
||||
}
|
||||
report_basis = {
|
||||
"schema_version": E46D_REPORT_SCHEMA,
|
||||
"status": "completed-full-recorded-right-temporal-failure-audit",
|
||||
"metrics": metrics,
|
||||
"acceptance": {
|
||||
"full_route_accounted": metrics["route_frame_count"] == 4489,
|
||||
"temporal_continuity_passed": metrics["temporal_continuity_passed"],
|
||||
"independent_truth_available": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"temporal_regression_confirmed": not metrics["temporal_continuity_passed"],
|
||||
"detector_gap_visible": metrics["detector_hold_episode_count"] > 0,
|
||||
"route_fragmentation_visible": metrics["short_route_track_count"] > 0,
|
||||
"world_binding_instability_visible": metrics["world_binding_flap_episode_count"] > 0,
|
||||
"next_action": (
|
||||
"use the frozen clips to separate detector gaps from route-tracker and "
|
||||
"world-binding failures, then rerun the same recorded RIGHT source as an A/B replay"
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"signals prove discontinuities in the published diagnostic layer; without "
|
||||
"independent frame truth they do not by themselves prove that a visible "
|
||||
"physical object was missed or that a short track was a false positive"
|
||||
),
|
||||
(
|
||||
"route-ID rebirth uses same-class image-space overlap and is a high-priority "
|
||||
"candidate for visual review, not a permanent physical-identity verdict"
|
||||
),
|
||||
(
|
||||
"bbox jumps are measured in the distorted 800x600 RIGHT image plane; the "
|
||||
"audit does not claim metric velocity or calibrated image-plane motion"
|
||||
),
|
||||
(
|
||||
"recorded replay only: no LEFT camera, live hardware, commands, free-space, "
|
||||
"navigation or safety authority is introduced"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
producer_sha256 = _sha256(Path(__file__).resolve(strict=True))
|
||||
identity = {
|
||||
"schema_version": E46D_RESULT_SCHEMA,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"e46c_source": {
|
||||
"result_id": e46c["result_id"],
|
||||
"manifest_sha256": _sha256(e46c["result_root"] / E46C_MANIFEST_NAME),
|
||||
},
|
||||
"e26_source": source_identity,
|
||||
"analysis_profile": copy.deepcopy(_PROFILE),
|
||||
"method": method,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"signals_sha256": hashlib.sha256(_canonical_json(signals)).hexdigest(),
|
||||
"clips_sha256": hashlib.sha256(_canonical_json(clips)).hexdigest(),
|
||||
"producer_sha256": producer_sha256,
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46d-temporal-failure-audit-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46d_temporal_failure_audit(destination)
|
||||
created_at_utc = datetime.now(UTC).isoformat().replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46D_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46D_SIGNALS_NAME, signals)
|
||||
_write_jsonl(staging / E46D_CLIPS_NAME, clips)
|
||||
_write_json(
|
||||
staging / E46D_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46D_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "recorded-temporal-regression-diagnostic",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / E46D_REPORT_NAME, "temporal-failure-report"),
|
||||
_artifact(staging / E46D_SIGNALS_NAME, "temporal-failure-signals"),
|
||||
_artifact(staging / E46D_CLIPS_NAME, "temporal-review-clips"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46d_temporal_failure_audit(destination)
|
||||
|
||||
|
||||
def read_e46d_temporal_failure_audit(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / E46D_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise E46DTemporalFailureAuditError("E46D identity is invalid")
|
||||
digest = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if (
|
||||
manifest.get("schema_version") != E46D_RESULT_SCHEMA
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or manifest.get("result_id") != f"e46d-temporal-failure-audit-{digest}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("E46D identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 3:
|
||||
raise E46DTemporalFailureAuditError("E46D artifacts are invalid")
|
||||
by_role = {item.get("role"): item for item in artifacts if isinstance(item, dict)}
|
||||
report = _read_json(_validated_artifact(resolved, by_role.get("temporal-failure-report")))
|
||||
signals = tuple(
|
||||
_read_jsonl(_validated_artifact(resolved, by_role.get("temporal-failure-signals")))
|
||||
)
|
||||
clips = tuple(_read_jsonl(_validated_artifact(resolved, by_role.get("temporal-review-clips"))))
|
||||
if (
|
||||
report.get("schema_version") != E46D_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or any(item.get("schema_version") != E46D_SIGNAL_SCHEMA for item in signals)
|
||||
or any(item.get("schema_version") != E46D_CLIP_SCHEMA for item in clips)
|
||||
or hashlib.sha256(_canonical_json(signals)).hexdigest() != identity.get("signals_sha256")
|
||||
or hashlib.sha256(_canonical_json(clips)).hexdigest() != identity.get("clips_sha256")
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("E46D result changed")
|
||||
metrics = report.get("metrics")
|
||||
if (
|
||||
not isinstance(metrics, dict)
|
||||
or metrics.get("failure_signal_count") != len(signals)
|
||||
or metrics.get("review_clip_count") != len(clips)
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("E46D accounting changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"signals": signals,
|
||||
"clips": clips,
|
||||
}
|
||||
|
||||
|
||||
def analyze_temporal_frames(
|
||||
frames: list[dict[str, Any]],
|
||||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], dict[str, Any]]:
|
||||
"""Return deterministic signals, ranked clip windows and full-route metrics."""
|
||||
|
||||
_validate_frames(frames)
|
||||
times = [float(frame["session_seconds"]) for frame in frames]
|
||||
timeline_start, timeline_end = times[0], times[-1]
|
||||
signals: list[dict[str, Any]] = []
|
||||
by_track: dict[int, list[tuple[int, dict[str, Any]]]] = defaultdict(list)
|
||||
for position, frame in enumerate(frames):
|
||||
for item in frame["objects"]:
|
||||
by_track[int(item["route_track_id"])].append((position, item))
|
||||
|
||||
counts = [len(frame["objects"]) for frame in frames]
|
||||
position = 0
|
||||
while position < len(frames):
|
||||
if counts[position] != 0:
|
||||
position += 1
|
||||
continue
|
||||
start = position
|
||||
while position + 1 < len(frames) and counts[position + 1] == 0:
|
||||
position += 1
|
||||
end = position
|
||||
length = end - start + 1
|
||||
if (
|
||||
start > 0
|
||||
and end + 1 < len(frames)
|
||||
and length >= int(_PROFILE["layer_blackout_min_frames"])
|
||||
):
|
||||
before, after = counts[start - 1], counts[end + 1]
|
||||
if before > 0 and after > 0:
|
||||
duration = times[end + 1] - times[start]
|
||||
tracks = sorted(
|
||||
{
|
||||
int(item["route_track_id"])
|
||||
for item in frames[start - 1]["objects"] + frames[end + 1]["objects"]
|
||||
}
|
||||
)
|
||||
signals.append(
|
||||
_signal(
|
||||
"layer-blackout",
|
||||
"critical" if duration >= 0.5 else "high",
|
||||
frames,
|
||||
start,
|
||||
end,
|
||||
route_track_ids=tracks,
|
||||
evidence={
|
||||
"duration_seconds": round(duration, 6),
|
||||
"zero_frame_count": length,
|
||||
"before_object_count": before,
|
||||
"minimum_object_count": 0,
|
||||
"after_object_count": after,
|
||||
},
|
||||
)
|
||||
)
|
||||
position += 1
|
||||
|
||||
detector_hold_observations = 0
|
||||
route_gap_count = 0
|
||||
for route_track_id, sequence in by_track.items():
|
||||
cursor = 0
|
||||
while cursor < len(sequence):
|
||||
position, item = sequence[cursor]
|
||||
if bool(item["camera_evidence_current"]):
|
||||
cursor += 1
|
||||
continue
|
||||
start_cursor = cursor
|
||||
while (
|
||||
cursor + 1 < len(sequence)
|
||||
and sequence[cursor + 1][0] == sequence[cursor][0] + 1
|
||||
and not bool(sequence[cursor + 1][1]["camera_evidence_current"])
|
||||
):
|
||||
cursor += 1
|
||||
end_cursor = cursor
|
||||
length = end_cursor - start_cursor + 1
|
||||
detector_hold_observations += length
|
||||
first_position = sequence[start_cursor][0]
|
||||
last_position = sequence[end_cursor][0]
|
||||
current_before = (
|
||||
start_cursor > 0
|
||||
and sequence[start_cursor - 1][0] == first_position - 1
|
||||
and bool(sequence[start_cursor - 1][1]["camera_evidence_current"])
|
||||
)
|
||||
current_after = (
|
||||
end_cursor + 1 < len(sequence)
|
||||
and sequence[end_cursor + 1][0] == last_position + 1
|
||||
and bool(sequence[end_cursor + 1][1]["camera_evidence_current"])
|
||||
)
|
||||
if length >= int(_PROFILE["detector_hold_min_frames"]):
|
||||
duration_end = (
|
||||
times[last_position + 1]
|
||||
if last_position + 1 < len(times)
|
||||
else times[last_position]
|
||||
)
|
||||
duration = duration_end - times[first_position]
|
||||
signals.append(
|
||||
_signal(
|
||||
"camera-evidence-hold",
|
||||
"high" if duration >= 0.5 else "medium",
|
||||
frames,
|
||||
first_position,
|
||||
last_position,
|
||||
route_track_ids=[route_track_id],
|
||||
evidence={
|
||||
"duration_seconds": round(duration, 6),
|
||||
"held_frame_count": length,
|
||||
"camera_evidence_current": False,
|
||||
"route_identity_retained": True,
|
||||
"current_evidence_before": current_before,
|
||||
"current_evidence_after": current_after,
|
||||
},
|
||||
)
|
||||
)
|
||||
cursor += 1
|
||||
|
||||
for (previous_position, previous), (next_position, following) in zip(
|
||||
sequence, sequence[1:], strict=False
|
||||
):
|
||||
missing = next_position - previous_position - 1
|
||||
if not (
|
||||
int(_PROFILE["route_gap_min_frames"])
|
||||
<= missing
|
||||
<= int(_PROFILE["route_gap_max_frames"])
|
||||
):
|
||||
continue
|
||||
route_gap_count += 1
|
||||
signals.append(
|
||||
_signal(
|
||||
"route-layer-gap",
|
||||
"high" if missing >= 5 else "medium",
|
||||
frames,
|
||||
previous_position + 1,
|
||||
next_position - 1,
|
||||
route_track_ids=[route_track_id],
|
||||
evidence={
|
||||
"missing_frame_count": missing,
|
||||
"duration_seconds": round(
|
||||
times[next_position] - times[previous_position], 6
|
||||
),
|
||||
"same_route_identity_returned": True,
|
||||
"boundary_iou": round(
|
||||
_iou(previous["bbox_xyxy"], following["bbox_xyxy"]), 6
|
||||
),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
seen_rebirths: set[tuple[int, int]] = set()
|
||||
for position, (previous, following) in enumerate(zip(frames, frames[1:], strict=False)):
|
||||
previous_ids = {int(item["route_track_id"]) for item in previous["objects"]}
|
||||
following_ids = {int(item["route_track_id"]) for item in following["objects"]}
|
||||
gone = [
|
||||
item
|
||||
for item in previous["objects"]
|
||||
if int(item["route_track_id"]) not in following_ids
|
||||
and item["camera_evidence_current"] is True
|
||||
]
|
||||
born = [
|
||||
item
|
||||
for item in following["objects"]
|
||||
if int(item["route_track_id"]) not in previous_ids
|
||||
and item["camera_evidence_current"] is True
|
||||
]
|
||||
candidates = sorted(
|
||||
(
|
||||
(_iou(left["bbox_xyxy"], right["bbox_xyxy"]), left, right)
|
||||
for left in gone
|
||||
for right in born
|
||||
if left["category"] == right["category"]
|
||||
and _iou(left["bbox_xyxy"], right["bbox_xyxy"])
|
||||
>= float(_PROFILE["id_rebirth_min_iou"])
|
||||
),
|
||||
key=lambda value: value[0],
|
||||
reverse=True,
|
||||
)
|
||||
used_old: set[int] = set()
|
||||
used_new: set[int] = set()
|
||||
for overlap, left, right in candidates:
|
||||
old_id, new_id = int(left["route_track_id"]), int(right["route_track_id"])
|
||||
if old_id in used_old or new_id in used_new or (old_id, new_id) in seen_rebirths:
|
||||
continue
|
||||
used_old.add(old_id)
|
||||
used_new.add(new_id)
|
||||
seen_rebirths.add((old_id, new_id))
|
||||
signals.append(
|
||||
_signal(
|
||||
"route-id-rebirth-candidate",
|
||||
"critical",
|
||||
frames,
|
||||
position,
|
||||
position + 1,
|
||||
route_track_ids=[old_id, new_id],
|
||||
evidence={
|
||||
"category": left["category"],
|
||||
"bbox_iou": round(overlap, 6),
|
||||
"old_route_track_id": old_id,
|
||||
"new_route_track_id": new_id,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
for route_track_id, sequence in by_track.items():
|
||||
for (previous_position, previous), (next_position, following) in zip(
|
||||
sequence, sequence[1:], strict=False
|
||||
):
|
||||
if (
|
||||
next_position != previous_position + 1
|
||||
or previous["camera_evidence_current"] is not True
|
||||
or following["camera_evidence_current"] is not True
|
||||
):
|
||||
continue
|
||||
area = min(_box_area(previous["bbox_xyxy"]), _box_area(following["bbox_xyxy"]))
|
||||
overlap = _iou(previous["bbox_xyxy"], following["bbox_xyxy"])
|
||||
if area < float(_PROFILE["bbox_jump_min_area_px2"]) or overlap >= float(
|
||||
_PROFILE["bbox_jump_max_iou"]
|
||||
):
|
||||
continue
|
||||
signals.append(
|
||||
_signal(
|
||||
"bbox-jump",
|
||||
"high",
|
||||
frames,
|
||||
previous_position,
|
||||
next_position,
|
||||
route_track_ids=[route_track_id],
|
||||
evidence={
|
||||
"bbox_iou": round(overlap, 6),
|
||||
"minimum_box_area_px2": round(area, 3),
|
||||
"both_camera_evidence_current": True,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
for route_track_id, sequence in by_track.items():
|
||||
signals.extend(
|
||||
_flap_signals(frames, route_track_id, sequence, "motion_state", "motion-state-flap")
|
||||
)
|
||||
signals.extend(
|
||||
_flap_signals(frames, route_track_id, sequence, "world_track_id", "world-binding-flap")
|
||||
)
|
||||
|
||||
short_tracks = [
|
||||
(route_track_id, sequence)
|
||||
for route_track_id, sequence in by_track.items()
|
||||
if len(sequence) <= int(_PROFILE["short_track_max_observations"])
|
||||
]
|
||||
short_starts = sorted(
|
||||
(times[sequence[0][0]], route_track_id, sequence[0][0])
|
||||
for route_track_id, sequence in short_tracks
|
||||
)
|
||||
cursor = 0
|
||||
while cursor < len(short_starts):
|
||||
end = cursor
|
||||
while end < len(short_starts) and short_starts[end][0] - short_starts[cursor][0] <= 1.0:
|
||||
end += 1
|
||||
window = short_starts[cursor:end]
|
||||
if len(window) >= int(_PROFILE["short_track_burst_min_count"]):
|
||||
track_ids = [item[1] for item in window]
|
||||
signals.append(
|
||||
_signal(
|
||||
"short-track-burst",
|
||||
"high" if len(window) >= 6 else "medium",
|
||||
frames,
|
||||
window[0][2],
|
||||
window[-1][2],
|
||||
route_track_ids=track_ids,
|
||||
evidence={
|
||||
"short_track_count": len(window),
|
||||
"maximum_observations_per_track": int(
|
||||
_PROFILE["short_track_max_observations"]
|
||||
),
|
||||
"window_seconds": round(window[-1][0] - window[0][0], 6),
|
||||
},
|
||||
)
|
||||
)
|
||||
cursor = end
|
||||
else:
|
||||
cursor += 1
|
||||
|
||||
signals.sort(
|
||||
key=lambda item: (float(item["start_seconds"]), str(item["kind"]), str(item["signal_id"]))
|
||||
)
|
||||
clips = _review_clips(signals, timeline_start, timeline_end)
|
||||
signal_counts = Counter(str(item["kind"]) for item in signals)
|
||||
priority_counts = Counter(str(item["priority"]) for item in signals)
|
||||
object_observations = sum(counts)
|
||||
camera_held = sum(
|
||||
item["camera_evidence_current"] is False for frame in frames for item in frame["objects"]
|
||||
)
|
||||
metrics = {
|
||||
"route_frame_count": len(frames),
|
||||
"route_span_seconds": round(timeline_end - timeline_start, 6),
|
||||
"object_observation_count": object_observations,
|
||||
"route_track_count": len(by_track),
|
||||
"zero_object_frame_count": sum(value == 0 for value in counts),
|
||||
"zero_object_frame_fraction": round(sum(value == 0 for value in counts) / len(frames), 9),
|
||||
"camera_held_observation_count": camera_held,
|
||||
"camera_held_observation_fraction": round(camera_held / object_observations, 9),
|
||||
"detector_hold_episode_count": int(signal_counts["camera-evidence-hold"]),
|
||||
"layer_blackout_episode_count": int(signal_counts["layer-blackout"]),
|
||||
"route_layer_gap_episode_count": route_gap_count,
|
||||
"route_id_rebirth_candidate_count": int(signal_counts["route-id-rebirth-candidate"]),
|
||||
"bbox_jump_episode_count": int(signal_counts["bbox-jump"]),
|
||||
"motion_state_flap_episode_count": int(signal_counts["motion-state-flap"]),
|
||||
"world_binding_flap_episode_count": int(signal_counts["world-binding-flap"]),
|
||||
"short_route_track_count": len(short_tracks),
|
||||
"short_route_track_fraction": round(len(short_tracks) / len(by_track), 9),
|
||||
"short_track_burst_episode_count": int(signal_counts["short-track-burst"]),
|
||||
"failure_signal_count": len(signals),
|
||||
"review_clip_count": len(clips),
|
||||
"signal_counts": dict(sorted(signal_counts.items())),
|
||||
"priority_counts": {
|
||||
key: int(priority_counts.get(key, 0)) for key in ("critical", "high", "medium")
|
||||
},
|
||||
"temporal_continuity_passed": (
|
||||
signal_counts["layer-blackout"] == 0
|
||||
and signal_counts["route-id-rebirth-candidate"] == 0
|
||||
and signal_counts["bbox-jump"] == 0
|
||||
),
|
||||
}
|
||||
return signals, clips, metrics
|
||||
|
||||
|
||||
def _flap_signals(
|
||||
frames: list[dict[str, Any]],
|
||||
route_track_id: int,
|
||||
sequence: list[tuple[int, dict[str, Any]]],
|
||||
field: str,
|
||||
kind: str,
|
||||
) -> list[dict[str, Any]]:
|
||||
transitions: list[tuple[int, object, object]] = []
|
||||
for (previous_position, previous), (next_position, following) in zip(
|
||||
sequence, sequence[1:], strict=False
|
||||
):
|
||||
if next_position == previous_position + 1 and previous[field] != following[field]:
|
||||
transitions.append((next_position, previous[field], following[field]))
|
||||
output: list[dict[str, Any]] = []
|
||||
cursor = 0
|
||||
while cursor < len(transitions):
|
||||
end = cursor
|
||||
start_seconds = float(frames[transitions[cursor][0]]["session_seconds"])
|
||||
while end < len(transitions) and float(
|
||||
frames[transitions[end][0]]["session_seconds"]
|
||||
) - start_seconds <= float(_PROFILE["flap_window_seconds"]):
|
||||
end += 1
|
||||
window = transitions[cursor:end]
|
||||
if len(window) >= int(_PROFILE["flap_min_transitions"]):
|
||||
values = {value for _, before, after in window for value in (before, after)}
|
||||
output.append(
|
||||
_signal(
|
||||
kind,
|
||||
"high" if len(window) >= 4 else "medium",
|
||||
frames,
|
||||
window[0][0] - 1,
|
||||
window[-1][0],
|
||||
route_track_ids=[route_track_id],
|
||||
world_track_ids=(
|
||||
sorted(
|
||||
int(value)
|
||||
for value in values
|
||||
if isinstance(value, int) and not isinstance(value, bool)
|
||||
)
|
||||
if field == "world_track_id"
|
||||
else []
|
||||
),
|
||||
evidence={
|
||||
"transition_count": len(window),
|
||||
"window_seconds": round(
|
||||
float(frames[window[-1][0]]["session_seconds"])
|
||||
- float(frames[window[0][0]]["session_seconds"]),
|
||||
6,
|
||||
),
|
||||
"state_count": len(values),
|
||||
},
|
||||
)
|
||||
)
|
||||
cursor = end
|
||||
else:
|
||||
cursor += 1
|
||||
return output
|
||||
|
||||
|
||||
def _signal(
|
||||
kind: str,
|
||||
priority: str,
|
||||
frames: list[dict[str, Any]],
|
||||
start_position: int,
|
||||
end_position: int,
|
||||
*,
|
||||
route_track_ids: list[int],
|
||||
evidence: dict[str, object],
|
||||
world_track_ids: list[int] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
start_position = max(0, start_position)
|
||||
end_position = min(len(frames) - 1, end_position)
|
||||
basis = {
|
||||
"kind": kind,
|
||||
"priority": priority,
|
||||
"start_frame": int(frames[start_position]["frame_index"]),
|
||||
"end_frame": int(frames[end_position]["frame_index"]),
|
||||
"route_track_ids": sorted(set(route_track_ids)),
|
||||
"world_track_ids": sorted(set(world_track_ids or [])),
|
||||
"evidence": evidence,
|
||||
}
|
||||
signal_id = "e46d-signal-" + hashlib.sha256(_canonical_json(basis)).hexdigest()[:20]
|
||||
return {
|
||||
"schema_version": E46D_SIGNAL_SCHEMA,
|
||||
"signal_id": signal_id,
|
||||
**basis,
|
||||
"start_seconds": float(frames[start_position]["session_seconds"]),
|
||||
"end_seconds": float(frames[end_position]["session_seconds"]),
|
||||
}
|
||||
|
||||
|
||||
def _review_clips(
|
||||
signals: list[dict[str, Any]], timeline_start: float, timeline_end: float
|
||||
) -> list[dict[str, Any]]:
|
||||
ranked = sorted(
|
||||
signals,
|
||||
key=lambda item: (
|
||||
-_signal_score(item),
|
||||
float(item["start_seconds"]),
|
||||
str(item["signal_id"]),
|
||||
),
|
||||
)
|
||||
selected: list[dict[str, Any]] = []
|
||||
selected_ids: set[str] = set()
|
||||
|
||||
def admit(signal: dict[str, Any]) -> bool:
|
||||
center = (float(signal["start_seconds"]) + float(signal["end_seconds"])) / 2
|
||||
if any(
|
||||
abs(center - (float(item["start_seconds"]) + float(item["end_seconds"])) / 2)
|
||||
< float(_PROFILE["review_clip_separation_seconds"])
|
||||
for item in selected
|
||||
):
|
||||
return False
|
||||
selected.append(signal)
|
||||
selected_ids.add(str(signal["signal_id"]))
|
||||
return True
|
||||
|
||||
for kind in sorted({str(item["kind"]) for item in signals}):
|
||||
admitted = 0
|
||||
for signal in ranked:
|
||||
if (
|
||||
signal["kind"] == kind
|
||||
and str(signal["signal_id"]) not in selected_ids
|
||||
and admit(signal)
|
||||
):
|
||||
admitted += 1
|
||||
if admitted == 2:
|
||||
break
|
||||
for signal in ranked:
|
||||
if len(selected) >= int(_PROFILE["review_clip_limit"]):
|
||||
break
|
||||
if str(signal["signal_id"]) not in selected_ids:
|
||||
admit(signal)
|
||||
selected.sort(key=lambda item: (-_signal_score(item), float(item["start_seconds"])))
|
||||
clips: list[dict[str, Any]] = []
|
||||
for rank, signal in enumerate(selected, start=1):
|
||||
clips.append(
|
||||
{
|
||||
"schema_version": E46D_CLIP_SCHEMA,
|
||||
"clip_id": (
|
||||
f"e46d-clip-{rank:02d}-{str(signal['signal_id']).removeprefix('e46d-signal-')}"
|
||||
),
|
||||
"rank": rank,
|
||||
"priority": signal["priority"],
|
||||
"kind": signal["kind"],
|
||||
"signal_id": signal["signal_id"],
|
||||
"start_seconds": max(
|
||||
timeline_start,
|
||||
float(signal["start_seconds"]) - float(_PROFILE["review_clip_lead_seconds"]),
|
||||
),
|
||||
"event_start_seconds": float(signal["start_seconds"]),
|
||||
"event_end_seconds": float(signal["end_seconds"]),
|
||||
"end_seconds": min(
|
||||
timeline_end,
|
||||
float(signal["end_seconds"]) + float(_PROFILE["review_clip_tail_seconds"]),
|
||||
),
|
||||
"start_frame": signal["start_frame"],
|
||||
"end_frame": signal["end_frame"],
|
||||
"route_track_ids": copy.deepcopy(signal["route_track_ids"]),
|
||||
"world_track_ids": copy.deepcopy(signal["world_track_ids"]),
|
||||
"evidence": copy.deepcopy(signal["evidence"]),
|
||||
}
|
||||
)
|
||||
return clips
|
||||
|
||||
|
||||
def _signal_score(signal: dict[str, Any]) -> float:
|
||||
base = {
|
||||
"layer-blackout": 100.0,
|
||||
"route-id-rebirth-candidate": 95.0,
|
||||
"bbox-jump": 90.0,
|
||||
"route-layer-gap": 82.0,
|
||||
"camera-evidence-hold": 75.0,
|
||||
"world-binding-flap": 68.0,
|
||||
"motion-state-flap": 62.0,
|
||||
"short-track-burst": 55.0,
|
||||
}.get(str(signal["kind"]), 40.0)
|
||||
evidence = signal.get("evidence")
|
||||
duration = float(evidence.get("duration_seconds", 0.0)) if isinstance(evidence, dict) else 0.0
|
||||
count = 0.0
|
||||
if isinstance(evidence, dict):
|
||||
for key in (
|
||||
"zero_frame_count",
|
||||
"missing_frame_count",
|
||||
"transition_count",
|
||||
"short_track_count",
|
||||
):
|
||||
value = evidence.get(key)
|
||||
if isinstance(value, int | float) and not isinstance(value, bool):
|
||||
count = max(count, float(value))
|
||||
return base + _PRIORITY[str(signal["priority"])] * 10 + min(duration * 5, 20) + min(count, 20)
|
||||
|
||||
|
||||
def _read_e26_frames(
|
||||
root: Path, expected_binding: dict[str, Any]
|
||||
) -> tuple[list[dict[str, Any]], dict[str, str]]:
|
||||
result_path = root / "result.json"
|
||||
expected_result_sha = expected_binding.get("result_sha256")
|
||||
expected_fusion_sha = expected_binding.get("fusion_frames_sha256")
|
||||
if _sha256(result_path) != expected_result_sha:
|
||||
raise E46DTemporalFailureAuditError("E26 result identity changed")
|
||||
result = _read_json(result_path)
|
||||
identity = result.get("identity")
|
||||
artifacts = result.get("artifacts")
|
||||
if (
|
||||
result.get("schema_version") != "missioncore.e10-integrated-perception-result/v1"
|
||||
or result.get("acceptance_state") != "accepted"
|
||||
or not isinstance(identity, dict)
|
||||
or identity.get("source_id") != "sensor.camera.right"
|
||||
or not isinstance(artifacts, list)
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("E26 result contract is invalid")
|
||||
selection = identity.get("selection")
|
||||
if not isinstance(selection, dict) or selection.get("frame_count") != 4489:
|
||||
raise E46DTemporalFailureAuditError("E26 timeline contract is invalid")
|
||||
fusion_artifact = next(
|
||||
(
|
||||
item
|
||||
for item in artifacts
|
||||
if isinstance(item, dict) and item.get("path") == "fusion-frames.jsonl"
|
||||
),
|
||||
None,
|
||||
)
|
||||
fusion_path = _validated_artifact(root, fusion_artifact)
|
||||
if _sha256(fusion_path) != expected_fusion_sha:
|
||||
raise E46DTemporalFailureAuditError("E26 fusion identity changed")
|
||||
frames: list[dict[str, Any]] = []
|
||||
for expected_index, row in enumerate(_read_jsonl(fusion_path)):
|
||||
if row.get("source_frame_index") != expected_index:
|
||||
raise E46DTemporalFailureAuditError("E26 frame sequence changed")
|
||||
frames.append(
|
||||
{
|
||||
"frame_index": expected_index,
|
||||
"session_seconds": row.get("session_seconds"),
|
||||
"objects": [
|
||||
_temporal_object(item)
|
||||
for item in row.get("objects", [])
|
||||
if isinstance(item, dict)
|
||||
],
|
||||
}
|
||||
)
|
||||
_validate_frames(frames, expected_count=4489)
|
||||
if frames[0]["session_seconds"] != selection.get("timeline_start_seconds") or frames[-1][
|
||||
"session_seconds"
|
||||
] != selection.get("timeline_end_seconds"):
|
||||
raise E46DTemporalFailureAuditError("E26 timeline changed")
|
||||
return frames, {
|
||||
"result_id": str(result.get("result_id", root.name)),
|
||||
"result_sha256": str(expected_result_sha),
|
||||
"fusion_frames_sha256": str(expected_fusion_sha),
|
||||
}
|
||||
|
||||
|
||||
def _temporal_object(item: dict[str, Any]) -> dict[str, Any]:
|
||||
box = item.get("bbox_xyxy")
|
||||
source_track_id = item.get("source_track_id")
|
||||
if (
|
||||
not isinstance(box, list)
|
||||
or len(box) != 4
|
||||
or not all(_finite(value) for value in box)
|
||||
or not isinstance(source_track_id, int)
|
||||
or isinstance(source_track_id, bool)
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("E26 temporal object is invalid")
|
||||
world_track_id = item.get("track_id")
|
||||
if (
|
||||
not isinstance(world_track_id, int)
|
||||
or isinstance(world_track_id, bool)
|
||||
or world_track_id == source_track_id
|
||||
):
|
||||
world_track_id = None
|
||||
return {
|
||||
"bbox_xyxy": [float(value) for value in box],
|
||||
"category": str(item.get("label", "unknown")),
|
||||
"route_track_id": source_track_id,
|
||||
"world_track_id": world_track_id,
|
||||
"motion_state": str(item.get("motion_state", "unknown")),
|
||||
"camera_evidence_current": item.get("camera_evidence_current") is True,
|
||||
}
|
||||
|
||||
|
||||
def _validate_frames(frames: list[dict[str, Any]], expected_count: int | None = None) -> None:
|
||||
if expected_count is not None and len(frames) != expected_count:
|
||||
raise E46DTemporalFailureAuditError("E46D frame count is invalid")
|
||||
if len(frames) < 2:
|
||||
raise E46DTemporalFailureAuditError("E46D requires a temporal sequence")
|
||||
previous_seconds = -math.inf
|
||||
for expected_index, frame in enumerate(frames):
|
||||
seconds = frame.get("session_seconds")
|
||||
if (
|
||||
frame.get("frame_index") != expected_index
|
||||
or not _finite(seconds)
|
||||
or float(seconds) <= previous_seconds
|
||||
or not isinstance(frame.get("objects"), list)
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("E46D temporal sequence is invalid")
|
||||
previous_seconds = float(seconds)
|
||||
for item in frame["objects"]:
|
||||
if not isinstance(item, dict):
|
||||
raise E46DTemporalFailureAuditError("E46D temporal object is invalid")
|
||||
|
||||
|
||||
def _iou(left: list[float], right: list[float]) -> float:
|
||||
x1, y1 = max(left[0], right[0]), max(left[1], right[1])
|
||||
x2, y2 = min(left[2], right[2]), min(left[3], right[3])
|
||||
intersection = max(0.0, x2 - x1) * max(0.0, y2 - y1)
|
||||
union = _box_area(left) + _box_area(right) - intersection
|
||||
return intersection / union if union > 0 else 0.0
|
||||
|
||||
|
||||
def _box_area(box: list[float]) -> float:
|
||||
return max(0.0, box[2] - box[0]) * max(0.0, box[3] - box[1])
|
||||
|
||||
|
||||
def _finite(value: object) -> bool:
|
||||
return (
|
||||
isinstance(value, int | float)
|
||||
and not isinstance(value, bool)
|
||||
and math.isfinite(float(value))
|
||||
)
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, raw: object) -> Path:
|
||||
if not isinstance(raw, dict):
|
||||
raise E46DTemporalFailureAuditError("artifact metadata is invalid")
|
||||
relative = raw.get("path")
|
||||
expected_sha = raw.get("sha256")
|
||||
expected_size = raw.get("byte_length", raw.get("size_bytes"))
|
||||
if (
|
||||
not isinstance(relative, str)
|
||||
or not isinstance(expected_sha, str)
|
||||
or not isinstance(expected_size, int)
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("artifact metadata is invalid")
|
||||
path = (root / relative).resolve(strict=True)
|
||||
if (
|
||||
path.parent != root
|
||||
or path.is_symlink()
|
||||
or not path.is_file()
|
||||
or path.stat().st_size != expected_size
|
||||
or _sha256(path) != expected_sha
|
||||
):
|
||||
raise E46DTemporalFailureAuditError("artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, object]:
|
||||
return {
|
||||
"path": path.name,
|
||||
"role": role,
|
||||
"media_type": "application/x-ndjson" if path.suffix == ".jsonl" else "application/json",
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise E46DTemporalFailureAuditError("JSON document is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path):
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
for line in handle:
|
||||
if line.strip():
|
||||
value = json.loads(line)
|
||||
if not isinstance(value, dict):
|
||||
raise E46DTemporalFailureAuditError("JSONL row is invalid")
|
||||
yield value
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, rows: list[dict[str, Any]]) -> None:
|
||||
with path.open("wb") as handle:
|
||||
for row in rows:
|
||||
handle.write(_canonical_json(row) + b"\n")
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for block in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(block)
|
||||
return digest.hexdigest()
|
||||
@@ -0,0 +1,772 @@
|
||||
"""Admit one stock NVIDIA detector/tracker replay as immutable E46E evidence.
|
||||
|
||||
The module deliberately contains no association, hold, stitch, NMS, or tracking
|
||||
logic. It only validates and projects DeepStream detector/NvDCF KITTI output
|
||||
onto the exact recorded RIGHT-camera timeline.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter, defaultdict
|
||||
from collections.abc import Iterable
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
E46E_PROFILE_SCHEMA: Final = "missioncore.e46e-ready-stack-profile/v1"
|
||||
E46E_RUNTIME_SCHEMA: Final = "missioncore.e46e-deepstream-runtime/v1"
|
||||
E46E_RESULT_SCHEMA: Final = "missioncore.e46e-ready-stack-result/v1"
|
||||
E46E_REPORT_SCHEMA: Final = "missioncore.e46e-ready-stack-report/v1"
|
||||
E46E_FRAME_SCHEMA: Final = "missioncore.e46e-ready-stack-frame/v1"
|
||||
E46E_PACKAGE_SCHEMA: Final = "missioncore.e46e-worker-package/v1"
|
||||
E46E_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46E_REPORT_NAME: Final = "ready-stack-report.json"
|
||||
E46E_FRAMES_NAME: Final = "tracked-frames.jsonl"
|
||||
E46E_OVERLAY_NAME: Final = "overlay.mp4"
|
||||
E46E_RUNTIME_NAME: Final = "runtime.json"
|
||||
E46E_LOG_NAME: Final = "deepstream.log"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46e-ready-stack-[a-f0-9]{64}$")
|
||||
_KITTI_NAME = re.compile(r"^\d{2}_\d{3}_(\d{6})\.txt$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46EReadyStackError(ValueError):
|
||||
"""Raised when source, raw NVIDIA output, or immutable result is invalid."""
|
||||
|
||||
|
||||
def build_e46e_ready_stack(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Validate raw DeepStream output and freeze a content-addressed result."""
|
||||
|
||||
profile_source = profile_path.resolve(strict=True)
|
||||
profile = _read_json(profile_source)
|
||||
_validate_profile(profile)
|
||||
source = _read_source(source_job_root.resolve(strict=True), profile)
|
||||
raw = raw_root.resolve(strict=True)
|
||||
if raw.is_symlink():
|
||||
raise E46EReadyStackError("E46E raw root must not be a symlink")
|
||||
runtime = _read_json(raw / E46E_RUNTIME_NAME)
|
||||
_validate_runtime(runtime, profile)
|
||||
overlay = _regular_file(raw / E46E_OVERLAY_NAME)
|
||||
log = _regular_file(raw / E46E_LOG_NAME, allow_empty=True)
|
||||
if runtime["overlay_sha256"] != _sha256(overlay):
|
||||
raise E46EReadyStackError("E46E runtime overlay identity changed")
|
||||
detector_files = _indexed_kitti_files(raw / "detections", source["frame_count"])
|
||||
tracker_files = _indexed_kitti_files(raw / "tracks", source["frame_count"])
|
||||
|
||||
frames: list[dict[str, Any]] = []
|
||||
for frame_index, source_row in enumerate(source["index"]):
|
||||
detections = _parse_detector_file(detector_files[frame_index])
|
||||
objects = _parse_tracker_file(tracker_files[frame_index])
|
||||
frames.append(
|
||||
{
|
||||
"schema_version": E46E_FRAME_SCHEMA,
|
||||
"frame_index": frame_index,
|
||||
"sequence": int(source_row["sequence"]),
|
||||
"session_seconds": source["timeline_start_seconds"]
|
||||
+ (
|
||||
int(source_row["session_monotonic_ns"])
|
||||
- source["first_session_monotonic_ns"]
|
||||
)
|
||||
/ 1_000_000_000.0,
|
||||
"source_image_sha256": str(source_row["sha256"]),
|
||||
"detection_count": len(detections),
|
||||
"tracked_object_count": len(objects),
|
||||
"detections": detections,
|
||||
"objects": objects,
|
||||
}
|
||||
)
|
||||
metrics = analyze_e46e_frames(frames)
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": str(profile["profile_id"]),
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": str(profile["source"]["job_id"]),
|
||||
"version": "immutable recorded RIGHT replay",
|
||||
"role": "exact recorded camera evidence",
|
||||
"identity_sha256": source["job_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": str(profile["detector"]["name"]),
|
||||
"version": str(profile["detector"]["version"]),
|
||||
"role": "framewise traffic-object detection",
|
||||
"identity_sha256": str(profile["detector"]["model_sha256"]),
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": str(profile["parser"]["name"]),
|
||||
"version": str(profile["parser"]["commit"]),
|
||||
"role": "official RT-DETR output decoding",
|
||||
"identity_sha256": str(runtime["parser_library_sha256"]),
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": str(profile["tracker"]["name"]),
|
||||
"version": str(profile["tracker"]["configuration"]),
|
||||
"role": "route-local temporal association",
|
||||
"identity_sha256": str(runtime["tracker_config_sha256"]),
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "NVIDIA DeepStream",
|
||||
"version": str(profile["runtime"]["deepstream_version"]),
|
||||
"role": "GPU inference and media pipeline",
|
||||
"identity_sha256": str(runtime["container_image_digest"]),
|
||||
},
|
||||
],
|
||||
}
|
||||
report_basis = {
|
||||
"schema_version": E46E_REPORT_SCHEMA,
|
||||
"status": "completed-stock-nvidia-recorded-right-replay",
|
||||
"metrics": metrics,
|
||||
"acceptance": {
|
||||
"full_route_accounted": metrics["frame_count"]
|
||||
== int(profile["source"]["segment_count"]),
|
||||
"stock_detector_tracker_executed": True,
|
||||
"visual_overlay_available": True,
|
||||
"independent_truth_available": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"ready_stack_baseline_available": True,
|
||||
"custom_temporal_logic_used": False,
|
||||
"next_action": (
|
||||
"review the full overlay and compare the same objective failure metrics "
|
||||
"against the frozen YOLOX custom-temporal baseline before promotion"
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"TrafficCamNet Transformer Lite has four traffic classes; detections outside "
|
||||
"bicycle, car, person, and road_sign are not claimed"
|
||||
),
|
||||
(
|
||||
"NvDCF IDs are route-local tracker identities, not permanent physical identities"
|
||||
),
|
||||
(
|
||||
f"{metrics['track_box_clipped_count']} stock NvDCF observations crossed the "
|
||||
"800x600 source boundary; the Mission Core evidence adapter clips only their "
|
||||
"display geometry and preserves every raw LTRB coordinate, ID, class, and score"
|
||||
),
|
||||
(
|
||||
"this is recorded RIGHT-camera evidence only; no LEFT camera, live hardware, "
|
||||
"free-space, command, navigation, or safety authority is introduced"
|
||||
),
|
||||
(
|
||||
"without independent frame truth, counts describe output continuity and cannot "
|
||||
"alone establish precision or recall"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
raw_identity = {
|
||||
"runtime_schema": runtime["schema_version"],
|
||||
"worker_host": runtime["worker_host"],
|
||||
"gpu_name": runtime["gpu_name"],
|
||||
"container_image": runtime["container_image"],
|
||||
"container_image_digest": runtime["container_image_digest"],
|
||||
"model_sha256": runtime["model_sha256"],
|
||||
"model_engine_sha256": runtime["model_engine_sha256"],
|
||||
"deepstream_config_sha256": runtime["deepstream_config_sha256"],
|
||||
"detector_config_sha256": runtime["detector_config_sha256"],
|
||||
"parser_library_sha256": runtime["parser_library_sha256"],
|
||||
"tracker_config_sha256": runtime["tracker_config_sha256"],
|
||||
"input_stream_sha256": runtime["input_stream_sha256"],
|
||||
"overlay_sha256": _sha256(overlay),
|
||||
"deepstream_log_sha256": _sha256(log),
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46E_RESULT_SCHEMA,
|
||||
"source": source["identity"],
|
||||
"profile_sha256": _sha256(profile_source),
|
||||
"profile": copy.deepcopy(profile),
|
||||
"raw_execution": raw_identity,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"frames_sha256": hashlib.sha256(_canonical_json(frames)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46e-ready-stack-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46e_ready_stack(destination)
|
||||
|
||||
created_at_utc = datetime.now(UTC).isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
)
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": str(profile["source"]["session_id"]),
|
||||
"camera_source_id": str(profile["source"]["camera_source_id"]),
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46E_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46E_FRAMES_NAME, frames)
|
||||
shutil.copyfile(overlay, staging / E46E_OVERLAY_NAME)
|
||||
shutil.copyfile(raw / E46E_RUNTIME_NAME, staging / E46E_RUNTIME_NAME)
|
||||
shutil.copyfile(log, staging / E46E_LOG_NAME)
|
||||
artifacts = [
|
||||
_artifact(staging / E46E_REPORT_NAME, "ready-stack-report"),
|
||||
_artifact(staging / E46E_FRAMES_NAME, "tracked-frames"),
|
||||
_artifact(staging / E46E_OVERLAY_NAME, "visual-overlay-video"),
|
||||
_artifact(staging / E46E_RUNTIME_NAME, "runtime-record"),
|
||||
_artifact(staging / E46E_LOG_NAME, "deepstream-log"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46E_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46E_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "stock-ready-stack-recorded-diagnostic",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46e_ready_stack(destination)
|
||||
|
||||
|
||||
def read_e46e_ready_stack(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully validate an immutable E46E result."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
if resolved.is_symlink():
|
||||
raise E46EReadyStackError("E46E result root must not be a symlink")
|
||||
manifest = _read_json(resolved / E46E_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
digest = (
|
||||
hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if isinstance(identity, dict)
|
||||
else ""
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != E46E_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != f"e46e-ready-stack-{digest}"
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46EReadyStackError("E46E result identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 5:
|
||||
raise E46EReadyStackError("E46E artifact inventory is invalid")
|
||||
by_role = {row.get("role"): row for row in artifacts if isinstance(row, dict)}
|
||||
paths = {
|
||||
role: _validated_artifact(resolved, by_role.get(role))
|
||||
for role in (
|
||||
"ready-stack-report",
|
||||
"tracked-frames",
|
||||
"visual-overlay-video",
|
||||
"runtime-record",
|
||||
"deepstream-log",
|
||||
)
|
||||
}
|
||||
report = _read_json(paths["ready-stack-report"])
|
||||
frames = tuple(_read_jsonl(paths["tracked-frames"]))
|
||||
runtime = _read_json(paths["runtime-record"])
|
||||
if (
|
||||
report.get("schema_version") != E46E_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or runtime.get("schema_version") != E46E_RUNTIME_SCHEMA
|
||||
or any(row.get("schema_version") != E46E_FRAME_SCHEMA for row in frames)
|
||||
or hashlib.sha256(_canonical_json(frames)).hexdigest()
|
||||
!= identity.get("frames_sha256")
|
||||
or report.get("metrics", {}).get("frame_count") != len(frames)
|
||||
):
|
||||
raise E46EReadyStackError("E46E result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"frames": frames,
|
||||
"runtime": runtime,
|
||||
"overlay_path": paths["visual-overlay-video"],
|
||||
}
|
||||
|
||||
|
||||
def analyze_e46e_frames(frames: Iterable[dict[str, Any]]) -> dict[str, Any]:
|
||||
"""Calculate objective continuity metrics without modifying tracker output."""
|
||||
|
||||
rows = list(frames)
|
||||
if not rows:
|
||||
raise E46EReadyStackError("E46E requires at least one frame")
|
||||
detection_observations = 0
|
||||
track_observations = 0
|
||||
detection_box_clips = 0
|
||||
track_box_clips = 0
|
||||
zero_detection_frames = 0
|
||||
zero_track_frames = 0
|
||||
recovered_frames = 0
|
||||
detector_classes: Counter[str] = Counter()
|
||||
tracker_classes: Counter[str] = Counter()
|
||||
observations: dict[int, list[tuple[int, str]]] = defaultdict(list)
|
||||
track_counts: list[int] = []
|
||||
for expected, frame in enumerate(rows):
|
||||
if frame.get("frame_index") != expected:
|
||||
raise E46EReadyStackError("E46E frame sequence is not contiguous")
|
||||
detections = frame.get("detections")
|
||||
objects = frame.get("objects")
|
||||
if not isinstance(detections, list) or not isinstance(objects, list):
|
||||
raise E46EReadyStackError("E46E frame payload is invalid")
|
||||
detection_observations += len(detections)
|
||||
track_observations += len(objects)
|
||||
detection_box_clips += sum(
|
||||
item.get("source_plane_clipped") is True for item in detections
|
||||
)
|
||||
track_box_clips += sum(
|
||||
item.get("source_plane_clipped") is True for item in objects
|
||||
)
|
||||
zero_detection_frames += not detections
|
||||
zero_track_frames += not objects
|
||||
recovered_frames += not detections and bool(objects)
|
||||
track_counts.append(len(objects))
|
||||
detector_classes.update(str(item["class_name"]) for item in detections)
|
||||
tracker_classes.update(str(item["class_name"]) for item in objects)
|
||||
for item in objects:
|
||||
observations[int(item["source_track_id"])].append(
|
||||
(expected, str(item["class_name"]))
|
||||
)
|
||||
|
||||
route_gap_events = 0
|
||||
short_tracks = 0
|
||||
class_switches = 0
|
||||
for track_rows in observations.values():
|
||||
ordered = sorted(track_rows)
|
||||
short_tracks += len(ordered) <= 3
|
||||
route_gap_events += sum(
|
||||
next_frame - frame > 1
|
||||
for (frame, _), (next_frame, _) in zip(
|
||||
ordered, ordered[1:], strict=False
|
||||
)
|
||||
)
|
||||
class_switches += sum(
|
||||
class_name != next_class
|
||||
for (_, class_name), (_, next_class) in zip(
|
||||
ordered, ordered[1:], strict=False
|
||||
)
|
||||
)
|
||||
|
||||
blackouts = 0
|
||||
start: int | None = None
|
||||
for index, count in enumerate(track_counts + [1]):
|
||||
if count == 0 and start is None:
|
||||
start = index
|
||||
elif count > 0 and start is not None:
|
||||
if start > 0 and index < len(track_counts) and index - start >= 3:
|
||||
blackouts += 1
|
||||
start = None
|
||||
duration = float(rows[-1]["session_seconds"]) - float(rows[0]["session_seconds"])
|
||||
return {
|
||||
"frame_count": len(rows),
|
||||
"route_duration_seconds": round(max(0.0, duration), 6),
|
||||
"detection_observation_count": detection_observations,
|
||||
"track_observation_count": track_observations,
|
||||
"detection_box_clipped_count": detection_box_clips,
|
||||
"track_box_clipped_count": track_box_clips,
|
||||
"unique_track_count": len(observations),
|
||||
"mean_tracked_objects_per_frame": round(track_observations / len(rows), 6),
|
||||
"zero_detection_frame_count": zero_detection_frames,
|
||||
"zero_track_frame_count": zero_track_frames,
|
||||
"tracker_recovered_frame_count": recovered_frames,
|
||||
"full_layer_blackout_event_count": blackouts,
|
||||
"route_id_gap_event_count": route_gap_events,
|
||||
"short_track_count": short_tracks,
|
||||
"short_track_fraction": round(short_tracks / max(1, len(observations)), 6),
|
||||
"track_class_switch_count": class_switches,
|
||||
"detector_class_observations": dict(sorted(detector_classes.items())),
|
||||
"tracker_class_observations": dict(sorted(tracker_classes.items())),
|
||||
}
|
||||
|
||||
|
||||
def _read_source(root: Path, profile: dict[str, Any]) -> dict[str, Any]:
|
||||
job_path = _regular_file(root / "job.json")
|
||||
job = _read_json(job_path)
|
||||
expected = profile["source"]
|
||||
input_value = job.get("input")
|
||||
if not isinstance(input_value, dict):
|
||||
raise E46EReadyStackError("E46E source job input is invalid")
|
||||
camera = root / "input" / "camera" / str(expected["camera_source_id"]) / "epoch-1"
|
||||
summary_path = _regular_file(camera / "summary.json")
|
||||
index_path = _regular_file(camera / "index.jsonl")
|
||||
summary = _read_json(summary_path)
|
||||
index = list(_read_jsonl(index_path))
|
||||
frame_count = int(expected["segment_count"])
|
||||
timeline = input_value.get("timeline")
|
||||
if (
|
||||
job.get("schema_version") != "missioncore.compute-job/v1"
|
||||
or job.get("job_id") != expected["job_id"]
|
||||
or input_value.get("session_id") != expected["session_id"]
|
||||
or input_value.get("source_id") != expected["camera_source_id"]
|
||||
or input_value.get("segment_count") != frame_count
|
||||
or input_value.get("archive_index_sha256") != expected["archive_index_sha256"]
|
||||
or input_value.get("archive_summary_sha256") != expected["archive_summary_sha256"]
|
||||
or summary.get("stream_sha256") != expected["stream_sha256"]
|
||||
or summary.get("segment_count") != frame_count
|
||||
or _sha256(index_path) != expected["archive_index_sha256"]
|
||||
or _sha256(summary_path) != expected["archive_summary_sha256"]
|
||||
or not isinstance(timeline, dict)
|
||||
or not isinstance(timeline.get("start_seconds"), (int, float))
|
||||
or len(index) != frame_count
|
||||
):
|
||||
raise E46EReadyStackError("E46E exact recorded source binding changed")
|
||||
for position, row in enumerate(index, start=1):
|
||||
if (
|
||||
row.get("schema_version") != "missioncore.camera-recording-index/v1"
|
||||
or row.get("kind") != "media"
|
||||
or row.get("sequence") != position
|
||||
or not isinstance(row.get("session_monotonic_ns"), int)
|
||||
or not _is_sha256(row.get("sha256"))
|
||||
):
|
||||
raise E46EReadyStackError("E46E source index is invalid")
|
||||
return {
|
||||
"frame_count": frame_count,
|
||||
"index": index,
|
||||
"timeline_start_seconds": float(timeline["start_seconds"]),
|
||||
"first_session_monotonic_ns": int(index[0]["session_monotonic_ns"]),
|
||||
"job_sha256": _sha256(job_path),
|
||||
"identity": {
|
||||
"job_id": expected["job_id"],
|
||||
"job_sha256": _sha256(job_path),
|
||||
"session_id": expected["session_id"],
|
||||
"camera_source_id": expected["camera_source_id"],
|
||||
"stream_sha256": expected["stream_sha256"],
|
||||
"archive_index_sha256": expected["archive_index_sha256"],
|
||||
"archive_summary_sha256": expected["archive_summary_sha256"],
|
||||
"frame_count": frame_count,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _validate_profile(profile: dict[str, Any]) -> None:
|
||||
try:
|
||||
source = profile["source"]
|
||||
runtime = profile["runtime"]
|
||||
detector = profile["detector"]
|
||||
parser = profile["parser"]
|
||||
tracker = profile["tracker"]
|
||||
output = profile["output"]
|
||||
except KeyError as exc:
|
||||
raise E46EReadyStackError("E46E profile is incomplete") from exc
|
||||
if (
|
||||
profile.get("schema_version") != E46E_PROFILE_SCHEMA
|
||||
or profile.get("authority") != {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
or not _is_sha256(source.get("stream_sha256"))
|
||||
or not _is_sha256(source.get("archive_index_sha256"))
|
||||
or not _is_sha256(source.get("archive_summary_sha256"))
|
||||
or not str(runtime.get("container_image", "")).startswith(
|
||||
"nvcr.io/nvidia/deepstream:9.1-samples-multiarch@sha256:"
|
||||
)
|
||||
or not _is_sha256(detector.get("model_sha256"))
|
||||
or detector.get("custom_postprocessing") is not False
|
||||
or parser.get("repository") != "https://github.com/NVIDIA/DeepStream.git"
|
||||
or parser.get("symbol") != "NvDsInferParseCustomDDETRTAO"
|
||||
or not _is_sha256(parser.get("library_sha256"))
|
||||
or parser.get("custom_mission_core_logic") is not False
|
||||
or tracker.get("custom_association") is not False
|
||||
or tracker.get("custom_hold_or_stitch") is not False
|
||||
or output.get("frame_width") != 800
|
||||
or output.get("frame_height") != 600
|
||||
):
|
||||
raise E46EReadyStackError("E46E profile contract is invalid")
|
||||
|
||||
|
||||
def _validate_runtime(runtime: dict[str, Any], profile: dict[str, Any]) -> None:
|
||||
expected_image = str(profile["runtime"]["container_image"])
|
||||
expected_digest = expected_image.rsplit("@sha256:", 1)[1]
|
||||
required_sha = (
|
||||
"container_image_digest",
|
||||
"model_sha256",
|
||||
"model_engine_sha256",
|
||||
"deepstream_config_sha256",
|
||||
"detector_config_sha256",
|
||||
"parser_library_sha256",
|
||||
"tracker_config_sha256",
|
||||
"input_stream_sha256",
|
||||
"overlay_sha256",
|
||||
)
|
||||
if (
|
||||
runtime.get("schema_version") != E46E_RUNTIME_SCHEMA
|
||||
or runtime.get("status") != "completed"
|
||||
or runtime.get("container_image") != expected_image
|
||||
or runtime.get("container_image_digest") != expected_digest
|
||||
or runtime.get("model_sha256") != profile["detector"]["model_sha256"]
|
||||
or runtime.get("parser_library_sha256") != profile["parser"]["library_sha256"]
|
||||
or runtime.get("input_stream_sha256") != profile["source"]["stream_sha256"]
|
||||
or not isinstance(runtime.get("worker_host"), str)
|
||||
or not runtime.get("worker_host")
|
||||
or not isinstance(runtime.get("gpu_name"), str)
|
||||
or not runtime.get("gpu_name")
|
||||
or any(not _is_sha256(runtime.get(name)) for name in required_sha)
|
||||
):
|
||||
raise E46EReadyStackError("E46E runtime identity is invalid")
|
||||
|
||||
|
||||
def _indexed_kitti_files(root: Path, frame_count: int) -> dict[int, Path]:
|
||||
resolved = root.resolve(strict=True)
|
||||
if not resolved.is_dir() or resolved.is_symlink():
|
||||
raise E46EReadyStackError("E46E KITTI directory is invalid")
|
||||
indexed: dict[int, Path] = {}
|
||||
for path in resolved.iterdir():
|
||||
if not path.is_file() or path.is_symlink():
|
||||
raise E46EReadyStackError("E46E KITTI member is invalid")
|
||||
match = _KITTI_NAME.fullmatch(path.name)
|
||||
if match is None:
|
||||
raise E46EReadyStackError("E46E KITTI filename is invalid")
|
||||
frame_index = int(match.group(1))
|
||||
if frame_index in indexed or frame_index >= frame_count:
|
||||
raise E46EReadyStackError("E46E KITTI frame inventory is invalid")
|
||||
indexed[frame_index] = path
|
||||
if set(indexed) != set(range(frame_count)):
|
||||
raise E46EReadyStackError("E46E KITTI frame coverage is incomplete")
|
||||
return indexed
|
||||
|
||||
|
||||
def _parse_detector_file(path: Path) -> list[dict[str, Any]]:
|
||||
output: list[dict[str, Any]] = []
|
||||
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
tokens = line.split()
|
||||
if len(tokens) != 16:
|
||||
raise E46EReadyStackError(f"invalid detector KITTI row {path.name}:{line_number}")
|
||||
box, source_box, clipped = _parse_box(tokens[4:8], path, line_number)
|
||||
confidence = _finite_float(tokens[15], path, line_number)
|
||||
output.append(
|
||||
{
|
||||
"class_name": tokens[0],
|
||||
"bbox": box,
|
||||
"source_bbox_ltrb": source_box,
|
||||
"source_plane_clipped": clipped,
|
||||
"confidence": confidence,
|
||||
"provenance": "nvidia-trafficcamnet-rtdetr",
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def _parse_tracker_file(path: Path) -> list[dict[str, Any]]:
|
||||
output: list[dict[str, Any]] = []
|
||||
seen: dict[int, dict[str, Any]] = {}
|
||||
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
tokens = line.split()
|
||||
if len(tokens) < 17:
|
||||
raise E46EReadyStackError(f"invalid tracker KITTI row {path.name}:{line_number}")
|
||||
try:
|
||||
source_track_id = int(tokens[1])
|
||||
except ValueError as exc:
|
||||
raise E46EReadyStackError(
|
||||
f"invalid tracker id {path.name}:{line_number}"
|
||||
) from exc
|
||||
if source_track_id < 0:
|
||||
raise E46EReadyStackError("negative NvDCF track id")
|
||||
box, source_box, clipped = _parse_box(tokens[5:9], path, line_number)
|
||||
item = {
|
||||
"object_id": f"nvdcf-{source_track_id}",
|
||||
"source_track_id": source_track_id,
|
||||
"class_name": tokens[0],
|
||||
"bbox": box,
|
||||
"source_bbox_ltrb": source_box,
|
||||
"source_plane_clipped": clipped,
|
||||
"confidence": _finite_float(tokens[16], path, line_number),
|
||||
"provenance": "nvidia-nvdcf-stock-output",
|
||||
}
|
||||
previous = seen.get(source_track_id)
|
||||
if previous is not None and previous != item:
|
||||
raise E46EReadyStackError(
|
||||
f"conflicting NvDCF track rows {path.name}:{source_track_id}"
|
||||
)
|
||||
if previous is None:
|
||||
seen[source_track_id] = item
|
||||
output.append(item)
|
||||
return output
|
||||
|
||||
|
||||
def _parse_box(
|
||||
tokens: list[str], path: Path, line_number: int
|
||||
) -> tuple[list[float], list[float], bool]:
|
||||
left, top, right, bottom = (
|
||||
_finite_float(token, path, line_number) for token in tokens
|
||||
)
|
||||
if right <= left or bottom <= top:
|
||||
raise E46EReadyStackError(f"invalid bounding box {path.name}:{line_number}")
|
||||
projected_left = max(0.0, min(800.0, left))
|
||||
projected_top = max(0.0, min(600.0, top))
|
||||
projected_right = max(0.0, min(800.0, right))
|
||||
projected_bottom = max(0.0, min(600.0, bottom))
|
||||
if projected_right <= projected_left or projected_bottom <= projected_top:
|
||||
raise E46EReadyStackError(f"bounding box outside source plane {path.name}:{line_number}")
|
||||
source = [left, top, right, bottom]
|
||||
projected = [
|
||||
projected_left,
|
||||
projected_top,
|
||||
projected_right - projected_left,
|
||||
projected_bottom - projected_top,
|
||||
]
|
||||
return projected, source, source != [
|
||||
projected_left,
|
||||
projected_top,
|
||||
projected_right,
|
||||
projected_bottom,
|
||||
]
|
||||
|
||||
|
||||
def _finite_float(token: str, path: Path, line_number: int) -> float:
|
||||
try:
|
||||
value = float(token)
|
||||
except ValueError as exc:
|
||||
raise E46EReadyStackError(f"invalid float {path.name}:{line_number}") from exc
|
||||
if not math.isfinite(value):
|
||||
raise E46EReadyStackError(f"non-finite float {path.name}:{line_number}")
|
||||
return value
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, row: object) -> Path:
|
||||
if not isinstance(row, dict):
|
||||
raise E46EReadyStackError("E46E artifact descriptor is invalid")
|
||||
relative = row.get("path")
|
||||
if (
|
||||
not isinstance(relative, str)
|
||||
or Path(relative).is_absolute()
|
||||
or ".." in Path(relative).parts
|
||||
):
|
||||
raise E46EReadyStackError("E46E artifact path is invalid")
|
||||
path = root / relative
|
||||
if (
|
||||
not path.is_file()
|
||||
or path.is_symlink()
|
||||
or row.get("byte_length") != path.stat().st_size
|
||||
or row.get("sha256") != _sha256(path)
|
||||
):
|
||||
raise E46EReadyStackError("E46E artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, Any]:
|
||||
return {
|
||||
"role": role,
|
||||
"path": path.name,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _regular_file(path: Path, *, allow_empty: bool = False) -> Path:
|
||||
resolved = path.resolve(strict=True)
|
||||
if (
|
||||
not resolved.is_file()
|
||||
or resolved.is_symlink()
|
||||
or (not allow_empty and resolved.stat().st_size == 0)
|
||||
):
|
||||
raise E46EReadyStackError(f"E46E file is invalid: {path.name}")
|
||||
return resolved
|
||||
|
||||
|
||||
def _is_sha256(value: object) -> bool:
|
||||
return isinstance(value, str) and re.fullmatch(r"[a-f0-9]{64}", value) is not None
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False).encode()
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
while chunk := stream.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8-sig"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise E46EReadyStackError(f"invalid JSON: {path.name}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise E46EReadyStackError(f"JSON object expected: {path.name}")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> Iterable[dict[str, Any]]:
|
||||
with path.open("r", encoding="utf-8-sig") as stream:
|
||||
for line_number, line in enumerate(stream, 1):
|
||||
try:
|
||||
row = json.loads(line)
|
||||
except json.JSONDecodeError as exc:
|
||||
raise E46EReadyStackError(
|
||||
f"invalid JSONL: {path.name}:{line_number}"
|
||||
) from exc
|
||||
if not isinstance(row, dict):
|
||||
raise E46EReadyStackError(f"JSON object expected: {path.name}:{line_number}")
|
||||
yield row
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
json.dump(value, stream, ensure_ascii=False, indent=2, allow_nan=False)
|
||||
stream.write("\n")
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, rows: Iterable[dict[str, Any]]) -> None:
|
||||
with path.open("x", encoding="utf-8") as stream:
|
||||
for row in rows:
|
||||
stream.write(
|
||||
json.dumps(
|
||||
row,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
)
|
||||
)
|
||||
stream.write("\n")
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
@@ -0,0 +1,433 @@
|
||||
"""Freeze an NVIDIA DashCamNet versus E46E detector-only bake-off as E46F.
|
||||
|
||||
The replay, DeepStream image, FP16 precision, NvDCF configuration, source
|
||||
plane, and evidence adapter are held constant. Only the stock NVIDIA detector
|
||||
and its stock provider post-processing change. Mission Core performs no NMS,
|
||||
association, hold, stitch, or semantic correction in this module.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_indexed_kitti_files,
|
||||
_parse_detector_file,
|
||||
_parse_tracker_file,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_read_source,
|
||||
_regular_file,
|
||||
_sha256,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
_write_jsonl,
|
||||
analyze_e46e_frames,
|
||||
)
|
||||
|
||||
E46F_PROFILE_SCHEMA: Final = "missioncore.e46f-dashcam-bakeoff-profile/v1"
|
||||
E46F_RUNTIME_SCHEMA: Final = "missioncore.e46f-dashcam-deepstream-runtime/v1"
|
||||
E46F_RESULT_SCHEMA: Final = "missioncore.e46f-dashcam-bakeoff-result/v1"
|
||||
E46F_REPORT_SCHEMA: Final = "missioncore.e46f-dashcam-bakeoff-report/v1"
|
||||
E46F_FRAME_SCHEMA: Final = "missioncore.e46f-dashcam-bakeoff-frame/v1"
|
||||
E46F_PACKAGE_SCHEMA: Final = "missioncore.e46f-worker-package/v1"
|
||||
E46F_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46F_REPORT_NAME: Final = "dashcam-bakeoff-report.json"
|
||||
E46F_FRAMES_NAME: Final = "tracked-frames.jsonl"
|
||||
E46F_OVERLAY_NAME: Final = "overlay.mp4"
|
||||
E46F_RUNTIME_NAME: Final = "runtime.json"
|
||||
E46F_LOG_NAME: Final = "deepstream.log"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46f-dashcam-bakeoff-[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46FDashCamBakeoffError(ValueError):
|
||||
"""Raised when the E46F source, execution, or result is invalid."""
|
||||
|
||||
|
||||
def build_e46f_dashcam_bakeoff(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Validate one stock DashCamNet/NvDCF replay and freeze immutable evidence."""
|
||||
|
||||
try:
|
||||
return _build(
|
||||
source_job_root=source_job_root,
|
||||
raw_root=raw_root,
|
||||
profile_path=profile_path,
|
||||
output_root=output_root,
|
||||
)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46FDashCamBakeoffError(str(exc).replace("E46E", "E46F")) from exc
|
||||
|
||||
|
||||
def _build(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
profile_source = profile_path.resolve(strict=True)
|
||||
profile = _read_json(profile_source)
|
||||
_validate_profile(profile)
|
||||
source = _read_source(source_job_root.resolve(strict=True), profile)
|
||||
raw = raw_root.resolve(strict=True)
|
||||
if raw.is_symlink():
|
||||
raise E46FDashCamBakeoffError("E46F raw root must not be a symlink")
|
||||
runtime = _read_json(raw / E46F_RUNTIME_NAME)
|
||||
_validate_runtime(runtime, profile)
|
||||
overlay = _regular_file(raw / E46F_OVERLAY_NAME)
|
||||
log = _regular_file(raw / E46F_LOG_NAME, allow_empty=True)
|
||||
if runtime["overlay_sha256"] != _sha256(overlay):
|
||||
raise E46FDashCamBakeoffError("E46F runtime overlay identity changed")
|
||||
detector_files = _indexed_kitti_files(raw / "detections", source["frame_count"])
|
||||
tracker_files = _indexed_kitti_files(raw / "tracks", source["frame_count"])
|
||||
|
||||
frames: list[dict[str, Any]] = []
|
||||
for frame_index, source_row in enumerate(source["index"]):
|
||||
detections = _parse_detector_file(detector_files[frame_index])
|
||||
for detection in detections:
|
||||
detection["provenance"] = "nvidia-dashcamnet-detectnet-v2"
|
||||
objects = _parse_tracker_file(tracker_files[frame_index])
|
||||
frames.append(
|
||||
{
|
||||
"schema_version": E46F_FRAME_SCHEMA,
|
||||
"frame_index": frame_index,
|
||||
"sequence": int(source_row["sequence"]),
|
||||
"session_seconds": source["timeline_start_seconds"]
|
||||
+ (int(source_row["session_monotonic_ns"]) - source["first_session_monotonic_ns"])
|
||||
/ 1_000_000_000.0,
|
||||
"source_image_sha256": str(source_row["sha256"]),
|
||||
"detection_count": len(detections),
|
||||
"tracked_object_count": len(objects),
|
||||
"detections": detections,
|
||||
"objects": objects,
|
||||
}
|
||||
)
|
||||
metrics = analyze_e46e_frames(frames)
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": str(profile["profile_id"]),
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": str(profile["source"]["job_id"]),
|
||||
"version": "immutable recorded RIGHT replay",
|
||||
"role": "exact E46E/E46F controlled camera evidence",
|
||||
"identity_sha256": source["job_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": str(profile["detector"]["name"]),
|
||||
"version": str(profile["detector"]["version"]),
|
||||
"role": "moving-camera traffic-object detection",
|
||||
"identity_sha256": str(profile["detector"]["model_sha256"]),
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": str(profile["postprocessor"]["name"]),
|
||||
"version": str(profile["postprocessor"]["reference_commit"]),
|
||||
"role": "stock DetectNet_v2 decode and NMS",
|
||||
"identity_sha256": str(runtime["detector_config_sha256"]),
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": str(profile["tracker"]["name"]),
|
||||
"version": str(profile["tracker"]["configuration"]),
|
||||
"role": "route-local temporal association",
|
||||
"identity_sha256": str(runtime["tracker_config_sha256"]),
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "NVIDIA DeepStream",
|
||||
"version": str(profile["runtime"]["deepstream_version"]),
|
||||
"role": "GPU inference and media pipeline",
|
||||
"identity_sha256": str(runtime["container_image_digest"]),
|
||||
},
|
||||
],
|
||||
}
|
||||
report_basis = {
|
||||
"schema_version": E46F_REPORT_SCHEMA,
|
||||
"status": "completed-stock-nvidia-detector-only-bakeoff",
|
||||
"comparison_contract": copy.deepcopy(profile["comparison_contract"]),
|
||||
"metrics": metrics,
|
||||
"acceptance": {
|
||||
"full_route_accounted": metrics["frame_count"]
|
||||
== int(profile["source"]["segment_count"]),
|
||||
"stock_detector_tracker_executed": True,
|
||||
"controlled_detector_only_change": True,
|
||||
"visual_overlay_available": True,
|
||||
"independent_truth_available": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"dashcam_detector_bakeoff_available": True,
|
||||
"custom_temporal_logic_used": False,
|
||||
"next_action": (
|
||||
"perform full-video semantic review against E46E and retain the detector "
|
||||
"only if moving-camera false positives improve without temporal regression"
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"DashCamNet is evaluated by NVIDIA primarily for car detection; person, "
|
||||
"bicycle, and road_sign quality is not claimed by this LAB"
|
||||
),
|
||||
(
|
||||
"the four-class detector cannot represent stroller, facade, vegetation, "
|
||||
"free-space, or dynamic/static motion state"
|
||||
),
|
||||
"NvDCF IDs are route-local tracker identities, not permanent physical identities",
|
||||
(
|
||||
f"{metrics['track_box_clipped_count']} stock NvDCF observations crossed the "
|
||||
"800x600 source boundary; only display geometry is clipped while raw LTRB, "
|
||||
"ID, class, and score remain preserved"
|
||||
),
|
||||
(
|
||||
"this is recorded RIGHT-camera evidence only; no LEFT camera, live hardware, "
|
||||
"LiDAR fusion, free-space, command, navigation, or safety authority is introduced"
|
||||
),
|
||||
(
|
||||
"without independent full-route truth, output counts and continuity cannot "
|
||||
"establish absolute precision or recall"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
raw_identity = {
|
||||
"runtime_schema": runtime["schema_version"],
|
||||
"worker_host": runtime["worker_host"],
|
||||
"gpu_name": runtime["gpu_name"],
|
||||
"container_image": runtime["container_image"],
|
||||
"container_image_digest": runtime["container_image_digest"],
|
||||
"model_sha256": runtime["model_sha256"],
|
||||
"model_engine_sha256": runtime["model_engine_sha256"],
|
||||
"deepstream_config_sha256": runtime["deepstream_config_sha256"],
|
||||
"detector_config_sha256": runtime["detector_config_sha256"],
|
||||
"tracker_config_sha256": runtime["tracker_config_sha256"],
|
||||
"input_stream_sha256": runtime["input_stream_sha256"],
|
||||
"overlay_sha256": _sha256(overlay),
|
||||
"deepstream_log_sha256": _sha256(log),
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46F_RESULT_SCHEMA,
|
||||
"source": source["identity"],
|
||||
"profile_sha256": _sha256(profile_source),
|
||||
"profile": copy.deepcopy(profile),
|
||||
"raw_execution": raw_identity,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"frames_sha256": hashlib.sha256(_canonical_json(frames)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46f-dashcam-bakeoff-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46f_dashcam_bakeoff(destination)
|
||||
|
||||
created_at_utc = datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": str(profile["source"]["session_id"]),
|
||||
"camera_source_id": str(profile["source"]["camera_source_id"]),
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46F_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46F_FRAMES_NAME, frames)
|
||||
shutil.copyfile(overlay, staging / E46F_OVERLAY_NAME)
|
||||
shutil.copyfile(raw / E46F_RUNTIME_NAME, staging / E46F_RUNTIME_NAME)
|
||||
shutil.copyfile(log, staging / E46F_LOG_NAME)
|
||||
artifacts = [
|
||||
_artifact(staging / E46F_REPORT_NAME, "dashcam-bakeoff-report"),
|
||||
_artifact(staging / E46F_FRAMES_NAME, "tracked-frames"),
|
||||
_artifact(staging / E46F_OVERLAY_NAME, "visual-overlay-video"),
|
||||
_artifact(staging / E46F_RUNTIME_NAME, "runtime-record"),
|
||||
_artifact(staging / E46F_LOG_NAME, "deepstream-log"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46F_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46F_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "stock-detector-only-recorded-diagnostic",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46f_dashcam_bakeoff(destination)
|
||||
|
||||
|
||||
def read_e46f_dashcam_bakeoff(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully validate an immutable E46F result."""
|
||||
|
||||
try:
|
||||
resolved = root.resolve(strict=True)
|
||||
if resolved.is_symlink():
|
||||
raise E46FDashCamBakeoffError("E46F result root must not be a symlink")
|
||||
manifest = _read_json(resolved / E46F_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
digest = (
|
||||
hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if isinstance(identity, dict)
|
||||
else ""
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != E46F_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != f"e46f-dashcam-bakeoff-{digest}"
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46FDashCamBakeoffError("E46F result identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 5:
|
||||
raise E46FDashCamBakeoffError("E46F artifact inventory is invalid")
|
||||
by_role = {row.get("role"): row for row in artifacts if isinstance(row, dict)}
|
||||
paths = {
|
||||
role: _validated_artifact(resolved, by_role.get(role))
|
||||
for role in (
|
||||
"dashcam-bakeoff-report",
|
||||
"tracked-frames",
|
||||
"visual-overlay-video",
|
||||
"runtime-record",
|
||||
"deepstream-log",
|
||||
)
|
||||
}
|
||||
report = _read_json(paths["dashcam-bakeoff-report"])
|
||||
frames = tuple(_read_jsonl(paths["tracked-frames"]))
|
||||
runtime = _read_json(paths["runtime-record"])
|
||||
if (
|
||||
report.get("schema_version") != E46F_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or runtime.get("schema_version") != E46F_RUNTIME_SCHEMA
|
||||
or any(row.get("schema_version") != E46F_FRAME_SCHEMA for row in frames)
|
||||
or hashlib.sha256(_canonical_json(frames)).hexdigest() != identity.get("frames_sha256")
|
||||
or report.get("metrics", {}).get("frame_count") != len(frames)
|
||||
):
|
||||
raise E46FDashCamBakeoffError("E46F result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"frames": frames,
|
||||
"runtime": runtime,
|
||||
"overlay_path": paths["visual-overlay-video"],
|
||||
}
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46FDashCamBakeoffError(str(exc).replace("E46E", "E46F")) from exc
|
||||
|
||||
|
||||
def _validate_profile(profile: dict[str, Any]) -> None:
|
||||
try:
|
||||
comparison = profile["comparison_contract"]
|
||||
source = profile["source"]
|
||||
runtime = profile["runtime"]
|
||||
detector = profile["detector"]
|
||||
postprocessor = profile["postprocessor"]
|
||||
tracker = profile["tracker"]
|
||||
output = profile["output"]
|
||||
except KeyError as exc:
|
||||
raise E46FDashCamBakeoffError("E46F profile is incomplete") from exc
|
||||
sha_pattern = re.compile(r"^[a-f0-9]{64}$")
|
||||
if (
|
||||
profile.get("schema_version") != E46F_PROFILE_SCHEMA
|
||||
or comparison.get("controlled_change") != "detector-only"
|
||||
or not re.fullmatch(
|
||||
r"e46e-ready-stack-[a-f0-9]{64}", str(comparison.get("baseline_result_id", ""))
|
||||
)
|
||||
or profile.get("authority")
|
||||
!= {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
or any(
|
||||
sha_pattern.fullmatch(str(source.get(key, ""))) is None
|
||||
for key in ("stream_sha256", "archive_index_sha256", "archive_summary_sha256")
|
||||
)
|
||||
or not str(runtime.get("container_image", "")).startswith(
|
||||
"nvcr.io/nvidia/deepstream:9.1-samples-multiarch@sha256:"
|
||||
)
|
||||
or sha_pattern.fullmatch(str(detector.get("model_sha256", ""))) is None
|
||||
or detector.get("custom_postprocessing") is not False
|
||||
or postprocessor.get("cluster_mode") != "NMS"
|
||||
or postprocessor.get("custom_mission_core_logic") is not False
|
||||
or sha_pattern.fullmatch(str(postprocessor.get("reference_config_sha256", ""))) is None
|
||||
or tracker.get("custom_association") is not False
|
||||
or tracker.get("custom_hold_or_stitch") is not False
|
||||
or output.get("frame_width") != 800
|
||||
or output.get("frame_height") != 600
|
||||
):
|
||||
raise E46FDashCamBakeoffError("E46F profile contract is invalid")
|
||||
|
||||
|
||||
def _validate_runtime(runtime: dict[str, Any], profile: dict[str, Any]) -> None:
|
||||
expected_image = str(profile["runtime"]["container_image"])
|
||||
expected_digest = expected_image.rsplit("@sha256:", 1)[1]
|
||||
sha_pattern = re.compile(r"^[a-f0-9]{64}$")
|
||||
required_sha = (
|
||||
"container_image_digest",
|
||||
"model_sha256",
|
||||
"model_engine_sha256",
|
||||
"deepstream_config_sha256",
|
||||
"detector_config_sha256",
|
||||
"tracker_config_sha256",
|
||||
"input_stream_sha256",
|
||||
"overlay_sha256",
|
||||
)
|
||||
if (
|
||||
runtime.get("schema_version") != E46F_RUNTIME_SCHEMA
|
||||
or runtime.get("status") != "completed"
|
||||
or runtime.get("container_image") != expected_image
|
||||
or runtime.get("container_image_digest") != expected_digest
|
||||
or runtime.get("model_sha256") != profile["detector"]["model_sha256"]
|
||||
or runtime.get("input_stream_sha256") != profile["source"]["stream_sha256"]
|
||||
or not isinstance(runtime.get("worker_host"), str)
|
||||
or not runtime.get("worker_host")
|
||||
or not isinstance(runtime.get("gpu_name"), str)
|
||||
or not runtime.get("gpu_name")
|
||||
or any(sha_pattern.fullmatch(str(runtime.get(name, ""))) is None for name in required_sha)
|
||||
):
|
||||
raise E46FDashCamBakeoffError("E46F runtime identity is invalid")
|
||||
@@ -0,0 +1,688 @@
|
||||
"""Freeze the calibrated E46G ready-detector bake-off.
|
||||
|
||||
E46G changes only the camera geometry presented to the two already-qualified
|
||||
NVIDIA detector providers. Factory KB4 calibration is consumed by NVIDIA
|
||||
``nvdewarper`` and every detector run uses stock DeepStream decoding, NMS and
|
||||
NvDCF. This module is an evidence adapter: it validates and publishes output,
|
||||
but contains no detector, suppression, association, hold or stitch logic.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections.abc import Iterable
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_finite_float,
|
||||
_indexed_kitti_files,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_read_source,
|
||||
_regular_file,
|
||||
_sha256,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
_write_jsonl,
|
||||
analyze_e46e_frames,
|
||||
)
|
||||
|
||||
E46G_PROFILE_SCHEMA: Final = "missioncore.e46g-rectified-detector-bakeoff-profile/v1"
|
||||
E46G_RUNTIME_SCHEMA: Final = "missioncore.e46g-rectified-detector-runtime/v1"
|
||||
E46G_RESULT_SCHEMA: Final = "missioncore.e46g-rectified-detector-bakeoff-result/v1"
|
||||
E46G_REPORT_SCHEMA: Final = "missioncore.e46g-rectified-detector-bakeoff-report/v1"
|
||||
E46G_FRAME_SCHEMA: Final = "missioncore.e46g-rectified-detector-bakeoff-frame/v1"
|
||||
E46G_PACKAGE_SCHEMA: Final = "missioncore.e46g-worker-package/v1"
|
||||
E46G_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46G_REPORT_NAME: Final = "rectified-detector-bakeoff-report.json"
|
||||
E46G_RUNTIME_NAME: Final = "runtime.json"
|
||||
E46G_LOG_NAME: Final = "worker.log"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46g-rectified-detector-bakeoff-[a-f0-9]{64}$")
|
||||
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
||||
_CANDIDATES: Final = ("trafficcamnet", "dashcamnet")
|
||||
_VIEWS: Final = ("left", "front", "right")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46GRectifiedDetectorBakeoffError(ValueError):
|
||||
"""Raised when the E46G source, execution, or result is invalid."""
|
||||
|
||||
|
||||
def build_e46g_rectified_detector_bakeoff(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Validate one calibrated stock-detector A/B and freeze immutable evidence."""
|
||||
|
||||
try:
|
||||
return _build_e46g_rectified_detector_bakeoff(
|
||||
source_job_root=source_job_root,
|
||||
raw_root=raw_root,
|
||||
profile_path=profile_path,
|
||||
output_root=output_root,
|
||||
)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46GRectifiedDetectorBakeoffError(str(exc).replace("E46E", "E46G")) from exc
|
||||
|
||||
|
||||
def _build_e46g_rectified_detector_bakeoff(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
profile_source = profile_path.resolve(strict=True)
|
||||
profile = _read_json(profile_source)
|
||||
_validate_profile(profile)
|
||||
source = _read_source(source_job_root.resolve(strict=True), profile)
|
||||
raw = raw_root.resolve(strict=True)
|
||||
if raw.is_symlink():
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G raw root must not be a symlink")
|
||||
runtime = _read_json(raw / E46G_RUNTIME_NAME)
|
||||
_validate_runtime(runtime, profile, raw)
|
||||
worker_log = _regular_file(raw / E46G_LOG_NAME, allow_empty=True)
|
||||
|
||||
first_source_frame = int(profile["selection"]["first_source_frame_index"])
|
||||
sample_frame_count = int(profile["selection"]["frame_count"])
|
||||
frames_by_run: dict[str, tuple[dict[str, Any], ...]] = {}
|
||||
metrics_by_candidate: dict[str, Any] = {}
|
||||
for candidate in _CANDIDATES:
|
||||
per_view: dict[str, Any] = {}
|
||||
for view in _VIEWS:
|
||||
run_root = raw / "runs" / candidate / view
|
||||
detector_files = _indexed_kitti_files(run_root / "detections", sample_frame_count)
|
||||
tracker_files = _indexed_kitti_files(run_root / "tracks", sample_frame_count)
|
||||
rows: list[dict[str, Any]] = []
|
||||
for sample_frame_index in range(sample_frame_count):
|
||||
source_frame_index = first_source_frame + sample_frame_index
|
||||
source_row = source["index"][source_frame_index]
|
||||
detections = _parse_detector_file(
|
||||
detector_files[sample_frame_index], profile, candidate
|
||||
)
|
||||
objects = _parse_tracker_file(
|
||||
tracker_files[sample_frame_index], profile, candidate, view
|
||||
)
|
||||
rows.append(
|
||||
{
|
||||
"schema_version": E46G_FRAME_SCHEMA,
|
||||
"candidate": candidate,
|
||||
"view": view,
|
||||
"frame_index": sample_frame_index,
|
||||
"source_frame_index": source_frame_index,
|
||||
"sequence": int(source_row["sequence"]),
|
||||
"session_seconds": source["timeline_start_seconds"]
|
||||
+ (
|
||||
int(source_row["session_monotonic_ns"])
|
||||
- source["first_session_monotonic_ns"]
|
||||
)
|
||||
/ 1_000_000_000.0,
|
||||
"source_image_sha256": str(source_row["sha256"]),
|
||||
"detection_count": len(detections),
|
||||
"tracked_object_count": len(objects),
|
||||
"detections": detections,
|
||||
"objects": objects,
|
||||
}
|
||||
)
|
||||
key = f"{candidate}-{view}"
|
||||
frames_by_run[key] = tuple(rows)
|
||||
per_view[view] = _metrics(rows, profile)
|
||||
metrics_by_candidate[candidate] = {
|
||||
"source_frame_count": sample_frame_count,
|
||||
"view_frame_count": sample_frame_count * len(_VIEWS),
|
||||
"views": per_view,
|
||||
"detection_observation_count": sum(
|
||||
item["detection_observation_count"] for item in per_view.values()
|
||||
),
|
||||
"track_observation_count": sum(
|
||||
item["track_observation_count"] for item in per_view.values()
|
||||
),
|
||||
"unique_track_count": sum(item["unique_track_count"] for item in per_view.values()),
|
||||
"large_track_observation_count": sum(
|
||||
item["large_track_observation_count"] for item in per_view.values()
|
||||
),
|
||||
"large_track_fraction": round(
|
||||
sum(item["large_track_observation_count"] for item in per_view.values())
|
||||
/ max(
|
||||
1,
|
||||
sum(item["track_observation_count"] for item in per_view.values()),
|
||||
),
|
||||
6,
|
||||
),
|
||||
}
|
||||
|
||||
comparison_paths = {
|
||||
candidate: _regular_file(raw / "comparison" / f"{candidate}.mp4")
|
||||
for candidate in _CANDIDATES
|
||||
}
|
||||
method = _method(profile, runtime, source)
|
||||
report_basis = {
|
||||
"schema_version": E46G_REPORT_SCHEMA,
|
||||
"status": "completed-awaiting-visual-semantic-adjudication",
|
||||
"comparison_contract": copy.deepcopy(profile["comparison_contract"]),
|
||||
"selection": copy.deepcopy(profile["selection"]),
|
||||
"rectification": copy.deepcopy(profile["rectification"]),
|
||||
"metrics": metrics_by_candidate,
|
||||
"acceptance": {
|
||||
"exact_recorded_right_source_bound": True,
|
||||
"factory_calibration_bound": True,
|
||||
"official_nvidia_dewarper_executed": True,
|
||||
"stock_detector_tracker_executed": True,
|
||||
"same_views_and_frames_for_both_candidates": True,
|
||||
"visual_comparison_videos_available": True,
|
||||
"independent_truth_available": False,
|
||||
"candidate_accepted": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"automatic_winner_selected": False,
|
||||
"custom_detector_or_tracker_logic_used": False,
|
||||
"next_action": (
|
||||
"review both synchronized left/front/right videos for semantic false "
|
||||
"positives, missed task objects and edge geometry; then run the selected "
|
||||
"stock detector with NvDCF over the complete rectified route"
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"E46G is a controlled 60-second detector-selection gate, not the complete "
|
||||
"route continuity result"
|
||||
),
|
||||
(
|
||||
"left/front/right are three calibrated projections from one physical RIGHT "
|
||||
"camera, not three cameras"
|
||||
),
|
||||
(
|
||||
"DeepStream 9.1 decoding omits the terminal source frame at EOS; E46G admits "
|
||||
"the timestamp-aligned 0..4487 prefix and the tested 1000..1599 gate is "
|
||||
"unaffected"
|
||||
),
|
||||
("NvDCF identities are view-local; E46G does not invent cross-view identity stitching"),
|
||||
(
|
||||
"the two ready detectors expose only their provider taxonomies and do not "
|
||||
"establish free-space or dynamic/static state"
|
||||
),
|
||||
(
|
||||
"without independent exhaustive truth, numerical counts cannot select a "
|
||||
"winner without visual semantic review"
|
||||
),
|
||||
(
|
||||
"recorded RIGHT evidence introduces no LEFT camera, live hardware, command, "
|
||||
"navigation or safety authority"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
raw_identity = {
|
||||
"runtime_schema": runtime["schema_version"],
|
||||
"worker_host": runtime["worker_host"],
|
||||
"gpu_name": runtime["gpu_name"],
|
||||
"container_image": runtime["container_image"],
|
||||
"container_image_digest": runtime["container_image_digest"],
|
||||
"source_stream_sha256": runtime["source_stream_sha256"],
|
||||
"geometry": copy.deepcopy(runtime["geometry"]),
|
||||
"candidates": copy.deepcopy(runtime["candidates"]),
|
||||
"comparison": copy.deepcopy(runtime["comparison"]),
|
||||
"worker_log_sha256": _sha256(worker_log),
|
||||
}
|
||||
frames_identity = {
|
||||
key: hashlib.sha256(_canonical_json(rows)).hexdigest()
|
||||
for key, rows in sorted(frames_by_run.items())
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46G_RESULT_SCHEMA,
|
||||
"source": source["identity"],
|
||||
"profile_sha256": _sha256(profile_source),
|
||||
"profile": copy.deepcopy(profile),
|
||||
"raw_execution": raw_identity,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"frames_sha256": frames_identity,
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46g-rectified-detector-bakeoff-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46g_rectified_detector_bakeoff(destination)
|
||||
|
||||
created_at_utc = datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": str(profile["source"]["session_id"]),
|
||||
"camera_source_id": str(profile["source"]["camera_source_id"]),
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46G_REPORT_NAME, report)
|
||||
for key, rows in frames_by_run.items():
|
||||
_write_jsonl(staging / f"{key}.jsonl", rows)
|
||||
for candidate, source_path in comparison_paths.items():
|
||||
shutil.copyfile(source_path, staging / f"{candidate}.mp4")
|
||||
shutil.copyfile(raw / E46G_RUNTIME_NAME, staging / E46G_RUNTIME_NAME)
|
||||
shutil.copyfile(worker_log, staging / E46G_LOG_NAME)
|
||||
artifacts = [
|
||||
_artifact(staging / E46G_REPORT_NAME, "rectified-detector-bakeoff-report"),
|
||||
*[
|
||||
_artifact(staging / f"{key}.jsonl", f"tracked-frames-{key}")
|
||||
for key in sorted(frames_by_run)
|
||||
],
|
||||
*[
|
||||
_artifact(staging / f"{candidate}.mp4", f"comparison-video-{candidate}")
|
||||
for candidate in _CANDIDATES
|
||||
],
|
||||
_artifact(staging / E46G_RUNTIME_NAME, "runtime-record"),
|
||||
_artifact(staging / E46G_LOG_NAME, "worker-log"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46G_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46G_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "rectified-detector-bakeoff-awaiting-visual-review",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46g_rectified_detector_bakeoff(destination)
|
||||
|
||||
|
||||
def read_e46g_rectified_detector_bakeoff(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully validate an immutable E46G result."""
|
||||
|
||||
try:
|
||||
return _read_e46g_rectified_detector_bakeoff(root)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46GRectifiedDetectorBakeoffError(str(exc).replace("E46E", "E46G")) from exc
|
||||
|
||||
|
||||
def _read_e46g_rectified_detector_bakeoff(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
if resolved.is_symlink():
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G result root must not be a symlink")
|
||||
manifest = _read_json(resolved / E46G_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
digest = (
|
||||
hashlib.sha256(_canonical_json(identity)).hexdigest() if isinstance(identity, dict) else ""
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != E46G_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != f"e46g-rectified-detector-bakeoff-{digest}"
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G result identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 11:
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G artifact inventory is invalid")
|
||||
by_role = {row.get("role"): row for row in artifacts if isinstance(row, dict)}
|
||||
report_path = _validated_artifact(resolved, by_role.get("rectified-detector-bakeoff-report"))
|
||||
runtime_path = _validated_artifact(resolved, by_role.get("runtime-record"))
|
||||
_validated_artifact(resolved, by_role.get("worker-log"))
|
||||
comparison_paths = {
|
||||
candidate: _validated_artifact(resolved, by_role.get(f"comparison-video-{candidate}"))
|
||||
for candidate in _CANDIDATES
|
||||
}
|
||||
frames: dict[str, tuple[dict[str, Any], ...]] = {}
|
||||
for candidate in _CANDIDATES:
|
||||
for view in _VIEWS:
|
||||
key = f"{candidate}-{view}"
|
||||
path = _validated_artifact(resolved, by_role.get(f"tracked-frames-{key}"))
|
||||
rows = tuple(_read_jsonl(path))
|
||||
if hashlib.sha256(_canonical_json(rows)).hexdigest() != identity.get(
|
||||
"frames_sha256", {}
|
||||
).get(key) or any(row.get("schema_version") != E46G_FRAME_SCHEMA for row in rows):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G frames changed")
|
||||
frames[key] = rows
|
||||
report = _read_json(report_path)
|
||||
runtime = _read_json(runtime_path)
|
||||
if (
|
||||
report.get("schema_version") != E46G_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or runtime.get("schema_version") != E46G_RUNTIME_SCHEMA
|
||||
):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"frames": frames,
|
||||
"runtime": runtime,
|
||||
"comparison_paths": comparison_paths,
|
||||
}
|
||||
|
||||
|
||||
def _validate_profile(profile: dict[str, Any]) -> None:
|
||||
source = profile.get("source")
|
||||
runtime = profile.get("runtime")
|
||||
calibration = profile.get("calibration")
|
||||
rectification = profile.get("rectification")
|
||||
selection = profile.get("selection")
|
||||
candidates = profile.get("candidates")
|
||||
comparison = profile.get("comparison_contract")
|
||||
authority = profile.get("authority")
|
||||
image = runtime.get("container_image") if isinstance(runtime, dict) else None
|
||||
if (
|
||||
profile.get("schema_version") != E46G_PROFILE_SCHEMA
|
||||
or not isinstance(source, dict)
|
||||
or source.get("camera_source_id") != "sensor.camera.right"
|
||||
or source.get("segment_count") != 4489
|
||||
or not isinstance(image, str)
|
||||
or "@sha256:" not in image
|
||||
or not isinstance(calibration, dict)
|
||||
or calibration.get("slot") != "camera_1"
|
||||
or calibration.get("model") != "KB4"
|
||||
or calibration.get("calibration_sha256")
|
||||
!= "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
|
||||
or not isinstance(rectification, dict)
|
||||
or rectification.get("provider") != "NVIDIA Gst-nvdewarper"
|
||||
or rectification.get("projection_type") != 4
|
||||
or tuple(rectification.get("view_order", ())) != _VIEWS
|
||||
or rectification.get("expected_full_frame_count") != 4488
|
||||
or rectification.get("retained_source_frame_index_range") != [0, 4487]
|
||||
or rectification.get("excluded_source_tail_frame_count") != 1
|
||||
or not isinstance(selection, dict)
|
||||
or not isinstance(selection.get("first_source_frame_index"), int)
|
||||
or not isinstance(selection.get("frame_count"), int)
|
||||
or selection["first_source_frame_index"] < 0
|
||||
or selection["frame_count"] < 1
|
||||
or selection["first_source_frame_index"] + selection["frame_count"]
|
||||
> rectification["expected_full_frame_count"]
|
||||
or not isinstance(candidates, dict)
|
||||
or set(candidates) != set(_CANDIDATES)
|
||||
or any(candidates[name].get("custom_postprocessing") is not False for name in candidates)
|
||||
or not isinstance(comparison, dict)
|
||||
or comparison.get("controlled_change") != "detector-provider-only"
|
||||
or authority
|
||||
!= {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G profile is invalid")
|
||||
|
||||
|
||||
def _validate_runtime(runtime: dict[str, Any], profile: dict[str, Any], raw: Path) -> None:
|
||||
image = str(profile["runtime"]["container_image"])
|
||||
digest = image.rsplit("@sha256:", 1)[-1]
|
||||
geometry = runtime.get("geometry")
|
||||
candidates = runtime.get("candidates")
|
||||
comparison = runtime.get("comparison")
|
||||
if (
|
||||
runtime.get("schema_version") != E46G_RUNTIME_SCHEMA
|
||||
or runtime.get("status") != "completed"
|
||||
or runtime.get("container_image") != image
|
||||
or runtime.get("container_image_digest") != digest
|
||||
or runtime.get("source_stream_sha256") != profile["source"]["stream_sha256"]
|
||||
or runtime.get("first_source_frame_index")
|
||||
!= profile["selection"]["first_source_frame_index"]
|
||||
or runtime.get("sample_frame_count") != profile["selection"]["frame_count"]
|
||||
or not runtime.get("worker_host")
|
||||
or not runtime.get("gpu_name")
|
||||
or not isinstance(geometry, dict)
|
||||
or set(geometry) != set(_VIEWS)
|
||||
or not isinstance(candidates, dict)
|
||||
or set(candidates) != set(_CANDIDATES)
|
||||
or not isinstance(comparison, dict)
|
||||
or set(comparison) != set(_CANDIDATES)
|
||||
):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G runtime identity is invalid")
|
||||
for view in _VIEWS:
|
||||
row = geometry[view]
|
||||
_runtime_artifact(raw, row, "full_rectified_video", f"geometry/{view}.mp4")
|
||||
_runtime_artifact(raw, row, "sample_video", f"samples/{view}.mp4")
|
||||
if (
|
||||
row.get("dewarper_config_sha256")
|
||||
!= profile["rectification"]["views"][view]["config_sha256"]
|
||||
or row.get("full_frame_count") != profile["rectification"]["expected_full_frame_count"]
|
||||
or row.get("retained_source_frame_index_range")
|
||||
!= profile["rectification"]["retained_source_frame_index_range"]
|
||||
or row.get("excluded_source_tail_frame_count")
|
||||
!= profile["rectification"]["excluded_source_tail_frame_count"]
|
||||
or row.get("sample_frame_count") != profile["selection"]["frame_count"]
|
||||
):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G dewarper config changed")
|
||||
for candidate in _CANDIDATES:
|
||||
candidate_row = candidates[candidate]
|
||||
if candidate_row.get("model_sha256") != profile["candidates"][candidate][
|
||||
"model_sha256"
|
||||
] or set(candidate_row.get("runs", {})) != set(_VIEWS):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G candidate identity changed")
|
||||
for view in _VIEWS:
|
||||
row = candidate_row["runs"][view]
|
||||
prefix = f"runs/{candidate}/{view}"
|
||||
_runtime_artifact(raw, row, "overlay", f"{prefix}/overlay.mp4")
|
||||
_runtime_artifact(raw, row, "deepstream_log", f"{prefix}/deepstream.log")
|
||||
if row.get("frame_count") != profile["selection"]["frame_count"]:
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G run coverage changed")
|
||||
_runtime_artifact(
|
||||
raw,
|
||||
comparison[candidate],
|
||||
"video",
|
||||
f"comparison/{candidate}.mp4",
|
||||
)
|
||||
|
||||
|
||||
def _runtime_artifact(raw: Path, row: object, key: str, expected_relative: str) -> None:
|
||||
if not isinstance(row, dict):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G runtime artifact is invalid")
|
||||
relative = row.get(f"{key}_path")
|
||||
digest = row.get(f"{key}_sha256")
|
||||
path = raw / str(relative)
|
||||
if (
|
||||
relative != expected_relative
|
||||
or not isinstance(digest, str)
|
||||
or _SHA256.fullmatch(digest) is None
|
||||
or not path.is_file()
|
||||
or path.is_symlink()
|
||||
or _sha256(path) != digest
|
||||
):
|
||||
raise E46GRectifiedDetectorBakeoffError("E46G runtime artifact changed")
|
||||
|
||||
|
||||
def _parse_detector_file(
|
||||
path: Path, profile: dict[str, Any], candidate: str
|
||||
) -> list[dict[str, Any]]:
|
||||
output: list[dict[str, Any]] = []
|
||||
width, height = profile["rectification"]["output_resolution"]
|
||||
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
tokens = line.split()
|
||||
if len(tokens) != 16:
|
||||
raise E46GRectifiedDetectorBakeoffError(
|
||||
f"invalid detector KITTI row {path.name}:{line_number}"
|
||||
)
|
||||
box, source_box, clipped = _parse_box(tokens[4:8], path, line_number, width, height)
|
||||
output.append(
|
||||
{
|
||||
"class_name": tokens[0],
|
||||
"bbox": box,
|
||||
"source_bbox_ltrb": source_box,
|
||||
"source_plane_clipped": clipped,
|
||||
"confidence": _finite_float(tokens[15], path, line_number),
|
||||
"provenance": f"nvidia-{candidate}-stock-deepstream",
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def _parse_tracker_file(
|
||||
path: Path, profile: dict[str, Any], candidate: str, view: str
|
||||
) -> list[dict[str, Any]]:
|
||||
output: list[dict[str, Any]] = []
|
||||
seen: set[int] = set()
|
||||
width, height = profile["rectification"]["output_resolution"]
|
||||
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
tokens = line.split()
|
||||
if len(tokens) < 17:
|
||||
raise E46GRectifiedDetectorBakeoffError(
|
||||
f"invalid tracker KITTI row {path.name}:{line_number}"
|
||||
)
|
||||
try:
|
||||
track_id = int(tokens[1])
|
||||
except ValueError as exc:
|
||||
raise E46GRectifiedDetectorBakeoffError(
|
||||
f"invalid tracker id {path.name}:{line_number}"
|
||||
) from exc
|
||||
if track_id < 0 or track_id in seen:
|
||||
raise E46GRectifiedDetectorBakeoffError("invalid NvDCF track identity")
|
||||
seen.add(track_id)
|
||||
box, source_box, clipped = _parse_box(tokens[5:9], path, line_number, width, height)
|
||||
output.append(
|
||||
{
|
||||
"object_id": f"{candidate}-{view}-nvdcf-{track_id}",
|
||||
"source_track_id": track_id,
|
||||
"class_name": tokens[0],
|
||||
"bbox": box,
|
||||
"source_bbox_ltrb": source_box,
|
||||
"source_plane_clipped": clipped,
|
||||
"confidence": _finite_float(tokens[16], path, line_number),
|
||||
"provenance": "nvidia-nvdcf-stock-view-local-output",
|
||||
}
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def _parse_box(
|
||||
tokens: list[str], path: Path, line_number: int, width: int, height: int
|
||||
) -> tuple[list[float], list[float], bool]:
|
||||
left, top, right, bottom = (_finite_float(token, path, line_number) for token in tokens)
|
||||
if right <= left or bottom <= top:
|
||||
raise E46GRectifiedDetectorBakeoffError(f"invalid bounding box {path.name}:{line_number}")
|
||||
projected = [
|
||||
max(0.0, min(float(width), left)),
|
||||
max(0.0, min(float(height), top)),
|
||||
max(0.0, min(float(width), right)),
|
||||
max(0.0, min(float(height), bottom)),
|
||||
]
|
||||
if projected[2] <= projected[0] or projected[3] <= projected[1]:
|
||||
raise E46GRectifiedDetectorBakeoffError(
|
||||
f"bounding box outside rectified plane {path.name}:{line_number}"
|
||||
)
|
||||
source = [left, top, right, bottom]
|
||||
return (
|
||||
[projected[0], projected[1], projected[2] - projected[0], projected[3] - projected[1]],
|
||||
source,
|
||||
source != projected,
|
||||
)
|
||||
|
||||
|
||||
def _metrics(rows: Iterable[dict[str, Any]], profile: dict[str, Any]) -> dict[str, Any]:
|
||||
values = list(rows)
|
||||
base = analyze_e46e_frames(values)
|
||||
width, height = profile["rectification"]["output_resolution"]
|
||||
plane_area = float(width * height)
|
||||
large = sum(
|
||||
float(item["bbox"][2]) * float(item["bbox"][3]) / plane_area >= 0.2
|
||||
for row in values
|
||||
for item in row["objects"]
|
||||
)
|
||||
return {
|
||||
**base,
|
||||
"large_track_observation_count": large,
|
||||
"large_track_fraction": round(large / max(1, base["track_observation_count"]), 6),
|
||||
}
|
||||
|
||||
|
||||
def _method(
|
||||
profile: dict[str, Any], runtime: dict[str, Any], source: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
components: list[dict[str, Any]] = [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": profile["source"]["job_id"],
|
||||
"version": "immutable recorded RIGHT replay",
|
||||
"role": "single physical camera source",
|
||||
"identity_sha256": source["job_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "XGRIDS K1 factory camera_1 KB4",
|
||||
"version": profile["calibration"]["model"],
|
||||
"role": "fisheye source geometry",
|
||||
"identity_sha256": profile["calibration"]["calibration_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "NVIDIA Gst-nvdewarper",
|
||||
"version": profile["runtime"]["deepstream_version"],
|
||||
"role": "calibrated fisheye-to-perspective adapter",
|
||||
"identity_sha256": hashlib.sha256(
|
||||
_canonical_json(profile["rectification"])
|
||||
).hexdigest(),
|
||||
},
|
||||
]
|
||||
for candidate in _CANDIDATES:
|
||||
row = profile["candidates"][candidate]
|
||||
components.append(
|
||||
{
|
||||
"kind": "model",
|
||||
"name": row["name"],
|
||||
"version": row["version"],
|
||||
"role": "controlled ready detector candidate",
|
||||
"identity_sha256": row["model_sha256"],
|
||||
}
|
||||
)
|
||||
components.extend(
|
||||
[
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "NVIDIA NvDCF",
|
||||
"version": profile["tracker"]["configuration"],
|
||||
"role": "view-local temporal association",
|
||||
"identity_sha256": runtime["candidates"]["trafficcamnet"]["runs"]["front"][
|
||||
"tracker_config_sha256"
|
||||
],
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "NVIDIA DeepStream",
|
||||
"version": profile["runtime"]["deepstream_version"],
|
||||
"role": "GPU media, inference and tracking runtime",
|
||||
"identity_sha256": runtime["container_image_digest"],
|
||||
},
|
||||
]
|
||||
)
|
||||
return {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": profile["profile_id"],
|
||||
"components": components,
|
||||
}
|
||||
@@ -0,0 +1,485 @@
|
||||
"""Freeze the selected stock NVIDIA provider over the retained FRONT route.
|
||||
|
||||
E46H is deliberately narrow: one immutable recorded RIGHT source, factory KB4,
|
||||
the official NVIDIA dewarper FRONT projection, TrafficCamNet and stock NvDCF.
|
||||
Mission Core validates and publishes the evidence but implements none of the
|
||||
detector, suppression, association, hold or stitch logic.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_indexed_kitti_files,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_read_source,
|
||||
_regular_file,
|
||||
_sha256,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
_write_jsonl,
|
||||
)
|
||||
from k1link.compute.e46g_rectified_detector_bakeoff import (
|
||||
E46GRectifiedDetectorBakeoffError,
|
||||
_metrics,
|
||||
_parse_detector_file,
|
||||
_parse_tracker_file,
|
||||
)
|
||||
|
||||
E46H_PROFILE_SCHEMA: Final = "missioncore.e46h-full-rectified-front-replay-profile/v1"
|
||||
E46H_RUNTIME_SCHEMA: Final = "missioncore.e46h-full-rectified-front-runtime/v1"
|
||||
E46H_RESULT_SCHEMA: Final = "missioncore.e46h-full-rectified-front-replay-result/v1"
|
||||
E46H_REPORT_SCHEMA: Final = "missioncore.e46h-full-rectified-front-replay-report/v1"
|
||||
E46H_FRAME_SCHEMA: Final = "missioncore.e46h-full-rectified-front-replay-frame/v1"
|
||||
E46H_PACKAGE_SCHEMA: Final = "missioncore.e46h-worker-package/v1"
|
||||
E46H_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46H_REPORT_NAME: Final = "full-rectified-front-report.json"
|
||||
E46H_FRAMES_NAME: Final = "tracked-frames.jsonl"
|
||||
E46H_OVERLAY_NAME: Final = "overlay.mp4"
|
||||
E46H_RUNTIME_NAME: Final = "runtime.json"
|
||||
E46H_LOG_NAME: Final = "worker.log"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46h-full-rectified-front-replay-[a-f0-9]{64}$")
|
||||
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"free_space_authority": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class E46HFullRectifiedFrontReplayError(ValueError):
|
||||
"""Raised when the E46H source, execution, or result is invalid."""
|
||||
|
||||
|
||||
def build_e46h_full_rectified_front_replay(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Validate the full retained FRONT replay and freeze immutable evidence."""
|
||||
|
||||
try:
|
||||
return _build_e46h_full_rectified_front_replay(
|
||||
source_job_root=source_job_root,
|
||||
raw_root=raw_root,
|
||||
profile_path=profile_path,
|
||||
output_root=output_root,
|
||||
)
|
||||
except (E46EReadyStackError, E46GRectifiedDetectorBakeoffError) as exc:
|
||||
raise E46HFullRectifiedFrontReplayError(str(exc).replace("E46E", "E46H")) from exc
|
||||
|
||||
|
||||
def _build_e46h_full_rectified_front_replay(
|
||||
*, source_job_root: Path, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
profile_source = profile_path.resolve(strict=True)
|
||||
profile = _read_json(profile_source)
|
||||
_validate_profile(profile)
|
||||
source = _read_source(source_job_root.resolve(strict=True), profile)
|
||||
raw = raw_root.resolve(strict=True)
|
||||
if raw.is_symlink():
|
||||
raise E46HFullRectifiedFrontReplayError("E46H raw root must not be a symlink")
|
||||
runtime = _read_json(raw / E46H_RUNTIME_NAME)
|
||||
_validate_runtime(runtime, profile, raw)
|
||||
overlay = _regular_file(raw / "run" / E46H_OVERLAY_NAME)
|
||||
worker_log = _regular_file(raw / E46H_LOG_NAME, allow_empty=True)
|
||||
|
||||
frame_count = int(profile["selection"]["frame_count"])
|
||||
detector_files = _indexed_kitti_files(raw / "run" / "detections", frame_count)
|
||||
tracker_files = _indexed_kitti_files(raw / "run" / "tracks", frame_count)
|
||||
frames: list[dict[str, Any]] = []
|
||||
for frame_index in range(frame_count):
|
||||
source_row = source["index"][frame_index]
|
||||
detections = _parse_detector_file(detector_files[frame_index], profile, "trafficcamnet")
|
||||
objects = _parse_tracker_file(
|
||||
tracker_files[frame_index], profile, "trafficcamnet", "front"
|
||||
)
|
||||
frames.append(
|
||||
{
|
||||
"schema_version": E46H_FRAME_SCHEMA,
|
||||
"frame_index": frame_index,
|
||||
"source_frame_index": frame_index,
|
||||
"sequence": int(source_row["sequence"]),
|
||||
"session_seconds": source["timeline_start_seconds"]
|
||||
+ (
|
||||
int(source_row["session_monotonic_ns"])
|
||||
- source["first_session_monotonic_ns"]
|
||||
)
|
||||
/ 1_000_000_000.0,
|
||||
"source_image_sha256": str(source_row["sha256"]),
|
||||
"detection_count": len(detections),
|
||||
"tracked_object_count": len(objects),
|
||||
"detections": detections,
|
||||
"objects": objects,
|
||||
}
|
||||
)
|
||||
metrics = _metrics(frames, profile)
|
||||
method = _method(profile, runtime, source)
|
||||
report_basis = {
|
||||
"schema_version": E46H_REPORT_SCHEMA,
|
||||
"status": "completed-awaiting-full-route-visual-review",
|
||||
"baseline_result_id": profile["baseline_result_id"],
|
||||
"selection": copy.deepcopy(profile["selection"]),
|
||||
"rectification": copy.deepcopy(profile["rectification"]),
|
||||
"metrics": metrics,
|
||||
"acceptance": {
|
||||
"exact_recorded_right_source_bound": True,
|
||||
"factory_calibration_bound": True,
|
||||
"official_nvidia_dewarper_executed": True,
|
||||
"selected_stock_detector_tracker_executed": True,
|
||||
"retained_route_accounted": metrics["frame_count"] == frame_count,
|
||||
"terminal_source_frame_excluded": True,
|
||||
"full_visual_review_completed": False,
|
||||
"independent_truth_available": False,
|
||||
"candidate_accepted": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"selected_provider": "front-trafficcamnet-stock-nvdcf",
|
||||
"custom_detector_or_tracker_logic_used": False,
|
||||
"provider_promoted": False,
|
||||
"next_action": (
|
||||
"review the complete seekable FRONT overlay for continuity, duplicates, stale "
|
||||
"tracks, semantic false positives and long object-layer blackouts"
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"E46H covers the timestamp-aligned 0..4487 prefix; DeepStream 9.1 decoding "
|
||||
"reproducibly omits the terminal source frame 4488 at EOS"
|
||||
),
|
||||
(
|
||||
"TrafficCamNet and NvDCF remain ready providers; E46H contains no custom "
|
||||
"detector, NMS, association, hold or stitch logic"
|
||||
),
|
||||
(
|
||||
"FRONT is one calibrated projection of the physical RIGHT camera; no LEFT "
|
||||
"camera or cross-view identity is introduced"
|
||||
),
|
||||
(
|
||||
"without independent exhaustive truth, continuity counts cannot establish "
|
||||
"precision, recall or physical identity correctness"
|
||||
),
|
||||
(
|
||||
"class, temporal identity, LiDAR range and dynamic/static state remain separate "
|
||||
"evidence layers; E46H introduces no command, navigation or safety authority"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
raw_identity = {
|
||||
"runtime_schema": runtime["schema_version"],
|
||||
"worker_host": runtime["worker_host"],
|
||||
"gpu_name": runtime["gpu_name"],
|
||||
"container_image": runtime["container_image"],
|
||||
"container_image_digest": runtime["container_image_digest"],
|
||||
"source_stream_sha256": runtime["source_stream_sha256"],
|
||||
"geometry": copy.deepcopy(runtime["geometry"]),
|
||||
"run": copy.deepcopy(runtime["run"]),
|
||||
"worker_log_sha256": _sha256(worker_log),
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46H_RESULT_SCHEMA,
|
||||
"source": source["identity"],
|
||||
"profile_sha256": _sha256(profile_source),
|
||||
"profile": copy.deepcopy(profile),
|
||||
"raw_execution": raw_identity,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"frames_sha256": hashlib.sha256(_canonical_json(frames)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46h-full-rectified-front-replay-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46h_full_rectified_front_replay(destination)
|
||||
|
||||
created_at = datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at,
|
||||
"source_session_id": profile["source"]["session_id"],
|
||||
"camera_source_id": profile["source"]["camera_source_id"],
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46H_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46H_FRAMES_NAME, frames)
|
||||
shutil.copyfile(overlay, staging / E46H_OVERLAY_NAME)
|
||||
shutil.copyfile(raw / E46H_RUNTIME_NAME, staging / E46H_RUNTIME_NAME)
|
||||
shutil.copyfile(worker_log, staging / E46H_LOG_NAME)
|
||||
artifacts = [
|
||||
_artifact(staging / E46H_REPORT_NAME, "full-rectified-front-report"),
|
||||
_artifact(staging / E46H_FRAMES_NAME, "tracked-frames"),
|
||||
_artifact(staging / E46H_OVERLAY_NAME, "visual-overlay-video"),
|
||||
_artifact(staging / E46H_RUNTIME_NAME, "runtime-record"),
|
||||
_artifact(staging / E46H_LOG_NAME, "worker-log"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46H_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46H_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at,
|
||||
"acceptance_state": "full-rectified-front-awaiting-visual-review",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46h_full_rectified_front_replay(destination)
|
||||
|
||||
|
||||
def read_e46h_full_rectified_front_replay(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully validate one immutable E46H result."""
|
||||
|
||||
try:
|
||||
return _read_e46h_full_rectified_front_replay(root)
|
||||
except (E46EReadyStackError, E46GRectifiedDetectorBakeoffError) as exc:
|
||||
raise E46HFullRectifiedFrontReplayError(str(exc).replace("E46E", "E46H")) from exc
|
||||
|
||||
|
||||
def _read_e46h_full_rectified_front_replay(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
if resolved.is_symlink():
|
||||
raise E46HFullRectifiedFrontReplayError("E46H result root must not be a symlink")
|
||||
manifest = _read_json(resolved / E46H_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
digest = (
|
||||
hashlib.sha256(_canonical_json(identity)).hexdigest() if isinstance(identity, dict) else ""
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != E46H_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != f"e46h-full-rectified-front-replay-{digest}"
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46HFullRectifiedFrontReplayError("E46H result identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 5:
|
||||
raise E46HFullRectifiedFrontReplayError("E46H artifact inventory is invalid")
|
||||
by_role = {row.get("role"): row for row in artifacts if isinstance(row, dict)}
|
||||
report_path = _validated_artifact(resolved, by_role.get("full-rectified-front-report"))
|
||||
frames_path = _validated_artifact(resolved, by_role.get("tracked-frames"))
|
||||
overlay_path = _validated_artifact(resolved, by_role.get("visual-overlay-video"))
|
||||
runtime_path = _validated_artifact(resolved, by_role.get("runtime-record"))
|
||||
_validated_artifact(resolved, by_role.get("worker-log"))
|
||||
report = _read_json(report_path)
|
||||
frames = tuple(_read_jsonl(frames_path))
|
||||
runtime = _read_json(runtime_path)
|
||||
if (
|
||||
report.get("schema_version") != E46H_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or runtime.get("schema_version") != E46H_RUNTIME_SCHEMA
|
||||
or any(row.get("schema_version") != E46H_FRAME_SCHEMA for row in frames)
|
||||
or hashlib.sha256(_canonical_json(frames)).hexdigest() != identity.get("frames_sha256")
|
||||
or report.get("metrics", {}).get("frame_count") != len(frames)
|
||||
):
|
||||
raise E46HFullRectifiedFrontReplayError("E46H result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"frames": frames,
|
||||
"runtime": runtime,
|
||||
"overlay_path": overlay_path,
|
||||
}
|
||||
|
||||
|
||||
def _validate_profile(profile: dict[str, Any]) -> None:
|
||||
source = profile.get("source")
|
||||
selection = profile.get("selection")
|
||||
calibration = profile.get("calibration")
|
||||
rectification = profile.get("rectification")
|
||||
runtime = profile.get("runtime")
|
||||
detector = profile.get("detector")
|
||||
parser = profile.get("parser")
|
||||
tracker = profile.get("tracker")
|
||||
authority = profile.get("authority")
|
||||
image = runtime.get("container_image") if isinstance(runtime, dict) else None
|
||||
if (
|
||||
profile.get("schema_version") != E46H_PROFILE_SCHEMA
|
||||
or not isinstance(profile.get("baseline_result_id"), str)
|
||||
or not profile["baseline_result_id"].startswith("e46g-rectified-detector-bakeoff-")
|
||||
or not isinstance(source, dict)
|
||||
or source.get("camera_source_id") != "sensor.camera.right"
|
||||
or source.get("segment_count") != 4489
|
||||
or not isinstance(selection, dict)
|
||||
or selection.get("first_source_frame_index") != 0
|
||||
or selection.get("last_source_frame_index") != 4487
|
||||
or selection.get("frame_count") != 4488
|
||||
or selection.get("excluded_source_tail_frame_count") != 1
|
||||
or not isinstance(calibration, dict)
|
||||
or calibration.get("slot") != "camera_1"
|
||||
or calibration.get("model") != "KB4"
|
||||
or calibration.get("calibration_sha256")
|
||||
!= "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
|
||||
or not isinstance(rectification, dict)
|
||||
or rectification.get("provider") != "NVIDIA Gst-nvdewarper"
|
||||
or rectification.get("projection_type") != 4
|
||||
or rectification.get("view") != "front"
|
||||
or rectification.get("output_resolution") != [960, 544]
|
||||
or not isinstance(image, str)
|
||||
or "@sha256:" not in image
|
||||
or not isinstance(detector, dict)
|
||||
or detector.get("name") != "NVIDIA TrafficCamNet Transformer Lite"
|
||||
or detector.get("custom_postprocessing") is not False
|
||||
or not isinstance(parser, dict)
|
||||
or parser.get("symbol") != "NvDsInferParseCustomDDETRTAO"
|
||||
or parser.get("custom_mission_core_logic") is not False
|
||||
or not isinstance(tracker, dict)
|
||||
or tracker.get("custom_association") is not False
|
||||
or tracker.get("custom_hold_or_stitch") is not False
|
||||
or authority
|
||||
!= {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
):
|
||||
raise E46HFullRectifiedFrontReplayError("E46H profile is invalid")
|
||||
|
||||
|
||||
def _validate_runtime(runtime: dict[str, Any], profile: dict[str, Any], raw: Path) -> None:
|
||||
image = profile["runtime"]["container_image"]
|
||||
geometry = runtime.get("geometry")
|
||||
run = runtime.get("run")
|
||||
if (
|
||||
runtime.get("schema_version") != E46H_RUNTIME_SCHEMA
|
||||
or runtime.get("status") != "completed"
|
||||
or runtime.get("container_image") != image
|
||||
or runtime.get("container_image_digest") != image.rsplit("@sha256:", 1)[-1]
|
||||
or runtime.get("source_stream_sha256") != profile["source"]["stream_sha256"]
|
||||
or runtime.get("frame_count") != profile["selection"]["frame_count"]
|
||||
or runtime.get("retained_source_frame_index_range") != [0, 4487]
|
||||
or not runtime.get("worker_host")
|
||||
or not runtime.get("gpu_name")
|
||||
or not isinstance(geometry, dict)
|
||||
or not isinstance(run, dict)
|
||||
):
|
||||
raise E46HFullRectifiedFrontReplayError("E46H runtime identity is invalid")
|
||||
_runtime_artifact(raw, geometry, "video", "geometry/front.mp4")
|
||||
_runtime_artifact(raw, geometry, "log", "geometry/front.log")
|
||||
_runtime_artifact(raw, run, "overlay", "run/overlay.mp4")
|
||||
_runtime_artifact(raw, run, "deepstream_log", "run/deepstream.log")
|
||||
required = (
|
||||
geometry.get("config_sha256") == profile["rectification"]["config_sha256"],
|
||||
geometry.get("frame_count") == profile["selection"]["frame_count"],
|
||||
run.get("frame_count") == profile["selection"]["frame_count"],
|
||||
run.get("model_sha256") == profile["detector"]["model_sha256"],
|
||||
run.get("parser_library_sha256") == profile["parser"]["library_sha256"],
|
||||
run.get("deepstream_app_config_sha256")
|
||||
== profile["detector"]["deepstream_app_config_sha256"],
|
||||
run.get("detector_config_sha256")
|
||||
== profile["detector"]["detector_config_sha256"],
|
||||
_SHA256.fullmatch(str(run.get("tracker_config_sha256"))) is not None,
|
||||
_SHA256.fullmatch(str(run.get("model_engine_sha256"))) is not None,
|
||||
)
|
||||
if not all(required):
|
||||
raise E46HFullRectifiedFrontReplayError("E46H runtime component identity changed")
|
||||
|
||||
|
||||
def _runtime_artifact(raw: Path, row: dict[str, Any], key: str, expected: str) -> None:
|
||||
relative = row.get(f"{key}_path")
|
||||
digest = row.get(f"{key}_sha256")
|
||||
path = raw / str(relative)
|
||||
if (
|
||||
relative != expected
|
||||
or not isinstance(digest, str)
|
||||
or _SHA256.fullmatch(digest) is None
|
||||
or not path.is_file()
|
||||
or path.is_symlink()
|
||||
or _sha256(path) != digest
|
||||
):
|
||||
raise E46HFullRectifiedFrontReplayError("E46H runtime artifact changed")
|
||||
|
||||
|
||||
def _method(
|
||||
profile: dict[str, Any], runtime: dict[str, Any], source: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": profile["profile_id"],
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": profile["source"]["job_id"],
|
||||
"version": "immutable recorded RIGHT replay",
|
||||
"role": "single physical camera source",
|
||||
"identity_sha256": source["job_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "XGRIDS K1 factory camera_1 KB4",
|
||||
"version": profile["calibration"]["model"],
|
||||
"role": "fisheye source geometry",
|
||||
"identity_sha256": profile["calibration"]["calibration_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "NVIDIA Gst-nvdewarper",
|
||||
"version": profile["runtime"]["deepstream_version"],
|
||||
"role": "FRONT fisheye-to-perspective adapter",
|
||||
"identity_sha256": profile["rectification"]["config_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": profile["detector"]["name"],
|
||||
"version": profile["detector"]["version"],
|
||||
"role": "selected ready detector provider",
|
||||
"identity_sha256": profile["detector"]["model_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": profile["tracker"]["name"],
|
||||
"version": profile["tracker"]["configuration"],
|
||||
"role": "FRONT route-local temporal association",
|
||||
"identity_sha256": runtime["run"]["tracker_config_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": "NVIDIA DeepStream",
|
||||
"version": profile["runtime"]["deepstream_version"],
|
||||
"role": "GPU media, inference and tracking runtime",
|
||||
"identity_sha256": runtime["container_image_digest"],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
"""Freeze the ready NVIDIA Grounding DINO provider over the full FRONT replay.
|
||||
|
||||
E46I keeps the E46H camera adapter and changes only the detector provider. The
|
||||
result is diagnostic: detections and visual evidence are published, while
|
||||
tracking, physical identity, motion state, LiDAR range and command authority
|
||||
remain explicitly outside this experiment.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from statistics import fmean
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_sha256,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
_write_jsonl,
|
||||
)
|
||||
|
||||
E46I_PROFILE_SCHEMA: Final = "missioncore.e46i-grounding-dino-full-replay-profile/v1"
|
||||
E46I_RUNTIME_SCHEMA: Final = "missioncore.e46i-grounding-dino-full-runtime/v1"
|
||||
E46I_RESULT_SCHEMA: Final = "missioncore.e46i-grounding-dino-full-replay-result/v1"
|
||||
E46I_REPORT_SCHEMA: Final = "missioncore.e46i-grounding-dino-full-replay-report/v1"
|
||||
E46I_FRAME_SCHEMA: Final = "missioncore.e46i-grounding-dino-full-replay-frame/v1"
|
||||
E46I_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46I_REPORT_NAME: Final = "grounding-dino-full-report.json"
|
||||
E46I_FRAMES_NAME: Final = "detection-frames.jsonl"
|
||||
E46I_OVERLAY_NAME: Final = "grounding-dino-full-overlay.mp4"
|
||||
E46I_LABELS_NAME: Final = "e46i-full-labels.tar"
|
||||
E46I_RUNTIME_NAME: Final = "runtime.json"
|
||||
E46I_SHADOW_SHEET_NAME: Final = "shadow-gate-contact-sheet.png"
|
||||
E46I_ROUTE_SHEET_NAME: Final = "full-route-10s-contact-sheet.png"
|
||||
E46I_TARGETED_SHEET_NAME: Final = "targeted-windows-contact-sheet.png"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46i-grounding-dino-full-replay-[a-f0-9]{64}$")
|
||||
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
_REVIEW_WINDOWS: Final = [
|
||||
{
|
||||
"id": "legacy-false-wall",
|
||||
"label": "6.0–10.9 с · прежний false car на стене",
|
||||
"start_seconds": 6.0,
|
||||
"end_seconds": 10.9,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-shrub",
|
||||
"label": "178.6–180.3 с · прежний false car на кусте",
|
||||
"start_seconds": 178.6,
|
||||
"end_seconds": 180.3,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-ground",
|
||||
"label": "250.7–265.6 с · прежний false car на полотне",
|
||||
"start_seconds": 250.7,
|
||||
"end_seconds": 265.6,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-road",
|
||||
"label": "392.2–400.6 с · прежний false car на дороге",
|
||||
"start_seconds": 392.2,
|
||||
"end_seconds": 400.6,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "empty-scene",
|
||||
"label": "419.4–426.9 с · пустая сцена",
|
||||
"start_seconds": 419.4,
|
||||
"end_seconds": 426.9,
|
||||
"verdict": "empty-scene-mostly-preserved",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-terrace",
|
||||
"label": "440.8–448.4 с · прежний false car на террасе",
|
||||
"start_seconds": 440.8,
|
||||
"end_seconds": 448.4,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class E46IGroundingDinoFullReplayError(ValueError):
|
||||
"""Raised when E46I evidence or identity is invalid."""
|
||||
|
||||
|
||||
def build_e46i_grounding_dino_full_replay(
|
||||
*, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Validate raw NVIDIA output and freeze one immutable E46I result."""
|
||||
|
||||
try:
|
||||
return _build_e46i_grounding_dino_full_replay(
|
||||
raw_root=raw_root, profile_path=profile_path, output_root=output_root
|
||||
)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46IGroundingDinoFullReplayError(str(exc).replace("E46E", "E46I")) from exc
|
||||
|
||||
|
||||
def _build_e46i_grounding_dino_full_replay(
|
||||
*, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
raw = raw_root.resolve(strict=True)
|
||||
if raw.is_symlink():
|
||||
raise E46IGroundingDinoFullReplayError("E46I raw root must not be a symlink")
|
||||
profile_source = profile_path.resolve(strict=True)
|
||||
profile = _read_json(profile_source)
|
||||
_validate_profile(profile)
|
||||
runtime_source = raw / E46I_RUNTIME_NAME
|
||||
runtime = _read_json(runtime_source)
|
||||
_validate_runtime(runtime, profile, raw)
|
||||
|
||||
labels_root = raw / "labels"
|
||||
if not labels_root.is_dir() or labels_root.is_symlink():
|
||||
raise E46IGroundingDinoFullReplayError("E46I labels root is invalid")
|
||||
frame_count = int(profile["source"]["video_frame_count"])
|
||||
frames = _read_detection_frames(labels_root, profile, frame_count)
|
||||
metrics = _metrics(frames, runtime)
|
||||
method = _method(profile)
|
||||
|
||||
report_basis = {
|
||||
"schema_version": E46I_REPORT_SCHEMA,
|
||||
"status": "semantic-regression-suppressed-awaiting-temporal-layer",
|
||||
"baseline_result_id": profile["baseline_result_id"],
|
||||
"source": copy.deepcopy(profile["source"]),
|
||||
"provider": copy.deepcopy(profile["provider"]),
|
||||
"inference": copy.deepcopy(profile["inference"]),
|
||||
"metrics": metrics,
|
||||
"shadow_gate": copy.deepcopy(profile["visual_shadow_gate"]),
|
||||
"visual_review": {
|
||||
"status": "full-playback-and-targeted-window-review-completed",
|
||||
"complete_video_playback_completed": True,
|
||||
"reviewed_video_range_seconds": [0.0, 448.8],
|
||||
"playback_rate": 4.0,
|
||||
"review_windows": copy.deepcopy(_REVIEW_WINDOWS),
|
||||
"verdict": "material-semantic-progress-not-yet-complete-perception",
|
||||
"finding": (
|
||||
"All five large E46H background false-car cases are absent in the new "
|
||||
"provider output; real vehicles and people remain visible across the route."
|
||||
),
|
||||
"known_error": (
|
||||
"The fixed four-caption ontology misses one partially cropped person and "
|
||||
"labels a stroller as bicycle in the anchor review."
|
||||
),
|
||||
},
|
||||
"acceptance": {
|
||||
"exact_recorded_right_source_bound": True,
|
||||
"same_calibrated_front_adapter_as_baseline": True,
|
||||
"official_nvidia_model_executed": True,
|
||||
"fixed_threshold_full_route_executed": True,
|
||||
"retained_route_accounted": metrics["frame_count"] == frame_count,
|
||||
"legacy_large_false_background_gate_passed": True,
|
||||
"full_overlay_published": True,
|
||||
"offline_reproducibility_completed": False,
|
||||
"temporal_identity_available": False,
|
||||
"motion_state_available": False,
|
||||
"candidate_accepted": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"selected_provider": "nvidia-grounding-dino-swin-tiny-commercial-v1.0",
|
||||
"provider_semantic_progress": True,
|
||||
"provider_promoted": False,
|
||||
"custom_detector_or_postprocessing_used": False,
|
||||
"next_action": (
|
||||
"freeze this detector output, vendor the tokenizer for offline replay, then "
|
||||
"attach a ready temporal tracker before evaluating dynamic/static state"
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"E46I has no independent exhaustive truth, so observation counts are not "
|
||||
"precision or recall."
|
||||
),
|
||||
(
|
||||
"Grounding DINO output is frame-local and contains no stable object identity "
|
||||
"or motion state."
|
||||
),
|
||||
"The fixed caption set contains car, person, bicycle and road sign only.",
|
||||
(
|
||||
"TAO downloaded bert-base-uncased tokenizer data at startup; offline replay "
|
||||
"is not sealed yet."
|
||||
),
|
||||
(
|
||||
"LiDAR range, dynamic/static state, free space, commands, navigation and "
|
||||
"safety remain unaccepted."
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46I_RESULT_SCHEMA,
|
||||
"profile_sha256": _sha256(profile_source),
|
||||
"profile": copy.deepcopy(profile),
|
||||
"runtime_sha256": _sha256(runtime_source),
|
||||
"runtime": copy.deepcopy(runtime),
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"frames_sha256": hashlib.sha256(_canonical_json(frames)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46i-grounding-dino-full-replay-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46i_grounding_dino_full_replay(destination)
|
||||
|
||||
created_at = datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46I_REPORT_NAME, report)
|
||||
_write_jsonl(staging / E46I_FRAMES_NAME, frames)
|
||||
for name in (
|
||||
E46I_OVERLAY_NAME,
|
||||
E46I_LABELS_NAME,
|
||||
E46I_RUNTIME_NAME,
|
||||
E46I_SHADOW_SHEET_NAME,
|
||||
E46I_ROUTE_SHEET_NAME,
|
||||
E46I_TARGETED_SHEET_NAME,
|
||||
):
|
||||
shutil.copyfile(raw / name, staging / name)
|
||||
artifacts = [
|
||||
_artifact(staging / E46I_REPORT_NAME, "grounding-dino-full-report"),
|
||||
_artifact(staging / E46I_FRAMES_NAME, "detection-frames"),
|
||||
_artifact(staging / E46I_OVERLAY_NAME, "visual-overlay-video"),
|
||||
_artifact(staging / E46I_LABELS_NAME, "raw-labels-archive"),
|
||||
_artifact(staging / E46I_RUNTIME_NAME, "runtime-record"),
|
||||
_artifact(staging / E46I_SHADOW_SHEET_NAME, "shadow-gate-contact-sheet"),
|
||||
_artifact(staging / E46I_ROUTE_SHEET_NAME, "full-route-contact-sheet"),
|
||||
_artifact(staging / E46I_TARGETED_SHEET_NAME, "targeted-windows-contact-sheet"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46I_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46I_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at,
|
||||
"acceptance_state": "semantic-progress-awaiting-temporal-layer",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46i_grounding_dino_full_replay(destination)
|
||||
|
||||
|
||||
def read_e46i_grounding_dino_full_replay(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully validate one immutable E46I result."""
|
||||
|
||||
try:
|
||||
return _read_e46i_grounding_dino_full_replay(root)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46IGroundingDinoFullReplayError(str(exc).replace("E46E", "E46I")) from exc
|
||||
|
||||
|
||||
def _read_e46i_grounding_dino_full_replay(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
if resolved.is_symlink():
|
||||
raise E46IGroundingDinoFullReplayError("E46I result root must not be a symlink")
|
||||
manifest = _read_json(resolved / E46I_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
digest = (
|
||||
hashlib.sha256(_canonical_json(identity)).hexdigest() if isinstance(identity, dict) else ""
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != E46I_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != f"e46i-grounding-dino-full-replay-{digest}"
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46IGroundingDinoFullReplayError("E46I result identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 8:
|
||||
raise E46IGroundingDinoFullReplayError("E46I artifact inventory is invalid")
|
||||
by_role = {row.get("role"): row for row in artifacts if isinstance(row, dict)}
|
||||
report_path = _validated_artifact(resolved, by_role.get("grounding-dino-full-report"))
|
||||
frames_path = _validated_artifact(resolved, by_role.get("detection-frames"))
|
||||
overlay_path = _validated_artifact(resolved, by_role.get("visual-overlay-video"))
|
||||
labels_path = _validated_artifact(resolved, by_role.get("raw-labels-archive"))
|
||||
runtime_path = _validated_artifact(resolved, by_role.get("runtime-record"))
|
||||
shadow_sheet_path = _validated_artifact(
|
||||
resolved, by_role.get("shadow-gate-contact-sheet")
|
||||
)
|
||||
route_sheet_path = _validated_artifact(
|
||||
resolved, by_role.get("full-route-contact-sheet")
|
||||
)
|
||||
targeted_sheet_path = _validated_artifact(
|
||||
resolved, by_role.get("targeted-windows-contact-sheet")
|
||||
)
|
||||
report = _read_json(report_path)
|
||||
frames = tuple(_read_jsonl(frames_path))
|
||||
runtime = _read_json(runtime_path)
|
||||
if (
|
||||
report.get("schema_version") != E46I_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or runtime.get("schema_version") != E46I_RUNTIME_SCHEMA
|
||||
or any(row.get("schema_version") != E46I_FRAME_SCHEMA for row in frames)
|
||||
or hashlib.sha256(_canonical_json(frames)).hexdigest() != identity.get("frames_sha256")
|
||||
or report.get("metrics", {}).get("frame_count") != len(frames)
|
||||
):
|
||||
raise E46IGroundingDinoFullReplayError("E46I result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"frames": frames,
|
||||
"runtime": runtime,
|
||||
"overlay_path": overlay_path,
|
||||
"labels_path": labels_path,
|
||||
"shadow_sheet_path": shadow_sheet_path,
|
||||
"route_sheet_path": route_sheet_path,
|
||||
"targeted_sheet_path": targeted_sheet_path,
|
||||
}
|
||||
|
||||
|
||||
def _validate_profile(profile: dict[str, Any]) -> None:
|
||||
source = profile.get("source")
|
||||
provider = profile.get("provider")
|
||||
inference = profile.get("inference")
|
||||
shadow = profile.get("visual_shadow_gate")
|
||||
if (
|
||||
profile.get("schema_version") != E46I_PROFILE_SCHEMA
|
||||
or not str(profile.get("baseline_result_id", "")).startswith(
|
||||
"e46h-full-rectified-front-replay-"
|
||||
)
|
||||
or not isinstance(source, dict)
|
||||
or source.get("camera_source_id") != "sensor.camera.right"
|
||||
or source.get("view") != "front"
|
||||
or source.get("projection_resolution") != [960, 544]
|
||||
or source.get("video_frame_count") != 4488
|
||||
or source.get("video_frame_rate") != 10.0
|
||||
or source.get("video_duration_seconds") != 448.8
|
||||
or _SHA256.fullmatch(str(source.get("video_sha256"))) is None
|
||||
or not isinstance(provider, dict)
|
||||
or provider.get("name") != "NVIDIA TAO Grounding DINO Swin-Tiny Commercial"
|
||||
or provider.get("custom_detector_or_postprocessing") is not False
|
||||
or _SHA256.fullmatch(str(provider.get("model_sha256"))) is None
|
||||
or _SHA256.fullmatch(str(provider.get("engine_sha256"))) is None
|
||||
or not isinstance(inference, dict)
|
||||
or inference.get("captions") != ["car", "person", "bicycle", "road sign"]
|
||||
or inference.get("confidence_threshold") != 0.5
|
||||
or inference.get("processed_frame_count") != 4488
|
||||
or inference.get("input_resolution") != [960, 544]
|
||||
or not isinstance(shadow, dict)
|
||||
or shadow.get("threshold_changed_after_review") is not False
|
||||
or shadow.get("legacy_large_false_background_cases_suppressed") != 5
|
||||
or profile.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise E46IGroundingDinoFullReplayError("E46I profile is invalid")
|
||||
|
||||
|
||||
def _validate_runtime(runtime: dict[str, Any], profile: dict[str, Any], raw: Path) -> None:
|
||||
overlay = runtime.get("overlay")
|
||||
labels = runtime.get("labels_archive")
|
||||
if (
|
||||
runtime.get("schema_version") != E46I_RUNTIME_SCHEMA
|
||||
or runtime.get("run_status") != "SUCCESS"
|
||||
or runtime.get("processed_frame_count") != 4488
|
||||
or runtime.get("annotated_frame_count") != 4488
|
||||
or runtime.get("label_file_count") != 4488
|
||||
or runtime.get("source_video_sha256") != profile["source"]["video_sha256"]
|
||||
or runtime.get("model_onnx_sha256") != profile["provider"]["model_sha256"]
|
||||
or runtime.get("tensorrt_engine_sha256") != profile["provider"]["engine_sha256"]
|
||||
or runtime.get("spec_sha256") != profile["inference"]["spec_sha256"]
|
||||
or not isinstance(overlay, dict)
|
||||
or overlay.get("file") != E46I_OVERLAY_NAME
|
||||
or overlay.get("frame_count") != 4488
|
||||
or overlay.get("duration_seconds") != 448.8
|
||||
or not isinstance(labels, dict)
|
||||
or labels.get("file") != E46I_LABELS_NAME
|
||||
):
|
||||
raise E46IGroundingDinoFullReplayError("E46I runtime identity is invalid")
|
||||
for row in (overlay, labels):
|
||||
path = raw / str(row["file"])
|
||||
if (
|
||||
not path.is_file()
|
||||
or path.is_symlink()
|
||||
or path.stat().st_size != row.get("byte_length")
|
||||
or _sha256(path) != row.get("sha256")
|
||||
):
|
||||
raise E46IGroundingDinoFullReplayError("E46I runtime artifact changed")
|
||||
for name in (
|
||||
E46I_SHADOW_SHEET_NAME,
|
||||
E46I_ROUTE_SHEET_NAME,
|
||||
E46I_TARGETED_SHEET_NAME,
|
||||
):
|
||||
path = raw / name
|
||||
if not path.is_file() or path.is_symlink() or path.stat().st_size <= 0:
|
||||
raise E46IGroundingDinoFullReplayError("E46I visual artifact is missing")
|
||||
|
||||
|
||||
def _read_detection_frames(
|
||||
labels_root: Path, profile: dict[str, Any], frame_count: int
|
||||
) -> list[dict[str, Any]]:
|
||||
allowed = set(profile["inference"]["captions"])
|
||||
threshold = float(profile["inference"]["confidence_threshold"])
|
||||
frames: list[dict[str, Any]] = []
|
||||
for frame_number in range(1, frame_count + 1):
|
||||
path = labels_root / f"frame_{frame_number:06d}.txt"
|
||||
if not path.is_file() or path.is_symlink():
|
||||
raise E46IGroundingDinoFullReplayError("E46I label sequence is incomplete")
|
||||
detections: list[dict[str, Any]] = []
|
||||
for line_number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
tokens = line.split()
|
||||
if len(tokens) < 16:
|
||||
raise E46IGroundingDinoFullReplayError("E46I label row is invalid")
|
||||
label = " ".join(tokens[:-15])
|
||||
try:
|
||||
numeric = [float(value) for value in tokens[-15:]]
|
||||
except ValueError as exc:
|
||||
raise E46IGroundingDinoFullReplayError(
|
||||
f"E46I label row {path.name}:{line_number} is invalid"
|
||||
) from exc
|
||||
x1, y1, x2, y2 = numeric[3:7]
|
||||
confidence = numeric[-1]
|
||||
if (
|
||||
label not in allowed
|
||||
or not threshold <= confidence <= 1.0
|
||||
or not 0.0 <= x1 < x2 <= 960.0
|
||||
or not 0.0 <= y1 < y2 <= 544.0
|
||||
):
|
||||
raise E46IGroundingDinoFullReplayError("E46I detection contract is invalid")
|
||||
area_fraction = ((x2 - x1) * (y2 - y1)) / (960.0 * 544.0)
|
||||
detections.append(
|
||||
{
|
||||
"class_name": label,
|
||||
"confidence": round(confidence, 6),
|
||||
"bbox_xyxy": [
|
||||
round(x1, 3),
|
||||
round(y1, 3),
|
||||
round(x2, 3),
|
||||
round(y2, 3),
|
||||
],
|
||||
"area_fraction": round(area_fraction, 9),
|
||||
}
|
||||
)
|
||||
frames.append(
|
||||
{
|
||||
"schema_version": E46I_FRAME_SCHEMA,
|
||||
"frame_index": frame_number - 1,
|
||||
"session_seconds": round((frame_number - 1) / 10.0, 1),
|
||||
"label_sha256": _sha256(path),
|
||||
"detection_count": len(detections),
|
||||
"detections": detections,
|
||||
}
|
||||
)
|
||||
extras = [
|
||||
path
|
||||
for path in labels_root.glob("frame_*.txt")
|
||||
if path.name > f"frame_{frame_count:06d}.txt"
|
||||
]
|
||||
if extras:
|
||||
raise E46IGroundingDinoFullReplayError("E46I label sequence has extra frames")
|
||||
return frames
|
||||
|
||||
|
||||
def _metrics(frames: list[dict[str, Any]], runtime: dict[str, Any]) -> dict[str, Any]:
|
||||
detections = [detection for frame in frames for detection in frame["detections"]]
|
||||
counts = [int(frame["detection_count"]) for frame in frames]
|
||||
confidences = [float(detection["confidence"]) for detection in detections]
|
||||
areas = [float(detection["area_fraction"]) for detection in detections]
|
||||
classes = Counter(str(detection["class_name"]) for detection in detections)
|
||||
zero_runs: list[int] = []
|
||||
run = 0
|
||||
for count in counts:
|
||||
if count == 0:
|
||||
run += 1
|
||||
elif run:
|
||||
zero_runs.append(run)
|
||||
run = 0
|
||||
if run:
|
||||
zero_runs.append(run)
|
||||
return {
|
||||
"frame_count": len(frames),
|
||||
"route_duration_seconds": 448.8,
|
||||
"detection_observation_count": len(detections),
|
||||
"class_observation_counts": dict(sorted(classes.items())),
|
||||
"mean_detections_per_frame": round(fmean(counts), 6),
|
||||
"max_detections_per_frame": max(counts),
|
||||
"zero_detection_frame_count": sum(count == 0 for count in counts),
|
||||
"zero_detection_frame_fraction": round(
|
||||
sum(count == 0 for count in counts) / len(frames), 9
|
||||
),
|
||||
"zero_detection_run_count": len(zero_runs),
|
||||
"longest_zero_detection_run_frames": max(zero_runs, default=0),
|
||||
"confidence_mean": round(fmean(confidences), 6),
|
||||
"confidence_min": min(confidences),
|
||||
"confidence_max": max(confidences),
|
||||
"large_box_observation_count": sum(area >= 0.25 for area in areas),
|
||||
"large_box_observation_fraction": round(
|
||||
sum(area >= 0.25 for area in areas) / len(areas), 9
|
||||
),
|
||||
"largest_box_area_fraction": round(max(areas), 9),
|
||||
"worker_elapsed_seconds": float(runtime["elapsed_seconds"]),
|
||||
"worker_mean_frames_per_second": float(runtime["mean_frames_per_second"]),
|
||||
}
|
||||
|
||||
|
||||
def _method(profile: dict[str, Any]) -> dict[str, Any]:
|
||||
source = profile["source"]
|
||||
provider = profile["provider"]
|
||||
inference = profile["inference"]
|
||||
return {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": profile["profile_id"],
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": source["session_id"],
|
||||
"version": "immutable recorded RIGHT FRONT replay",
|
||||
"role": "single physical camera source",
|
||||
"identity_sha256": source["video_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "NVIDIA Gst-nvdewarper",
|
||||
"version": "DeepStream 9.1",
|
||||
"role": "existing E46H calibrated FRONT adapter",
|
||||
"identity_sha256": (
|
||||
"f861e31278550bbe3fc82f41df4381c8a7aaf113a98a10761d98fa085c6a56b4"
|
||||
),
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": provider["name"],
|
||||
"version": provider["version"],
|
||||
"role": "ready open-vocabulary detector provider",
|
||||
"identity_sha256": provider["model_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": provider["deployment_toolkit"],
|
||||
"version": "TensorRT FP16",
|
||||
"role": "GPU inference runtime",
|
||||
"identity_sha256": provider["engine_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "fixed open-vocabulary caption contract",
|
||||
"version": ", ".join(inference["captions"]),
|
||||
"role": "source-independent semantic query set",
|
||||
"identity_sha256": inference["spec_sha256"],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -0,0 +1,625 @@
|
||||
"""Freeze the one-pass YOLOX-S full-raw-fisheye realtime qualification.
|
||||
|
||||
E46J deliberately evaluates the production-shaped detector path: one physical
|
||||
K1 RIGHT frame produces one inference request. It does not dewarp, crop, tile,
|
||||
track, hold or stitch detections. The result proves replay capacity and keeps
|
||||
visual quality findings separate from ground-truth claims.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from statistics import fmean
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.e46e_ready_stack import (
|
||||
E46EReadyStackError,
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_sha256,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
)
|
||||
|
||||
E46J_PROFILE_SCHEMA: Final = "missioncore.e46j-raw-fisheye-realtime-profile/v1"
|
||||
E46J_RUNTIME_SCHEMA: Final = "missioncore.e46j-raw-fisheye-realtime-runtime/v1"
|
||||
E46J_FRAME_SCHEMA: Final = "missioncore.e46j-raw-fisheye-realtime-frame/v1"
|
||||
E46J_RESULT_SCHEMA: Final = "missioncore.e46j-raw-fisheye-realtime-result/v1"
|
||||
E46J_REPORT_SCHEMA: Final = "missioncore.e46j-raw-fisheye-realtime-report/v1"
|
||||
|
||||
E46J_MANIFEST_NAME: Final = "manifest.json"
|
||||
E46J_REPORT_NAME: Final = "raw-fisheye-realtime-report.json"
|
||||
E46J_FRAMES_NAME: Final = "frames.jsonl"
|
||||
E46J_RUNTIME_NAME: Final = "runtime.json"
|
||||
E46J_OVERLAY_NAME: Final = "raw-fisheye-yolox-overlay.mp4"
|
||||
E46J_ROUTE_SHEET_NAME: Final = "full-route-10s-contact-sheet.png"
|
||||
E46J_TARGETED_SHEET_NAME: Final = "targeted-windows-contact-sheet.png"
|
||||
E46J_SHADOW_SHEET_NAME: Final = "operator-shadow-exception-contact-sheet.png"
|
||||
|
||||
_RESULT_ID = re.compile(r"^e46j-raw-fisheye-realtime-[a-f0-9]{64}$")
|
||||
_SHA256 = re.compile(r"^[a-f0-9]{64}$")
|
||||
_CLASS_BY_ID: Final = {
|
||||
0: "person",
|
||||
1: "bicycle",
|
||||
2: "car",
|
||||
3: "motorcycle",
|
||||
5: "bus",
|
||||
7: "truck",
|
||||
}
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"provider_promoted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
_REVIEW_WINDOWS: Final = [
|
||||
{
|
||||
"id": "legacy-false-wall",
|
||||
"label": "6.0–10.9 с · прежний false car на стене",
|
||||
"start_seconds": 6.0,
|
||||
"end_seconds": 10.9,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-shrub",
|
||||
"label": "178.6–180.3 с · прежний false car на кусте",
|
||||
"start_seconds": 178.6,
|
||||
"end_seconds": 180.3,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-ground",
|
||||
"label": "250.7–265.6 с · прежний false car на полотне",
|
||||
"start_seconds": 250.7,
|
||||
"end_seconds": 265.6,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-road",
|
||||
"label": "392.2–400.6 с · прежний false car на дороге",
|
||||
"start_seconds": 392.2,
|
||||
"end_seconds": 400.6,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
{
|
||||
"id": "operator-shadow",
|
||||
"label": "419.4–426.9 с · тень оператора",
|
||||
"start_seconds": 419.4,
|
||||
"end_seconds": 426.9,
|
||||
"verdict": "operator-shadow-person-false-positive-observed",
|
||||
},
|
||||
{
|
||||
"id": "legacy-false-terrace",
|
||||
"label": "440.8–448.4 с · прежний false car на террасе",
|
||||
"start_seconds": 440.8,
|
||||
"end_seconds": 448.4,
|
||||
"verdict": "legacy-background-false-positive-suppressed",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class E46JRawFisheyeRealtimeError(ValueError):
|
||||
"""Raised when E46J evidence or immutable identity is invalid."""
|
||||
|
||||
|
||||
def build_e46j_raw_fisheye_realtime(
|
||||
*, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
"""Validate worker evidence and freeze one immutable E46J result."""
|
||||
|
||||
try:
|
||||
return _build_e46j_raw_fisheye_realtime(
|
||||
raw_root=raw_root,
|
||||
profile_path=profile_path,
|
||||
output_root=output_root,
|
||||
)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46JRawFisheyeRealtimeError(str(exc).replace("E46E", "E46J")) from exc
|
||||
|
||||
|
||||
def _build_e46j_raw_fisheye_realtime(
|
||||
*, raw_root: Path, profile_path: Path, output_root: Path
|
||||
) -> dict[str, Any]:
|
||||
raw = raw_root.resolve(strict=True)
|
||||
if raw.is_symlink():
|
||||
raise E46JRawFisheyeRealtimeError("E46J raw root must not be a symlink")
|
||||
profile_source = profile_path.resolve(strict=True)
|
||||
profile = _read_json(profile_source)
|
||||
_validate_profile(profile)
|
||||
runtime_source = raw / E46J_RUNTIME_NAME
|
||||
runtime = _read_json(runtime_source)
|
||||
_validate_runtime(runtime, profile, profile_source, raw)
|
||||
frames_source = raw / E46J_FRAMES_NAME
|
||||
frames = _validated_frames(frames_source, profile, runtime)
|
||||
metrics = _metrics(frames, runtime)
|
||||
method = _method(profile)
|
||||
|
||||
report_basis = {
|
||||
"schema_version": E46J_REPORT_SCHEMA,
|
||||
"status": "realtime-capacity-passed-awaiting-temporal-layer",
|
||||
"source": copy.deepcopy(profile["source"]),
|
||||
"detector": copy.deepcopy(profile["detector"]),
|
||||
"preprocessing": copy.deepcopy(profile["preprocessing"]),
|
||||
"detection": copy.deepcopy(profile["detection"]),
|
||||
"metrics": metrics,
|
||||
"visual_review": {
|
||||
"status": "route-contact-sheet-and-targeted-window-review-completed",
|
||||
"full_route_contact_sheet_review_completed": True,
|
||||
"targeted_window_review_completed": True,
|
||||
"reviewed_video_range_seconds": [0.0, 448.723],
|
||||
"review_windows": copy.deepcopy(_REVIEW_WINDOWS),
|
||||
"verdict": "realtime-detector-progress-with-known-shadow-exception",
|
||||
"finding": (
|
||||
"Full raw KB4 fisheye coverage is retained. Vehicles remain visible on "
|
||||
"the route and at the circular image edge; the five previously reviewed "
|
||||
"large background car failures are absent in the targeted samples."
|
||||
),
|
||||
"known_error": (
|
||||
"The operator shadow is classified as person in 35 of the 75 frames "
|
||||
"inside the 419.4–426.9 second review window."
|
||||
),
|
||||
},
|
||||
"acceptance": {
|
||||
"exact_recorded_right_source_bound": True,
|
||||
"full_raw_fisheye_retained": True,
|
||||
"single_inference_per_source_frame": True,
|
||||
"all_source_frames_processed": metrics["frame_count"] == 4489,
|
||||
"zero_failed_frames": metrics["failed_frame_count"] == 0,
|
||||
"ten_hz_capacity_gate_passed": metrics["core_capacity_fps"] >= 10.0,
|
||||
"latency_gate_passed": (
|
||||
metrics["core_path_p95_ms"] <= 80.0
|
||||
and metrics["inference_request_p95_ms"] <= 60.0
|
||||
),
|
||||
"legacy_large_false_background_gate_passed": True,
|
||||
"full_overlay_published": True,
|
||||
"temporal_identity_available": False,
|
||||
"motion_state_available": False,
|
||||
"candidate_accepted": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
},
|
||||
"decision": {
|
||||
"selected_provider": "megvii-yolox-s-0.1.1rc0",
|
||||
"realtime_capacity_passed": True,
|
||||
"ready_for_temporal_bakeoff": True,
|
||||
"provider_promoted": False,
|
||||
"custom_detector_logic_used": False,
|
||||
"route_specific_filtering_used": False,
|
||||
"next_action": (
|
||||
"Keep the single raw-fisheye detector pass unchanged and attach a ready "
|
||||
"temporal tracker. Evaluate stable IDs, drop/recovery and dynamic/static "
|
||||
"state on the same full video before any live-hardware claim."
|
||||
),
|
||||
},
|
||||
"method": method,
|
||||
"limitations": [
|
||||
(
|
||||
"E46J has no independent exhaustive truth; detection observation counts "
|
||||
"are not precision or recall."
|
||||
),
|
||||
(
|
||||
"The detector is frame-local and does not provide persistent object IDs, "
|
||||
"track continuity or dynamic/static state."
|
||||
),
|
||||
"A known person false positive occurs on the operator shadow.",
|
||||
(
|
||||
"LiDAR range, free space, commands, navigation and safety remain outside "
|
||||
"this result."
|
||||
),
|
||||
(
|
||||
"The accepted capacity assumes the camera adapter and Triton share a "
|
||||
"local GPU host path; routing full FP32 tensors through an external Windows "
|
||||
"bridge is not an accepted realtime topology."
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": E46J_RESULT_SCHEMA,
|
||||
"profile_sha256": _sha256(profile_source),
|
||||
"profile": copy.deepcopy(profile),
|
||||
"runtime_sha256": _sha256(runtime_source),
|
||||
"runtime": copy.deepcopy(runtime),
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"frames_sha256": hashlib.sha256(_canonical_json(frames)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e46j-raw-fisheye-realtime-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_e46j_raw_fisheye_realtime(destination)
|
||||
|
||||
created_at = datetime.now(UTC).isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
)
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / E46J_REPORT_NAME, report)
|
||||
for name in (
|
||||
E46J_FRAMES_NAME,
|
||||
E46J_RUNTIME_NAME,
|
||||
E46J_OVERLAY_NAME,
|
||||
E46J_ROUTE_SHEET_NAME,
|
||||
E46J_TARGETED_SHEET_NAME,
|
||||
E46J_SHADOW_SHEET_NAME,
|
||||
):
|
||||
shutil.copyfile(raw / name, staging / name)
|
||||
artifacts = [
|
||||
_artifact(staging / E46J_REPORT_NAME, "raw-fisheye-realtime-report"),
|
||||
_artifact(staging / E46J_FRAMES_NAME, "detection-frames"),
|
||||
_artifact(staging / E46J_RUNTIME_NAME, "runtime-record"),
|
||||
_artifact(staging / E46J_OVERLAY_NAME, "visual-overlay-video"),
|
||||
_artifact(staging / E46J_ROUTE_SHEET_NAME, "full-route-contact-sheet"),
|
||||
_artifact(staging / E46J_TARGETED_SHEET_NAME, "targeted-windows-contact-sheet"),
|
||||
_artifact(staging / E46J_SHADOW_SHEET_NAME, "operator-shadow-contact-sheet"),
|
||||
]
|
||||
_write_json(
|
||||
staging / E46J_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": E46J_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at,
|
||||
"acceptance_state": "realtime-capacity-passed-awaiting-temporal-layer",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_e46j_raw_fisheye_realtime(destination)
|
||||
|
||||
|
||||
def read_e46j_raw_fisheye_realtime(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully validate one immutable E46J result."""
|
||||
|
||||
try:
|
||||
return _read_e46j_raw_fisheye_realtime(root)
|
||||
except E46EReadyStackError as exc:
|
||||
raise E46JRawFisheyeRealtimeError(str(exc).replace("E46E", "E46J")) from exc
|
||||
|
||||
|
||||
def _read_e46j_raw_fisheye_realtime(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
if resolved.is_symlink():
|
||||
raise E46JRawFisheyeRealtimeError("E46J result root must not be a symlink")
|
||||
manifest = _read_json(resolved / E46J_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
digest = (
|
||||
hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if isinstance(identity, dict)
|
||||
else ""
|
||||
)
|
||||
if (
|
||||
manifest.get("schema_version") != E46J_RESULT_SCHEMA
|
||||
or manifest.get("result_id") != f"e46j-raw-fisheye-realtime-{digest}"
|
||||
or manifest.get("identity_sha256") != digest
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J result identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 7:
|
||||
raise E46JRawFisheyeRealtimeError("E46J artifact inventory is invalid")
|
||||
by_role = {row.get("role"): row for row in artifacts if isinstance(row, dict)}
|
||||
paths = {
|
||||
role: _validated_artifact(resolved, by_role.get(role))
|
||||
for role in (
|
||||
"raw-fisheye-realtime-report",
|
||||
"detection-frames",
|
||||
"runtime-record",
|
||||
"visual-overlay-video",
|
||||
"full-route-contact-sheet",
|
||||
"targeted-windows-contact-sheet",
|
||||
"operator-shadow-contact-sheet",
|
||||
)
|
||||
}
|
||||
report = _read_json(paths["raw-fisheye-realtime-report"])
|
||||
runtime = _read_json(paths["runtime-record"])
|
||||
frames = tuple(_read_jsonl(paths["detection-frames"]))
|
||||
if (
|
||||
report.get("schema_version") != E46J_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or runtime.get("schema_version") != E46J_RUNTIME_SCHEMA
|
||||
or len(frames) != 4489
|
||||
or any(row.get("schema_version") != E46J_FRAME_SCHEMA for row in frames)
|
||||
or hashlib.sha256(_canonical_json(frames)).hexdigest()
|
||||
!= identity.get("frames_sha256")
|
||||
or report.get("metrics", {}).get("frame_count") != len(frames)
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"frames": frames,
|
||||
"runtime": runtime,
|
||||
"overlay_path": paths["visual-overlay-video"],
|
||||
"route_sheet_path": paths["full-route-contact-sheet"],
|
||||
"targeted_sheet_path": paths["targeted-windows-contact-sheet"],
|
||||
"shadow_sheet_path": paths["operator-shadow-contact-sheet"],
|
||||
}
|
||||
|
||||
|
||||
def _validate_profile(profile: dict[str, Any]) -> None:
|
||||
source = profile.get("source")
|
||||
detector = profile.get("detector")
|
||||
detection = profile.get("detection")
|
||||
acceptance = profile.get("acceptance")
|
||||
if (
|
||||
profile.get("schema_version") != E46J_PROFILE_SCHEMA
|
||||
or profile.get("profile_id") != "e46j-k1-right-raw-kb4-yolox-s-one-pass/v1"
|
||||
or not isinstance(source, dict)
|
||||
or source.get("camera_source_id") != "sensor.camera.right"
|
||||
or source.get("stream_sha256")
|
||||
!= "cadd1696ff000904eb78633a0a8418104b8024f178b91f3421789021ccb160e8"
|
||||
or source.get("frame_count") != 4489
|
||||
or source.get("average_rate") != "4489000/448723"
|
||||
or source.get("resolution") != [800, 600]
|
||||
or source.get("calibration_model") != "KB4"
|
||||
or not isinstance(detector, dict)
|
||||
or detector.get("architecture") != "YOLOX-S"
|
||||
or detector.get("license") != "Apache-2.0"
|
||||
or detector.get("single_inference_per_source_frame") is not True
|
||||
or _SHA256.fullmatch(str(detector.get("model_sha256"))) is None
|
||||
or _SHA256.fullmatch(str(detector.get("config_sha256"))) is None
|
||||
or not isinstance(detection, dict)
|
||||
or detection.get("minimum_score") != 0.5
|
||||
or detection.get("nms_iou_threshold") != 0.45
|
||||
or detection.get("target_class_ids") != [0, 1, 2, 3, 5, 7]
|
||||
or detection.get("custom_detector_logic") is not False
|
||||
or detection.get("route_specific_filtering") is not False
|
||||
or not isinstance(acceptance, dict)
|
||||
or acceptance.get("minimum_core_capacity_fps") != 10.0
|
||||
or acceptance.get("require_full_raw_fov") is not True
|
||||
or profile.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J profile is invalid")
|
||||
|
||||
|
||||
def _validate_runtime(
|
||||
runtime: dict[str, Any],
|
||||
profile: dict[str, Any],
|
||||
profile_path: Path,
|
||||
raw: Path,
|
||||
) -> None:
|
||||
source = runtime.get("source")
|
||||
model = runtime.get("model")
|
||||
metrics = runtime.get("metrics")
|
||||
acceptance = runtime.get("acceptance")
|
||||
artifacts = runtime.get("artifacts")
|
||||
if (
|
||||
runtime.get("schema_version") != E46J_RUNTIME_SCHEMA
|
||||
or runtime.get("status") != "completed"
|
||||
or runtime.get("profile_sha256") != _sha256(profile_path)
|
||||
or not isinstance(source, dict)
|
||||
or source.get("video_sha256") != profile["source"]["stream_sha256"]
|
||||
or source.get("decoded_frame_count") != 4489
|
||||
or source.get("resolution") != [800, 600]
|
||||
or "no crop/dewarp/tile" not in str(source.get("preprocessing"))
|
||||
or not isinstance(model, dict)
|
||||
or model.get("id") != profile["detector"]["id"]
|
||||
or model.get("model_sha256") != profile["detector"]["model_sha256"]
|
||||
or model.get("config_sha256") != profile["detector"]["config_sha256"]
|
||||
or model.get("inference_requests") != 4489
|
||||
or not isinstance(metrics, dict)
|
||||
or metrics.get("processed_frame_count") != 4489
|
||||
or metrics.get("failed_frame_count") != 0
|
||||
or float(metrics.get("core_capacity_fps", 0.0)) < 10.0
|
||||
or not isinstance(acceptance, dict)
|
||||
or acceptance.get("passed") is not True
|
||||
or not all(acceptance.get("checks", {}).values())
|
||||
or runtime.get("authority") != _AUTHORITY
|
||||
or not isinstance(artifacts, dict)
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J runtime identity is invalid")
|
||||
expected = {
|
||||
"frames": E46J_FRAMES_NAME,
|
||||
"overlay": E46J_OVERLAY_NAME,
|
||||
}
|
||||
for role, name in expected.items():
|
||||
row = artifacts.get(role)
|
||||
path = raw / name
|
||||
if (
|
||||
not isinstance(row, dict)
|
||||
or row.get("file") != name
|
||||
or not path.is_file()
|
||||
or path.is_symlink()
|
||||
or path.stat().st_size != row.get("byte_length")
|
||||
or _sha256(path) != row.get("sha256")
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J runtime artifact changed")
|
||||
for name in (
|
||||
E46J_ROUTE_SHEET_NAME,
|
||||
E46J_TARGETED_SHEET_NAME,
|
||||
E46J_SHADOW_SHEET_NAME,
|
||||
):
|
||||
path = raw / name
|
||||
if not path.is_file() or path.is_symlink() or path.stat().st_size <= 0:
|
||||
raise E46JRawFisheyeRealtimeError("E46J visual artifact is missing")
|
||||
|
||||
|
||||
def _validated_frames(
|
||||
path: Path, profile: dict[str, Any], runtime: dict[str, Any]
|
||||
) -> list[dict[str, Any]]:
|
||||
if not path.is_file() or path.is_symlink():
|
||||
raise E46JRawFisheyeRealtimeError("E46J frame evidence is missing")
|
||||
rows = list(_read_jsonl(path))
|
||||
if len(rows) != 4489:
|
||||
raise E46JRawFisheyeRealtimeError("E46J frame sequence is incomplete")
|
||||
artifact = runtime["artifacts"]["frames"]
|
||||
if path.stat().st_size != artifact["byte_length"] or _sha256(path) != artifact["sha256"]:
|
||||
raise E46JRawFisheyeRealtimeError("E46J frame evidence changed")
|
||||
frame_rate = float(profile["source"]["frame_rate"])
|
||||
class_counts: Counter[str] = Counter()
|
||||
for index, row in enumerate(rows):
|
||||
detections = row.get("detections")
|
||||
if (
|
||||
row.get("schema_version") != E46J_FRAME_SCHEMA
|
||||
or row.get("frame_index") != index
|
||||
or abs(float(row.get("session_seconds", -1.0)) - index / frame_rate) > 1e-5
|
||||
or not isinstance(detections, list)
|
||||
or not isinstance(row.get("latency_ms"), dict)
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J frame contract is invalid")
|
||||
for detection in detections:
|
||||
if not isinstance(detection, dict):
|
||||
raise E46JRawFisheyeRealtimeError("E46J detection is invalid")
|
||||
class_id = detection.get("class_id")
|
||||
box = detection.get("bbox_xyxy")
|
||||
score = detection.get("score")
|
||||
if (
|
||||
not isinstance(class_id, int)
|
||||
or detection.get("label") != _CLASS_BY_ID.get(class_id)
|
||||
or not isinstance(score, (int, float))
|
||||
or not 0.5 <= float(score) <= 1.0
|
||||
or not isinstance(box, list)
|
||||
or len(box) != 4
|
||||
or not 0.0 <= float(box[0]) < float(box[2]) <= 800.0
|
||||
or not 0.0 <= float(box[1]) < float(box[3]) <= 600.0
|
||||
):
|
||||
raise E46JRawFisheyeRealtimeError("E46J detection contract is invalid")
|
||||
class_counts[str(detection["label"])] += 1
|
||||
if dict(sorted(class_counts.items())) != runtime["metrics"]["class_observation_counts"]:
|
||||
raise E46JRawFisheyeRealtimeError("E46J class accounting changed")
|
||||
return rows
|
||||
|
||||
|
||||
def _metrics(frames: list[dict[str, Any]], runtime: dict[str, Any]) -> dict[str, Any]:
|
||||
detections = [item for frame in frames for item in frame["detections"]]
|
||||
counts = [len(frame["detections"]) for frame in frames]
|
||||
confidences = [float(item["score"]) for item in detections]
|
||||
zero_runs: list[int] = []
|
||||
run = 0
|
||||
for count in counts:
|
||||
if count == 0:
|
||||
run += 1
|
||||
elif run:
|
||||
zero_runs.append(run)
|
||||
run = 0
|
||||
if run:
|
||||
zero_runs.append(run)
|
||||
shadow_frames = [
|
||||
frame
|
||||
for frame in frames
|
||||
if 419.4 <= float(frame["session_seconds"]) <= 426.9
|
||||
]
|
||||
shadow_person_frames = sum(
|
||||
any(item["label"] == "person" for item in frame["detections"])
|
||||
for frame in shadow_frames
|
||||
)
|
||||
runtime_metrics = runtime["metrics"]
|
||||
latency = runtime_metrics["latency_ms"]
|
||||
return {
|
||||
"frame_count": len(frames),
|
||||
"route_duration_seconds": 448.723,
|
||||
"failed_frame_count": int(runtime_metrics["failed_frame_count"]),
|
||||
"detection_observation_count": len(detections),
|
||||
"class_observation_counts": copy.deepcopy(
|
||||
runtime_metrics["class_observation_counts"]
|
||||
),
|
||||
"mean_detections_per_frame": round(fmean(counts), 6),
|
||||
"max_detections_per_frame": max(counts),
|
||||
"zero_detection_frame_count": sum(count == 0 for count in counts),
|
||||
"zero_detection_frame_fraction": round(
|
||||
sum(count == 0 for count in counts) / len(frames), 9
|
||||
),
|
||||
"zero_detection_run_count": len(zero_runs),
|
||||
"longest_zero_detection_run_frames": max(zero_runs, default=0),
|
||||
"confidence_mean": round(fmean(confidences), 6),
|
||||
"confidence_min": min(confidences),
|
||||
"confidence_max": max(confidences),
|
||||
"core_capacity_fps": float(runtime_metrics["core_capacity_fps"]),
|
||||
"core_path_mean_ms": float(latency["core_path_ms"]["mean"]),
|
||||
"core_path_p95_ms": float(latency["core_path_ms"]["p95"]),
|
||||
"inference_request_mean_ms": float(
|
||||
latency["inference_request_ms"]["mean"]
|
||||
),
|
||||
"inference_request_p95_ms": float(latency["inference_request_ms"]["p95"]),
|
||||
"gpu_utilization_mean_percent": float(
|
||||
runtime_metrics["gpu"]["gpu_utilization_percent"]["mean"]
|
||||
),
|
||||
"operator_shadow_window_frame_count": len(shadow_frames),
|
||||
"operator_shadow_person_frame_count": shadow_person_frames,
|
||||
}
|
||||
|
||||
|
||||
def _method(profile: dict[str, Any]) -> dict[str, Any]:
|
||||
source = profile["source"]
|
||||
detector = profile["detector"]
|
||||
detection = profile["detection"]
|
||||
return {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "hybrid",
|
||||
"pipeline_id": profile["profile_id"],
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": source["session_id"],
|
||||
"version": "immutable recorded K1 RIGHT raw KB4",
|
||||
"role": "single physical camera source; full 800×600 raster",
|
||||
"identity_sha256": source["stream_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "tool",
|
||||
"name": "valid-FOV mask plus top-left letterbox",
|
||||
"version": "raw KB4 adapter v1",
|
||||
"role": "fill invalid pixels without crop, dewarp or virtual views",
|
||||
"identity_sha256": source["calibration_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "model",
|
||||
"name": detector["architecture"],
|
||||
"version": detector["source"],
|
||||
"role": "ready COCO-80 detector provider",
|
||||
"identity_sha256": detector["model_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "runtime",
|
||||
"name": detector["runtime"],
|
||||
"version": "co-located GPU network path",
|
||||
"role": "one synchronous inference request per source frame",
|
||||
"identity_sha256": detector["config_sha256"],
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "standard YOLOX decode and class-wise NMS",
|
||||
"version": (
|
||||
f"score {detection['minimum_score']} · IoU "
|
||||
f"{detection['nms_iou_threshold']}"
|
||||
),
|
||||
"role": "fixed source-independent detector output contract",
|
||||
"identity_sha256": detector["config_sha256"],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -117,7 +117,7 @@ def build_e49_detector_truth_evaluation(
|
||||
"prediction freeze belongs to another truth island"
|
||||
)
|
||||
|
||||
valid_fov = _read_valid_fov(
|
||||
valid_fov = read_valid_fov_mask(
|
||||
valid_fov_root,
|
||||
calibration_sha256=str(
|
||||
truth_island.manifest["identity"]["source"]["calibration_sha256"]
|
||||
@@ -640,12 +640,14 @@ def _class_count(predictions: list[dict[str, Any]]) -> dict[str, int]:
|
||||
return dict(result)
|
||||
|
||||
|
||||
def _read_valid_fov(
|
||||
def read_valid_fov_mask(
|
||||
root: Path,
|
||||
*,
|
||||
calibration_sha256: str,
|
||||
calibration_slot: str,
|
||||
) -> dict[str, Any]:
|
||||
"""Read an exact calibration-bound valid-FOV mask for detector metrics."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest_path = resolved / E49_MANIFEST_NAME
|
||||
manifest = _read_json(manifest_path)
|
||||
|
||||
@@ -0,0 +1,435 @@
|
||||
"""Camera-bound visual evidence for the sealed RAVNOVES00 PointPillars run.
|
||||
|
||||
L3.2 does not execute the detector again. It binds the immutable L3.1 visual
|
||||
sample to exact right-camera frames, projects the same LiDAR sample and model
|
||||
hypotheses through the admitted K1 calibration, and seals a new review result.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import shutil
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
import numpy as np
|
||||
|
||||
from k1link.compute.e30_camera_evidence import (
|
||||
E30CameraEvidenceError,
|
||||
materialize_e30_camera_frames,
|
||||
open_e30_camera_evidence_source,
|
||||
)
|
||||
from k1link.compute.lidar_field_review import E10LidarFieldSource
|
||||
from k1link.compute.semantic_geometry_fusion import projection_profile_from_source
|
||||
from k1link.device_plugins.xgrids_k1.analyze.calibrated_projection import (
|
||||
Kb4ProjectionProfile,
|
||||
)
|
||||
|
||||
RESULT_SCHEMA: Final = "missioncore.l32-pointpillars-camera-review/v1"
|
||||
CATALOG_SCHEMA: Final = "missioncore.l32-pointpillars-camera-review-catalog/v1"
|
||||
FRAME_SCHEMA: Final = "missioncore.l32-pointpillars-camera-review-frame/v1"
|
||||
MAX_CAMERA_BINDING_DELTA_MS: Final = 100.0
|
||||
BOX_EDGES: Final = (
|
||||
(0, 1), (1, 2), (2, 3), (3, 0),
|
||||
(4, 5), (5, 6), (6, 7), (7, 4),
|
||||
(0, 4), (1, 5), (2, 6), (3, 7),
|
||||
)
|
||||
|
||||
|
||||
class L32PointPillarsCameraReviewError(RuntimeError):
|
||||
"""The L3.2 evidence sources or generated result violate the contract."""
|
||||
|
||||
|
||||
def build_l32_pointpillars_camera_review(
|
||||
*,
|
||||
l31_result_root: Path,
|
||||
e10_pack_root: Path,
|
||||
camera_job_root: Path,
|
||||
ffmpeg_path: Path,
|
||||
output_root: Path,
|
||||
) -> Path:
|
||||
"""Build one immutable camera-first review from sealed local sources."""
|
||||
|
||||
l31_root = l31_result_root.expanduser().resolve(strict=True)
|
||||
if l31_root.is_symlink() or not l31_root.is_dir():
|
||||
raise L32PointPillarsCameraReviewError("L3.1 result root is invalid")
|
||||
l31_manifest = _read_json(l31_root / "manifest.json")
|
||||
l31_catalog = _read_json(l31_root / "catalog.json")
|
||||
if (
|
||||
l31_manifest.get("schema_version")
|
||||
!= "missioncore.l31-pointpillars-ravnoves/v1"
|
||||
or l31_manifest.get("result_id") != l31_root.name
|
||||
or l31_catalog.get("schema_version")
|
||||
!= "missioncore.l31-pointpillars-ravnoves-catalog/v1"
|
||||
or l31_catalog.get("result_id") != l31_root.name
|
||||
):
|
||||
raise L32PointPillarsCameraReviewError("L3.1 source identity is invalid")
|
||||
|
||||
source = E10LidarFieldSource(e10_pack_root)
|
||||
try:
|
||||
if (
|
||||
source.identity.get("session_id")
|
||||
!= l31_manifest["identity"].get("source_session_id")
|
||||
or source.identity.get("source_id") != "sensor.camera.right"
|
||||
):
|
||||
raise L32PointPillarsCameraReviewError(
|
||||
"camera-aligned LiDAR pack does not match L3.1"
|
||||
)
|
||||
projection = projection_profile_from_source(source)
|
||||
try:
|
||||
camera = open_e30_camera_evidence_source(
|
||||
camera_job_root=camera_job_root,
|
||||
ffmpeg_path=ffmpeg_path,
|
||||
expected_session_id=str(source.identity["session_id"]),
|
||||
expected_source_id=str(source.identity["source_id"]),
|
||||
)
|
||||
except E30CameraEvidenceError as exc:
|
||||
raise L32PointPillarsCameraReviewError(
|
||||
"right-camera evidence source is invalid"
|
||||
) from exc
|
||||
|
||||
bindings = _select_bindings(l31_root, l31_catalog, source)
|
||||
if not bindings:
|
||||
raise L32PointPillarsCameraReviewError(
|
||||
"no L3.1 visual frames overlap the right camera"
|
||||
)
|
||||
identity = {
|
||||
"schema_version": RESULT_SCHEMA,
|
||||
"source_session_id": source.identity["session_id"],
|
||||
"source_l31_result_id": l31_root.name,
|
||||
"source_l31_manifest_sha256": _sha256(l31_root / "manifest.json"),
|
||||
"source_l31_catalog_sha256": _sha256(l31_root / "catalog.json"),
|
||||
"source_e10_pack_id": source.pack_id,
|
||||
"source_camera_job": camera.identity(),
|
||||
"projection": {
|
||||
"model": "kb4",
|
||||
"source_id": projection.source_id,
|
||||
"calibration_slot": projection.calibration_slot,
|
||||
"width": projection.width,
|
||||
"height": projection.height,
|
||||
},
|
||||
"camera_binding": {
|
||||
"clock": "session-monotonic",
|
||||
"selection": "nearest-camera-frame",
|
||||
"maximum_absolute_delta_ms": MAX_CAMERA_BINDING_DELTA_MS,
|
||||
},
|
||||
"review_frame_indices": [item["frame_index"] for item in bindings],
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": {
|
||||
"shadow_only": True,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
"accuracy_accepted": False,
|
||||
},
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l32-pointpillars-camera-review-{identity_sha256}"
|
||||
root = output_root.expanduser().resolve()
|
||||
root.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
destination = root / result_id
|
||||
if destination.exists():
|
||||
_validate_existing(destination, identity)
|
||||
return destination
|
||||
|
||||
staging = root / f".{result_id}.{os.getpid()}.incomplete"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
camera_artifacts = materialize_e30_camera_frames(
|
||||
source=camera,
|
||||
source_frame_indices=tuple(
|
||||
int(item["camera_source_frame_index"]) for item in bindings
|
||||
),
|
||||
destination_root=staging / "frames",
|
||||
width=projection.width,
|
||||
height=projection.height,
|
||||
)
|
||||
catalog_frames: list[dict[str, object]] = []
|
||||
score_values: list[float] = []
|
||||
visible_boxes = 0
|
||||
projected_points = 0
|
||||
for binding in bindings:
|
||||
old_payload = binding.pop("source_payload")
|
||||
frame_id = str(binding["frame_id"])
|
||||
points = np.asarray(
|
||||
old_payload["points"]["values"], dtype=np.float64
|
||||
).reshape((-1, 4))
|
||||
projected = _project_sensor_points(points[:, :3], projection)
|
||||
projected_box_items = [
|
||||
_project_box(box, projection)
|
||||
for box in old_payload["prediction_boxes"]
|
||||
]
|
||||
projected_box_items = [item for item in projected_box_items if item]
|
||||
visible_boxes += len(projected_box_items)
|
||||
projected_points += int(projected.shape[0])
|
||||
score_values.extend(
|
||||
float(box["score"]) for box in old_payload["prediction_boxes"]
|
||||
)
|
||||
camera_frame_index = int(binding["camera_source_frame_index"])
|
||||
frame_payload = {
|
||||
"schema_version": FRAME_SCHEMA,
|
||||
"frame_id": frame_id,
|
||||
"summary": binding,
|
||||
"camera": camera_artifacts[camera_frame_index],
|
||||
"points": old_payload["points"],
|
||||
"prediction_boxes": old_payload["prediction_boxes"],
|
||||
"camera_projection": {
|
||||
"point_layout": "flat-xy-depth-m",
|
||||
"point_count": int(projected.shape[0]),
|
||||
"point_values": projected.reshape(-1).tolist(),
|
||||
"boxes": projected_box_items,
|
||||
},
|
||||
"interpretation": {
|
||||
"camera_is_semantic_reference": True,
|
||||
"lidar_is_metric_overlay": True,
|
||||
"boxes_are_model_hypotheses": True,
|
||||
"ground_truth_available": False,
|
||||
"accuracy_claim_allowed": False,
|
||||
},
|
||||
}
|
||||
frame_path = staging / f"frame-{frame_id}.json"
|
||||
_write_json(frame_path, frame_payload)
|
||||
catalog_frames.append(
|
||||
{
|
||||
**binding,
|
||||
"detail_path": frame_path.name,
|
||||
"detail_sha256": _sha256(frame_path),
|
||||
"detail_byte_length": frame_path.stat().st_size,
|
||||
"camera_path": camera_artifacts[camera_frame_index]["path"],
|
||||
"camera_sha256": camera_artifacts[camera_frame_index]["sha256"],
|
||||
}
|
||||
)
|
||||
|
||||
catalog = {
|
||||
"schema_version": CATALOG_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"source_session_id": source.identity["session_id"],
|
||||
"frame_count": len(catalog_frames),
|
||||
"frames": catalog_frames,
|
||||
}
|
||||
catalog_path = staging / "catalog.json"
|
||||
_write_json(catalog_path, catalog)
|
||||
scores = np.asarray(score_values, dtype=np.float64)
|
||||
deltas = np.asarray(
|
||||
[abs(float(item["camera_delta_ms"])) for item in bindings],
|
||||
dtype=np.float64,
|
||||
)
|
||||
metrics = {
|
||||
**l31_manifest["metrics"],
|
||||
"review_frame_count": len(bindings),
|
||||
"review_prediction_count": len(score_values),
|
||||
"review_visible_projected_box_count": visible_boxes,
|
||||
"review_projected_point_count": projected_points,
|
||||
"score_below_0_25_fraction": float(np.mean(scores < 0.25)),
|
||||
"score_below_0_50_fraction": float(np.mean(scores < 0.50)),
|
||||
"camera_binding_absolute_delta_ms": {
|
||||
"maximum": float(np.max(deltas)),
|
||||
"p50": float(np.percentile(deltas, 50)),
|
||||
"p95": float(np.percentile(deltas, 95)),
|
||||
},
|
||||
}
|
||||
limitations = [
|
||||
"The camera is a semantic reference, not labeled 3D ground truth.",
|
||||
"PointPillars boxes remain cross-domain model hypotheses.",
|
||||
(
|
||||
"LiDAR overlay uses the admitted factory KB4 calibration and "
|
||||
"nearest camera frame within 100 ms."
|
||||
),
|
||||
"The review cannot establish precision, recall or safety fitness.",
|
||||
]
|
||||
manifest = {
|
||||
"schema_version": RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity": identity,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": datetime.now(UTC).isoformat(timespec="milliseconds"),
|
||||
"status": "camera-bound-review-rejects-current-candidate",
|
||||
"metrics": metrics,
|
||||
"catalog": {
|
||||
"path": "catalog.json",
|
||||
"sha256": _sha256(catalog_path),
|
||||
"byte_length": catalog_path.stat().st_size,
|
||||
},
|
||||
"limitations": limitations,
|
||||
"authority": identity["authority"],
|
||||
}
|
||||
_write_json(staging / "manifest.json", manifest)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return destination
|
||||
finally:
|
||||
source.close()
|
||||
|
||||
|
||||
def _select_bindings(
|
||||
l31_root: Path,
|
||||
catalog: dict[str, Any],
|
||||
source: E10LidarFieldSource,
|
||||
) -> list[dict[str, Any]]:
|
||||
times = np.asarray(source.arrays["session_seconds"], dtype=np.float64)
|
||||
selected: list[dict[str, Any]] = []
|
||||
for descriptor in catalog.get("frames", []):
|
||||
if not isinstance(descriptor, dict):
|
||||
raise L32PointPillarsCameraReviewError("L3.1 catalog frame is invalid")
|
||||
target = float(descriptor["session_seconds"])
|
||||
row = int(np.argmin(np.abs(times - target)))
|
||||
delta_ms = float((times[row] - target) * 1000.0)
|
||||
if abs(delta_ms) > MAX_CAMERA_BINDING_DELTA_MS:
|
||||
continue
|
||||
frame_id = str(descriptor["frame_id"])
|
||||
payload_path = l31_root / str(descriptor["detail_path"])
|
||||
if (
|
||||
_sha256(payload_path) != descriptor.get("detail_sha256")
|
||||
or payload_path.stat().st_size != descriptor.get("detail_byte_length")
|
||||
):
|
||||
raise L32PointPillarsCameraReviewError("L3.1 visual frame changed")
|
||||
payload = _read_json(payload_path)
|
||||
selected.append(
|
||||
{
|
||||
"frame_id": frame_id,
|
||||
"frame_index": int(descriptor["frame_index"]),
|
||||
"session_seconds": target,
|
||||
"source_point_count": int(descriptor["source_point_count"]),
|
||||
"prediction_count": int(descriptor["prediction_count"]),
|
||||
"class_counts": descriptor["class_counts"],
|
||||
"inference_ms": float(descriptor["inference_ms"]),
|
||||
"camera_source_frame_index": int(
|
||||
source.arrays["source_frame_indices"][row]
|
||||
),
|
||||
"camera_session_seconds": float(times[row]),
|
||||
"camera_delta_ms": delta_ms,
|
||||
"source_payload": payload,
|
||||
}
|
||||
)
|
||||
return selected
|
||||
|
||||
|
||||
def _project_sensor_points(
|
||||
points_lidar: np.ndarray,
|
||||
profile: Kb4ProjectionProfile,
|
||||
) -> np.ndarray:
|
||||
pixels, depths, valid = _project_camera(points_lidar, profile)
|
||||
if not np.any(valid):
|
||||
return np.empty((0, 3), dtype=np.float64)
|
||||
return np.column_stack((pixels[valid], depths[valid]))
|
||||
|
||||
|
||||
def _project_box(
|
||||
box: dict[str, Any],
|
||||
profile: Kb4ProjectionProfile,
|
||||
) -> dict[str, object] | None:
|
||||
center = np.asarray([box["x_m"], box["y_m"], box["z_m"]], dtype=np.float64)
|
||||
length = float(box["length_m"])
|
||||
width = float(box["width_m"])
|
||||
height = float(box["height_m"])
|
||||
yaw = float(box["yaw_rad"])
|
||||
cosine = math.cos(yaw)
|
||||
sine = math.sin(yaw)
|
||||
corners: list[list[float]] = []
|
||||
for z_offset in (-height / 2.0, height / 2.0):
|
||||
for x_offset, y_offset in (
|
||||
(-length / 2.0, -width / 2.0),
|
||||
(length / 2.0, -width / 2.0),
|
||||
(length / 2.0, width / 2.0),
|
||||
(-length / 2.0, width / 2.0),
|
||||
):
|
||||
corners.append(
|
||||
[
|
||||
center[0] + x_offset * cosine - y_offset * sine,
|
||||
center[1] + x_offset * sine + y_offset * cosine,
|
||||
center[2] + z_offset,
|
||||
]
|
||||
)
|
||||
pixels, _, valid = _project_camera(np.asarray(corners), profile)
|
||||
segments: list[float] = []
|
||||
for start, end in BOX_EDGES:
|
||||
if valid[start] and valid[end]:
|
||||
segments.extend(
|
||||
[
|
||||
float(pixels[start, 0]),
|
||||
float(pixels[start, 1]),
|
||||
float(pixels[end, 0]),
|
||||
float(pixels[end, 1]),
|
||||
]
|
||||
)
|
||||
if not segments:
|
||||
return None
|
||||
return {
|
||||
"model_class": box["model_class"],
|
||||
"score": float(box["score"]),
|
||||
"segments_xyxy": segments,
|
||||
}
|
||||
|
||||
|
||||
def _project_camera(
|
||||
points_lidar: np.ndarray,
|
||||
profile: Kb4ProjectionProfile,
|
||||
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||
points = np.asarray(points_lidar, dtype=np.float64)
|
||||
transform = profile.t_camera_from_lidar
|
||||
camera = points @ transform[:3, :3].T + transform[:3, 3]
|
||||
x, y, z = camera.T
|
||||
radial = np.hypot(x, y)
|
||||
theta = np.arctan2(radial, z)
|
||||
theta2 = theta * theta
|
||||
k1, k2, k3, k4 = profile.distortion_kb4
|
||||
distorted = theta * (
|
||||
1.0 + k1 * theta2 + k2 * theta2**2 + k3 * theta2**3 + k4 * theta2**4
|
||||
)
|
||||
scale = np.divide(
|
||||
distorted, radial, out=np.zeros_like(distorted), where=radial > 1e-12
|
||||
)
|
||||
fx, fy, cx, cy = profile.intrinsic_fx_fy_cx_cy
|
||||
pixels = np.column_stack((fx * x * scale + cx, fy * y * scale + cy))
|
||||
valid = (
|
||||
(z > 1e-6)
|
||||
& np.isfinite(pixels).all(axis=1)
|
||||
& (pixels[:, 0] >= 0.0)
|
||||
& (pixels[:, 0] < profile.width)
|
||||
& (pixels[:, 1] >= 0.0)
|
||||
& (pixels[:, 1] < profile.height)
|
||||
)
|
||||
return pixels, z, valid
|
||||
|
||||
|
||||
def _validate_existing(root: Path, identity: dict[str, object]) -> None:
|
||||
manifest = _read_json(root / "manifest.json")
|
||||
if (
|
||||
manifest.get("schema_version") != RESULT_SCHEMA
|
||||
or manifest.get("identity") != identity
|
||||
or manifest.get("result_id") != root.name
|
||||
):
|
||||
raise L32PointPillarsCameraReviewError("existing L3.2 result differs")
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
raise L32PointPillarsCameraReviewError(f"invalid JSON: {path.name}") from exc
|
||||
if not isinstance(value, dict):
|
||||
raise L32PointPillarsCameraReviewError(f"invalid object: {path.name}")
|
||||
return value
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
while chunk := stream.read(1024 * 1024):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
@@ -0,0 +1,40 @@
|
||||
"""Fail-closed source admission for the RAVNOVES00 L3.3 review."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Final
|
||||
|
||||
RAVNOVES00_SESSION_ID: Final = "20260720T065719Z_viewer_live"
|
||||
RAVNOVES00_CAMERA_SOURCE_ID: Final = "sensor.camera.right"
|
||||
RAVNOVES00_ADMITTED_WORLD_STATE_RESULT_ID: Final = (
|
||||
"rectified-camera-world-state-04f0bb519af614ca16eae3d924ca930fb68869e0f5131916a56705e8b24d4bf7"
|
||||
)
|
||||
|
||||
|
||||
def is_admitted_world_state(
|
||||
result_id: object,
|
||||
identity: Mapping[str, object] | None = None,
|
||||
) -> bool:
|
||||
"""Return whether one world-state result is the sealed RAVNOVES00 parent."""
|
||||
|
||||
if result_id != RAVNOVES00_ADMITTED_WORLD_STATE_RESULT_ID:
|
||||
return False
|
||||
if identity is None:
|
||||
return True
|
||||
source = identity.get("source")
|
||||
return (
|
||||
isinstance(source, Mapping)
|
||||
and source.get("session_id") == RAVNOVES00_SESSION_ID
|
||||
and source.get("source_id") == RAVNOVES00_CAMERA_SOURCE_ID
|
||||
)
|
||||
|
||||
|
||||
def is_admitted_l33_identity(identity: Mapping[str, object]) -> bool:
|
||||
"""Return whether an L3.3 identity is bound to the admitted replay source."""
|
||||
|
||||
return (
|
||||
identity.get("source_session_id") == RAVNOVES00_SESSION_ID
|
||||
and identity.get("source_world_state_result_id")
|
||||
== RAVNOVES00_ADMITTED_WORLD_STATE_RESULT_ID
|
||||
)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,579 @@
|
||||
"""Freeze the first YOLOX candidate for RAVNOVES00_RIGHT_YOLOX_TRUTH_ISLAND_V1."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import Counter
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from .e46_detector_truth_island import (
|
||||
E46_CONTRACT_NAME,
|
||||
E46_MANIFEST_NAME,
|
||||
E46_REFERENCES_NAME,
|
||||
E46DetectorTruthIslandError,
|
||||
read_e46_detector_truth_island,
|
||||
)
|
||||
|
||||
L34_RESULT_SCHEMA: Final = "missioncore.l34-right-yolox-truth-island-freeze/v1"
|
||||
L34_REPORT_SCHEMA: Final = "missioncore.l34-right-yolox-truth-island-report/v1"
|
||||
L34_PREDICTION_SCHEMA: Final = (
|
||||
"missioncore.l34-right-yolox-truth-island-prediction/v1"
|
||||
)
|
||||
L34_PROFILE_SCHEMA: Final = "missioncore.l34-right-yolox-truth-island-profile/v1"
|
||||
L34_MANIFEST_NAME: Final = "manifest.json"
|
||||
L34_REPORT_NAME: Final = "benchmark-report.json"
|
||||
L34_PREDICTIONS_NAME: Final = "candidate-predictions.jsonl"
|
||||
|
||||
_L33_SCHEMA: Final = "missioncore.l33-camera-first-detector-review/v1"
|
||||
_DETECTOR_FRAME_SCHEMA: Final = "missioncore.rectified-yolox-frame/v1"
|
||||
_RESULT_ID: Final = re.compile(r"^l34-right-yolox-truth-island-freeze-[a-f0-9]{64}$")
|
||||
_SHA256: Final = re.compile(r"^[a-f0-9]{64}$")
|
||||
_CANDIDATE_ID: Final = "yolox-s-kb4-core3"
|
||||
_TARGET_CLASSES: Final = (
|
||||
"person",
|
||||
"bicycle",
|
||||
"motorcycle",
|
||||
"car",
|
||||
"heavy_vehicle",
|
||||
"static_obstacle",
|
||||
"animal",
|
||||
)
|
||||
_DETECTOR_LABELS: Final = frozenset(
|
||||
{"person", "bicycle", "motorcycle", "car", "truck", "bus"}
|
||||
)
|
||||
_AUTHORITY: Final = {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L34RightYoloxTruthIslandError(RuntimeError):
|
||||
"""The benchmark preregistration or one of its immutable inputs is invalid."""
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class L34RightYoloxTruthIsland:
|
||||
result_id: str
|
||||
result_root: Path
|
||||
manifest: dict[str, Any]
|
||||
report: dict[str, Any]
|
||||
predictions: tuple[dict[str, Any], ...]
|
||||
|
||||
|
||||
def build_l34_right_yolox_truth_island_freeze(
|
||||
*,
|
||||
profile_path: Path,
|
||||
truth_island_root: Path,
|
||||
detector_qualification_root: Path,
|
||||
l33_result_root: Path,
|
||||
output_root: Path,
|
||||
) -> L34RightYoloxTruthIsland:
|
||||
"""Freeze right-camera YOLOX predictions without reading human labels."""
|
||||
|
||||
profile = _read_profile(profile_path)
|
||||
try:
|
||||
truth_island = read_e46_detector_truth_island(truth_island_root)
|
||||
except E46DetectorTruthIslandError as reason:
|
||||
raise L34RightYoloxTruthIslandError("E46 truth island is invalid") from reason
|
||||
|
||||
contract = _read_json(truth_island.result_root / E46_CONTRACT_NAME)
|
||||
annotation = _object(contract.get("annotation"), "E46 annotation")
|
||||
truth_source = _object(
|
||||
_object(truth_island.manifest.get("identity"), "E46 identity").get(
|
||||
"source"
|
||||
),
|
||||
"E46 source",
|
||||
)
|
||||
if (
|
||||
contract.get("truth_state") != "labels-unavailable"
|
||||
or annotation.get("classes") != list(_TARGET_CLASSES)
|
||||
or truth_source.get("source_id") != "sensor.camera.right"
|
||||
or truth_source.get("session_id") != "20260720T065719Z_viewer_live"
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError(
|
||||
"truth island does not preserve the blind right-camera contract"
|
||||
)
|
||||
|
||||
qualification_root = _directory(detector_qualification_root)
|
||||
qualification_path = qualification_root / "qualification.json"
|
||||
frames_path = qualification_root / "frames.jsonl"
|
||||
qualification = _read_json(qualification_path)
|
||||
if (
|
||||
qualification.get("schema_version")
|
||||
!= "missioncore.rectified-yolox-qualification/v1"
|
||||
or _object(qualification.get("metrics"), "qualification metrics").get(
|
||||
"frames_processed"
|
||||
)
|
||||
!= 4489
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("YOLOX qualification is invalid")
|
||||
|
||||
l33_root = _directory(l33_result_root)
|
||||
l33_manifest_path = l33_root / L34_MANIFEST_NAME
|
||||
l33_manifest = _read_json(l33_manifest_path)
|
||||
l33_identity = _object(l33_manifest.get("identity"), "L3.3 identity")
|
||||
detector = _object(l33_identity.get("detector"), "L3.3 detector")
|
||||
semantic = _object(
|
||||
l33_identity.get("semantic_contract"),
|
||||
"L3.3 semantic contract",
|
||||
)
|
||||
if (
|
||||
l33_manifest.get("schema_version") != _L33_SCHEMA
|
||||
or l33_manifest.get("result_id") != l33_root.name
|
||||
or l33_identity.get("source_session_id")
|
||||
!= truth_source.get("session_id")
|
||||
or detector.get("architecture") != "YOLOX-S"
|
||||
or detector.get("model_sha256") != profile["candidate"]["model_sha256"]
|
||||
or semantic.get("minimum_detector_score")
|
||||
!= profile["candidate"]["minimum_score"]
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L3.3 candidate identity is invalid")
|
||||
|
||||
references = tuple(
|
||||
_read_jsonl(truth_island.result_root / E46_REFERENCES_NAME)
|
||||
)
|
||||
target_indices = {
|
||||
_integer(reference.get("frame_index"), "truth frame index")
|
||||
for reference in references
|
||||
}
|
||||
frames = _selected_detector_frames(frames_path, target_indices)
|
||||
predictions = freeze_l34_candidate_predictions(
|
||||
references=references,
|
||||
detector_frames=frames,
|
||||
minimum_score=float(profile["candidate"]["minimum_score"]),
|
||||
)
|
||||
predictions_bytes = b"".join(
|
||||
_canonical_json(row) + b"\n" for row in predictions
|
||||
)
|
||||
predictions_sha256 = hashlib.sha256(predictions_bytes).hexdigest()
|
||||
class_counts = Counter(
|
||||
prediction["label"]
|
||||
for row in predictions
|
||||
for prediction in row["predictions"]
|
||||
)
|
||||
identity = {
|
||||
"schema_version": L34_RESULT_SCHEMA,
|
||||
"profile": profile,
|
||||
"source": {
|
||||
"session_id": truth_source["session_id"],
|
||||
"source_id": truth_source["source_id"],
|
||||
"mode": "recorded-replay-only",
|
||||
},
|
||||
"truth_island": {
|
||||
"result_id": truth_island.result_id,
|
||||
"manifest_sha256": _sha256(
|
||||
truth_island.result_root / E46_MANIFEST_NAME
|
||||
),
|
||||
"truth_state": "labels-unavailable",
|
||||
},
|
||||
"candidate": {
|
||||
"l33_result_id": l33_root.name,
|
||||
"l33_manifest_sha256": _sha256(l33_manifest_path),
|
||||
"qualification_sha256": _sha256(qualification_path),
|
||||
"detector_frames_sha256": _sha256(frames_path),
|
||||
"prediction_rows_sha256": predictions_sha256,
|
||||
},
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34-right-yolox-truth-island-freeze-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l34_right_yolox_truth_island_freeze(destination)
|
||||
|
||||
report = {
|
||||
"schema_version": L34_REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"status": "predictions-frozen-awaiting-independent-truth",
|
||||
"profile_id": profile["profile_id"],
|
||||
"pipeline_id": profile["pipeline_id"],
|
||||
"source_session_id": truth_source["session_id"],
|
||||
"camera_source_id": truth_source["source_id"],
|
||||
"metrics": {
|
||||
"frame_count": len(predictions),
|
||||
"temporal_group_count": len(
|
||||
{str(row["group_id"]) for row in predictions}
|
||||
),
|
||||
"prediction_count": sum(
|
||||
len(row["predictions"]) for row in predictions
|
||||
),
|
||||
"frames_with_predictions": sum(
|
||||
bool(row["predictions"]) for row in predictions
|
||||
),
|
||||
"class_counts": {
|
||||
label: class_counts.get(label, 0) for label in _TARGET_CLASSES
|
||||
},
|
||||
"accuracy_metrics_available": False,
|
||||
},
|
||||
"decision": {
|
||||
"candidate_predictions_frozen": True,
|
||||
"truth_labels_read": False,
|
||||
"candidate_accepted": False,
|
||||
"model_retraining_authorized": False,
|
||||
"next_gate": (
|
||||
"complete two independent blind reviews, seal adjudicated "
|
||||
"truth, then evaluate these exact predictions"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
"RAVNOVES00 source-scoped benchmark; no cross-route claim",
|
||||
"accuracy remains unavailable until independent truth is sealed",
|
||||
"recorded replay only; live transport and hardware are out of scope",
|
||||
"only sensor.camera.right is admitted",
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"access": "read-only",
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
(staging / L34_PREDICTIONS_NAME).write_bytes(predictions_bytes)
|
||||
_write_json(staging / L34_REPORT_NAME, report)
|
||||
manifest = {
|
||||
"schema_version": L34_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": _utc_now(),
|
||||
"ground_truth": False,
|
||||
"acceptance_state": "prepared-awaiting-independent-human-truth",
|
||||
"artifacts": [
|
||||
_artifact(staging / L34_REPORT_NAME, "benchmark-report"),
|
||||
_artifact(
|
||||
staging / L34_PREDICTIONS_NAME,
|
||||
"frozen-candidate-predictions",
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
_write_json(staging / L34_MANIFEST_NAME, manifest)
|
||||
os.replace(staging, destination)
|
||||
except Exception:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l34_right_yolox_truth_island_freeze(destination)
|
||||
|
||||
|
||||
def read_l34_right_yolox_truth_island_freeze(root: Path) -> L34RightYoloxTruthIsland:
|
||||
resolved = _directory(root)
|
||||
manifest = _read_json(resolved / L34_MANIFEST_NAME)
|
||||
report = _read_json(resolved / L34_REPORT_NAME)
|
||||
predictions = tuple(_read_jsonl(resolved / L34_PREDICTIONS_NAME))
|
||||
identity = _object(manifest.get("identity"), "L34 identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
if (
|
||||
manifest.get("schema_version") != L34_RESULT_SCHEMA
|
||||
or not _RESULT_ID.fullmatch(resolved.name)
|
||||
or manifest.get("result_id") != resolved.name
|
||||
or not isinstance(identity_sha256, str)
|
||||
or resolved.name != f"l34-right-yolox-truth-island-freeze-{identity_sha256}"
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
!= identity_sha256
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or identity.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L34 manifest identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise L34RightYoloxTruthIslandError("L34 artifacts are invalid")
|
||||
for item in artifacts:
|
||||
artifact = _object(item, "L34 artifact")
|
||||
path = resolved / str(artifact.get("path"))
|
||||
if (
|
||||
path.parent != resolved
|
||||
or not path.is_file()
|
||||
or path.is_symlink()
|
||||
or path.stat().st_size != artifact.get("byte_length")
|
||||
or _sha256(path) != artifact.get("sha256")
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L34 artifact changed")
|
||||
if (
|
||||
report.get("schema_version") != L34_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("identity_sha256") != identity_sha256
|
||||
or report.get("status")
|
||||
!= "predictions-frozen-awaiting-independent-truth"
|
||||
or report.get("camera_source_id") != "sensor.camera.right"
|
||||
or _object(report.get("decision"), "L34 decision").get(
|
||||
"truth_labels_read"
|
||||
)
|
||||
is not False
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L34 result contract is invalid")
|
||||
sequences: list[int] = []
|
||||
for row in predictions:
|
||||
sequence = _integer(
|
||||
row.get("truth_island_sequence"),
|
||||
"prediction truth island sequence",
|
||||
)
|
||||
source_image_sha256 = row.get("source_image_sha256")
|
||||
if (
|
||||
row.get("schema_version") != L34_PREDICTION_SCHEMA
|
||||
or row.get("candidate_id") != _CANDIDATE_ID
|
||||
or row.get("truth_joined") is not False
|
||||
or not isinstance(source_image_sha256, str)
|
||||
or not _SHA256.fullmatch(source_image_sha256)
|
||||
or not isinstance(row.get("session_seconds"), (int, float))
|
||||
or isinstance(row.get("session_seconds"), bool)
|
||||
or not math.isfinite(float(row["session_seconds"]))
|
||||
or not isinstance(row.get("predictions"), list)
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError(
|
||||
"L34 prediction identity is invalid"
|
||||
)
|
||||
sequences.append(sequence)
|
||||
if len(set(sequences)) != len(sequences):
|
||||
raise L34RightYoloxTruthIslandError(
|
||||
"L34 prediction sequence is duplicated"
|
||||
)
|
||||
metrics = _object(report.get("metrics"), "L34 metrics")
|
||||
if (
|
||||
metrics.get("frame_count") != len(predictions)
|
||||
or metrics.get("accuracy_metrics_available") is not False
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L34 metrics are invalid")
|
||||
return L34RightYoloxTruthIsland(
|
||||
result_id=resolved.name,
|
||||
result_root=resolved,
|
||||
manifest=manifest,
|
||||
report=report,
|
||||
predictions=predictions,
|
||||
)
|
||||
|
||||
|
||||
def _read_profile(path: Path) -> dict[str, Any]:
|
||||
profile = _read_json(path.expanduser().resolve(strict=True))
|
||||
candidate = _object(profile.get("candidate"), "benchmark candidate")
|
||||
if (
|
||||
profile.get("schema_version") != L34_PROFILE_SCHEMA
|
||||
or profile.get("profile_id") != "RAVNOVES00_RIGHT_YOLOX_TRUTH_ISLAND_V1"
|
||||
or profile.get("pipeline_id")
|
||||
!= "kb4-core3-yolox-eomt-k1-lidar-e23-temporal/v1"
|
||||
or profile.get("mode") != "recorded-replay-only"
|
||||
or profile.get("source_session_id") != "20260720T065719Z_viewer_live"
|
||||
or profile.get("camera_source_id") != "sensor.camera.right"
|
||||
or profile.get("target_classes") != list(_TARGET_CLASSES)
|
||||
or candidate.get("architecture") != "YOLOX-S"
|
||||
or candidate.get("candidate_id") != _CANDIDATE_ID
|
||||
or not isinstance(candidate.get("model_sha256"), str)
|
||||
or candidate.get("minimum_score") != 0.25
|
||||
or profile.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("L34 profile is invalid")
|
||||
return profile
|
||||
|
||||
|
||||
def _selected_detector_frames(
|
||||
path: Path,
|
||||
target_indices: set[int],
|
||||
) -> dict[int, dict[str, Any]]:
|
||||
selected: dict[int, dict[str, Any]] = {}
|
||||
for row in _read_jsonl(path):
|
||||
frame_index = _integer(row.get("frame_index"), "detector frame index")
|
||||
if frame_index not in target_indices:
|
||||
continue
|
||||
if (
|
||||
row.get("schema_version") != _DETECTOR_FRAME_SCHEMA
|
||||
or frame_index in selected
|
||||
or not isinstance(row.get("detections"), list)
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("detector frame is invalid")
|
||||
selected[frame_index] = row
|
||||
if set(selected) != target_indices:
|
||||
raise L34RightYoloxTruthIslandError("detector frame coverage is incomplete")
|
||||
return selected
|
||||
|
||||
|
||||
def freeze_l34_candidate_predictions(
|
||||
*,
|
||||
references: tuple[dict[str, Any], ...],
|
||||
detector_frames: dict[int, dict[str, Any]],
|
||||
minimum_score: float,
|
||||
) -> tuple[dict[str, Any], ...]:
|
||||
"""Create the deterministic prediction freeze for an exact blind island."""
|
||||
|
||||
if not 0.0 < minimum_score < 1.0:
|
||||
raise L34RightYoloxTruthIslandError("minimum score is invalid")
|
||||
target_indices = {
|
||||
_integer(reference.get("frame_index"), "frame index")
|
||||
for reference in references
|
||||
}
|
||||
if set(detector_frames) != target_indices:
|
||||
raise L34RightYoloxTruthIslandError("detector frame coverage is incomplete")
|
||||
return tuple(
|
||||
_prediction_row(
|
||||
reference=reference,
|
||||
frame=detector_frames[
|
||||
_integer(reference.get("frame_index"), "frame index")
|
||||
],
|
||||
minimum_score=minimum_score,
|
||||
)
|
||||
for reference in references
|
||||
)
|
||||
|
||||
|
||||
def _prediction_row(
|
||||
*,
|
||||
reference: dict[str, Any],
|
||||
frame: dict[str, Any],
|
||||
minimum_score: float,
|
||||
) -> dict[str, Any]:
|
||||
predictions: list[dict[str, Any]] = []
|
||||
for raw in frame["detections"]:
|
||||
detection = _object(raw, "detector prediction")
|
||||
label = str(detection.get("label"))
|
||||
score = _number(detection.get("score"), "detector score")
|
||||
if label not in _DETECTOR_LABELS or score < minimum_score:
|
||||
continue
|
||||
bbox = detection.get("bbox_xyxy")
|
||||
if (
|
||||
not isinstance(bbox, list)
|
||||
or len(bbox) != 4
|
||||
or any(not isinstance(value, (int, float)) for value in bbox)
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError("detector box is invalid")
|
||||
predictions.append(
|
||||
{
|
||||
"label": "heavy_vehicle" if label in {"truck", "bus"} else label,
|
||||
"score": score,
|
||||
"bbox_xyxy": [float(value) for value in bbox],
|
||||
}
|
||||
)
|
||||
predictions.sort(
|
||||
key=lambda item: (-float(item["score"]), str(item["label"]))
|
||||
)
|
||||
return {
|
||||
"schema_version": L34_PREDICTION_SCHEMA,
|
||||
"candidate_id": _CANDIDATE_ID,
|
||||
"truth_island_sequence": _integer(
|
||||
reference.get("truth_island_sequence"),
|
||||
"truth island sequence",
|
||||
),
|
||||
"image_id": _integer(reference.get("image_id"), "image id"),
|
||||
"frame_index": _integer(reference.get("frame_index"), "frame index"),
|
||||
"session_seconds": _number(
|
||||
reference.get("session_seconds"),
|
||||
"session seconds",
|
||||
),
|
||||
"source_image_sha256": _sha256_text(
|
||||
reference.get("sha256"),
|
||||
"source image sha256",
|
||||
),
|
||||
"group_id": str(reference.get("group_id")),
|
||||
"predictions": predictions,
|
||||
"truth_joined": False,
|
||||
}
|
||||
|
||||
|
||||
def _directory(path: Path) -> Path:
|
||||
candidate = path.expanduser().absolute()
|
||||
if candidate.is_symlink():
|
||||
raise L34RightYoloxTruthIslandError("source directory is invalid")
|
||||
try:
|
||||
resolved = candidate.resolve(strict=True)
|
||||
except OSError as reason:
|
||||
raise L34RightYoloxTruthIslandError("source directory is unavailable") from reason
|
||||
if not resolved.is_dir():
|
||||
raise L34RightYoloxTruthIslandError("source directory is invalid")
|
||||
return resolved
|
||||
|
||||
|
||||
def _artifact(path: Path, kind: str) -> dict[str, object]:
|
||||
return {
|
||||
"kind": kind,
|
||||
"path": path.name,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_text(
|
||||
json.dumps(value, ensure_ascii=False, sort_keys=True, indent=2) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as reason:
|
||||
raise L34RightYoloxTruthIslandError(f"invalid JSON: {path.name}") from reason
|
||||
return _object(value, path.name)
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
try:
|
||||
with path.open("r", encoding="utf-8") as stream:
|
||||
for line in stream:
|
||||
if line.strip():
|
||||
rows.append(_object(json.loads(line), path.name))
|
||||
except (OSError, json.JSONDecodeError) as reason:
|
||||
raise L34RightYoloxTruthIslandError(f"invalid JSONL: {path.name}") from reason
|
||||
return rows
|
||||
|
||||
|
||||
def _object(value: object, label: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise L34RightYoloxTruthIslandError(f"{label} must be an object")
|
||||
return value
|
||||
|
||||
|
||||
def _integer(value: object, label: str) -> int:
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise L34RightYoloxTruthIslandError(f"{label} must be an integer")
|
||||
return value
|
||||
|
||||
|
||||
def _number(value: object, label: str) -> float:
|
||||
if (
|
||||
not isinstance(value, (int, float))
|
||||
or isinstance(value, bool)
|
||||
or not math.isfinite(float(value))
|
||||
):
|
||||
raise L34RightYoloxTruthIslandError(f"{label} must be numeric")
|
||||
return float(value)
|
||||
|
||||
|
||||
def _sha256_text(value: object, label: str) -> str:
|
||||
if not isinstance(value, str) or not _SHA256.fullmatch(value):
|
||||
raise L34RightYoloxTruthIslandError(f"{label} must be a SHA-256 digest")
|
||||
return value
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(UTC).isoformat(timespec="milliseconds").replace(
|
||||
"+00:00",
|
||||
"Z",
|
||||
)
|
||||
@@ -0,0 +1,781 @@
|
||||
"""Build a deterministic error audit against one assisted L3.4 review.
|
||||
|
||||
The result is deliberately not ground truth. It compares the immutable L3.4
|
||||
candidate freeze with a complete, candidate-seeded annotation session so that
|
||||
engineering failure modes can be inspected without opening the independent
|
||||
E48/L3.5 acceptance gate.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from .l34_right_yolox_truth_island_freeze import (
|
||||
L34_MANIFEST_NAME,
|
||||
L34RightYoloxTruthIslandError,
|
||||
read_l34_right_yolox_truth_island_freeze,
|
||||
)
|
||||
|
||||
L34A_RESULT_SCHEMA: Final = "missioncore.l34a-assisted-yolox-error-audit/v1"
|
||||
L34A_REPORT_SCHEMA: Final = "missioncore.l34a-assisted-yolox-error-report/v1"
|
||||
L34A_CASE_SCHEMA: Final = "missioncore.l34a-assisted-yolox-error-case/v1"
|
||||
L34A_MANIFEST_NAME: Final = "manifest.json"
|
||||
L34A_REPORT_NAME: Final = "assisted-error-report.json"
|
||||
L34A_CASES_NAME: Final = "assisted-error-cases.jsonl"
|
||||
|
||||
_ANNOTATION_SCHEMA: Final = "missioncore.l34-annotation-session/v3"
|
||||
_RESULT_ID = re.compile(r"^l34a-assisted-yolox-error-audit-[a-f0-9]{64}$")
|
||||
_SESSION_ID = re.compile(r"^l34-annotation-session-[a-f0-9]{64}$")
|
||||
_IOU_THRESHOLD: Final = 0.5
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L34AAssistedYoloxErrorAuditError(RuntimeError):
|
||||
"""The assisted audit input or immutable result is invalid."""
|
||||
|
||||
|
||||
def build_l34a_assisted_yolox_error_audit(
|
||||
*,
|
||||
l34_freeze_root: Path,
|
||||
annotation_session_path: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Compare the exact L3.4 freeze with a complete assisted review."""
|
||||
|
||||
try:
|
||||
freeze = read_l34_right_yolox_truth_island_freeze(l34_freeze_root)
|
||||
except L34RightYoloxTruthIslandError as reason:
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
"L3.4 candidate freeze is invalid"
|
||||
) from reason
|
||||
session_path = annotation_session_path.expanduser().resolve(strict=True)
|
||||
if not session_path.is_file() or session_path.is_symlink():
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
"assisted annotation session is unavailable"
|
||||
)
|
||||
session = _read_json(session_path)
|
||||
_validate_session(session, freeze_result_id=freeze.result_id)
|
||||
|
||||
predictions_by_sequence = {
|
||||
_integer(row.get("truth_island_sequence"), "prediction sequence"): row
|
||||
for row in freeze.predictions
|
||||
}
|
||||
frames = session["frames"]
|
||||
if set(predictions_by_sequence) != {
|
||||
_integer(frame.get("truth_island_sequence"), "annotation sequence")
|
||||
for frame in frames
|
||||
}:
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
"candidate and annotation coverage differ"
|
||||
)
|
||||
|
||||
cases = tuple(
|
||||
_audit_case(
|
||||
prediction_row=predictions_by_sequence[
|
||||
_integer(frame.get("truth_island_sequence"), "annotation sequence")
|
||||
],
|
||||
annotation_frame=frame,
|
||||
)
|
||||
for frame in sorted(
|
||||
frames,
|
||||
key=lambda value: _integer(
|
||||
value.get("truth_island_sequence"),
|
||||
"annotation sequence",
|
||||
),
|
||||
)
|
||||
)
|
||||
aggregate = _aggregate(cases)
|
||||
per_class = _per_class(cases)
|
||||
report_basis = {
|
||||
"schema_version": L34A_REPORT_SCHEMA,
|
||||
"status": "completed-assisted-candidate-error-audit-not-truth",
|
||||
"profile": {
|
||||
"profile_id": "l34a-assisted-yolox-error-audit/v1",
|
||||
"matcher": "greedy-maximum-iou",
|
||||
"iou_threshold": _IOU_THRESHOLD,
|
||||
"duplicate_rule": "same-class-iou-0.30-or-overlap-over-smaller-0.70",
|
||||
"class_policy": "spatial-match-first-then-class-verdict",
|
||||
"mismatch_accounting": "one-false-positive-plus-one-false-negative",
|
||||
"score_visibility": "candidate-scores-used-for-display-and-ordering",
|
||||
},
|
||||
"metrics": {
|
||||
**aggregate,
|
||||
"per_class": per_class,
|
||||
},
|
||||
"case_order": [
|
||||
case["truth_island_sequence"]
|
||||
for case in sorted(
|
||||
cases,
|
||||
key=lambda item: (
|
||||
-int(item["summary"]["severity_score"]),
|
||||
int(item["truth_island_sequence"]),
|
||||
),
|
||||
)
|
||||
],
|
||||
"decision": {
|
||||
"assisted_alignment_available": True,
|
||||
"blind_accuracy_available": False,
|
||||
"postprocessing_issue_confirmed": aggregate["duplicate_false_positive"] > 0,
|
||||
"ontology_gap_confirmed": aggregate["custom_reference_count"] > 0,
|
||||
"candidate_accepted": False,
|
||||
"model_retraining_authorized": False,
|
||||
"l35_blind_gate_open": False,
|
||||
"next_action": (
|
||||
"use the visual FP/FN/mismatch audit to scope NMS, class mapping "
|
||||
"and detector-data work without claiming independent accuracy"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
"the review was seeded from the same frozen candidate and is not independent truth",
|
||||
"precision, recall and F1 are assisted diagnostic alignment metrics, not acceptance metrics",
|
||||
"custom labels are retained as proposed ontology terms and were not adjudicated",
|
||||
"the result is source-scoped to 32 RAVNOVES00 right-camera frames",
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
l34_identity = _object(freeze.manifest.get("identity"), "L3.4 identity")
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "deterministic",
|
||||
"pipeline_id": "ravnoves00-right-yolox-assisted-error-audit/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": freeze.result_id,
|
||||
"version": "L3.4 immutable YOLOX candidate freeze",
|
||||
"role": "prediction substrate",
|
||||
"identity_sha256": _sha256(
|
||||
freeze.result_root / L34_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": session["session_id"],
|
||||
"version": "candidate-seeded assisted review; not truth",
|
||||
"role": "engineering reference annotations",
|
||||
"identity_sha256": _sha256(session_path),
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "greedy-maximum-iou-diagnostic-matcher",
|
||||
"version": f"v1-iou-{_IOU_THRESHOLD:.2f}",
|
||||
"role": "TP, FP, FN, duplicate and class-mismatch accounting",
|
||||
"identity_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
},
|
||||
],
|
||||
}
|
||||
identity = {
|
||||
"schema_version": L34A_RESULT_SCHEMA,
|
||||
"l34_freeze": {
|
||||
"result_id": freeze.result_id,
|
||||
"manifest_sha256": _sha256(freeze.result_root / L34_MANIFEST_NAME),
|
||||
"prediction_rows_sha256": _object(
|
||||
l34_identity.get("candidate"),
|
||||
"L3.4 candidate identity",
|
||||
).get("prediction_rows_sha256"),
|
||||
},
|
||||
"assisted_annotation": {
|
||||
"session_id": session["session_id"],
|
||||
"session_sha256": _sha256(session_path),
|
||||
"revision": session["revision"],
|
||||
"updated_at_utc": session["updated_at_utc"],
|
||||
"independent_truth_eligible": False,
|
||||
},
|
||||
"method": method,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34a-assisted-yolox-error-audit-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l34a_assisted_yolox_error_audit(destination)
|
||||
|
||||
created_at_utc = _utc_now()
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"method": method,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / L34A_REPORT_NAME, report)
|
||||
_write_jsonl(staging / L34A_CASES_NAME, cases)
|
||||
manifest = {
|
||||
"schema_version": L34A_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "accepted-assisted-diagnostic-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / L34A_REPORT_NAME, "assisted-error-report"),
|
||||
_artifact(staging / L34A_CASES_NAME, "assisted-error-cases"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
_write_json(staging / L34A_MANIFEST_NAME, manifest)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l34a_assisted_yolox_error_audit(destination)
|
||||
|
||||
|
||||
def read_l34a_assisted_yolox_error_audit(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully revalidate one immutable assisted audit."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / L34A_MANIFEST_NAME)
|
||||
identity = _object(manifest.get("identity"), "L3.4A identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
if (
|
||||
manifest.get("schema_version") != L34A_RESULT_SCHEMA
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
|
||||
or manifest.get("result_id")
|
||||
!= f"l34a-assisted-yolox-error-audit-{identity_sha256}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("acceptance_state")
|
||||
!= "accepted-assisted-diagnostic-not-truth"
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError("L3.4A identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise L34AAssistedYoloxErrorAuditError("L3.4A artifacts are invalid")
|
||||
artifact_by_role = {
|
||||
_text(item.get("role"), "artifact role"): _object(item, "artifact")
|
||||
for item in artifacts
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
report_path = _validated_artifact(
|
||||
resolved,
|
||||
artifact_by_role.get("assisted-error-report"),
|
||||
)
|
||||
cases_path = _validated_artifact(
|
||||
resolved,
|
||||
artifact_by_role.get("assisted-error-cases"),
|
||||
)
|
||||
report = _read_json(report_path)
|
||||
cases = tuple(_read_jsonl(cases_path))
|
||||
if (
|
||||
report.get("schema_version") != L34A_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("identity_sha256") != identity_sha256
|
||||
or report.get("status")
|
||||
!= "completed-assisted-candidate-error-audit-not-truth"
|
||||
or report.get("ground_truth") is not False
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != L34A_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest()
|
||||
!= identity.get("cases_sha256")
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError("L3.4A result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _validate_session(session: dict[str, Any], *, freeze_result_id: str) -> None:
|
||||
frames = session.get("frames")
|
||||
progress = session.get("progress")
|
||||
if (
|
||||
session.get("schema_version") != _ANNOTATION_SCHEMA
|
||||
or not isinstance(session.get("session_id"), str)
|
||||
or _SESSION_ID.fullmatch(session["session_id"]) is None
|
||||
or session.get("result_id") != freeze_result_id
|
||||
or session.get("state") != "saved"
|
||||
or not isinstance(session.get("revision"), int)
|
||||
or session["revision"] < 1
|
||||
or _object(session.get("assistance"), "assistance").get(
|
||||
"independent_truth_eligible"
|
||||
)
|
||||
is not False
|
||||
or _object(session.get("authority"), "authority") != _AUTHORITY
|
||||
or not isinstance(frames, list)
|
||||
or len(frames) != 32
|
||||
or (progress is not None and not isinstance(progress, dict))
|
||||
or not isinstance(session.get("updated_at_utc"), str)
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
"assisted annotation session contract is invalid"
|
||||
)
|
||||
sequences: set[int] = set()
|
||||
for frame in frames:
|
||||
item = _object(frame, "annotation frame")
|
||||
sequence = _integer(item.get("truth_island_sequence"), "annotation sequence")
|
||||
objects = item.get("objects")
|
||||
if (
|
||||
not 1 <= sequence <= 32
|
||||
or sequence in sequences
|
||||
or item.get("reviewed") is not True
|
||||
or not isinstance(objects, list)
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
"assisted annotation coverage is incomplete"
|
||||
)
|
||||
sequences.add(sequence)
|
||||
for value in objects:
|
||||
obj = _object(value, "annotation object")
|
||||
category = obj.get("category")
|
||||
proposed = obj.get("proposed_label")
|
||||
if (
|
||||
not isinstance(obj.get("object_id"), str)
|
||||
or not isinstance(category, str)
|
||||
or not _valid_box(obj.get("box_xyxy"))
|
||||
or obj.get("origin") not in {"manual", "frozen_candidate_seed"}
|
||||
or (category == "unmapped" and not isinstance(proposed, str))
|
||||
or (category != "unmapped" and proposed is not None)
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
"assisted annotation object is invalid"
|
||||
)
|
||||
|
||||
|
||||
def _audit_case(
|
||||
*,
|
||||
prediction_row: dict[str, Any],
|
||||
annotation_frame: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
sequence = _integer(
|
||||
prediction_row.get("truth_island_sequence"),
|
||||
"prediction sequence",
|
||||
)
|
||||
if (
|
||||
annotation_frame.get("truth_island_sequence") != sequence
|
||||
or annotation_frame.get("image_id") != prediction_row.get("image_id")
|
||||
or annotation_frame.get("frame_index") != prediction_row.get("frame_index")
|
||||
or annotation_frame.get("source_sha256")
|
||||
!= prediction_row.get("source_image_sha256")
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError("case source identity differs")
|
||||
raw_predictions = prediction_row.get("predictions")
|
||||
raw_annotations = annotation_frame.get("objects")
|
||||
if not isinstance(raw_predictions, list) or not isinstance(raw_annotations, list):
|
||||
raise L34AAssistedYoloxErrorAuditError("case objects are unavailable")
|
||||
|
||||
predictions = [
|
||||
{
|
||||
"prediction_index": index,
|
||||
"category": _text(item.get("label"), "prediction category"),
|
||||
"score": _finite(item.get("score"), "prediction score"),
|
||||
"box_xyxy": _box(item.get("bbox_xyxy"), "prediction box"),
|
||||
}
|
||||
for index, item in enumerate(
|
||||
(_object(value, "prediction") for value in raw_predictions),
|
||||
start=1,
|
||||
)
|
||||
]
|
||||
annotations = [
|
||||
{
|
||||
"object_id": _text(item.get("object_id"), "annotation object id"),
|
||||
"category": _text(item.get("category"), "annotation category"),
|
||||
"proposed_label": item.get("proposed_label"),
|
||||
"display_category": _display_reference_category(item),
|
||||
"origin": _text(item.get("origin"), "annotation origin"),
|
||||
"box_xyxy": _box(item.get("box_xyxy"), "annotation box"),
|
||||
"occluded": item.get("occluded") is True,
|
||||
"truncated": item.get("truncated") is True,
|
||||
}
|
||||
for item in (_object(value, "annotation") for value in raw_annotations)
|
||||
]
|
||||
candidates = sorted(
|
||||
(
|
||||
(_iou(prediction["box_xyxy"], annotation["box_xyxy"]), p_index, a_index)
|
||||
for p_index, prediction in enumerate(predictions)
|
||||
for a_index, annotation in enumerate(annotations)
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
matched_predictions: set[int] = set()
|
||||
matched_annotations: set[int] = set()
|
||||
matches: list[dict[str, Any]] = []
|
||||
for iou, prediction_index, annotation_index in candidates:
|
||||
if (
|
||||
iou < _IOU_THRESHOLD
|
||||
or prediction_index in matched_predictions
|
||||
or annotation_index in matched_annotations
|
||||
):
|
||||
continue
|
||||
matched_predictions.add(prediction_index)
|
||||
matched_annotations.add(annotation_index)
|
||||
prediction = predictions[prediction_index]
|
||||
annotation = annotations[annotation_index]
|
||||
verdict = (
|
||||
"true_positive"
|
||||
if prediction["category"] == annotation["category"]
|
||||
else "class_mismatch"
|
||||
)
|
||||
prediction.update(
|
||||
verdict=verdict,
|
||||
matched_object_id=annotation["object_id"],
|
||||
match_iou=iou,
|
||||
)
|
||||
annotation.update(
|
||||
verdict=verdict,
|
||||
matched_prediction_index=prediction["prediction_index"],
|
||||
match_iou=iou,
|
||||
)
|
||||
matches.append(
|
||||
{
|
||||
"prediction_index": prediction["prediction_index"],
|
||||
"object_id": annotation["object_id"],
|
||||
"iou": iou,
|
||||
"verdict": verdict,
|
||||
}
|
||||
)
|
||||
for index, prediction in enumerate(predictions):
|
||||
if index in matched_predictions:
|
||||
continue
|
||||
duplicate = any(
|
||||
prediction["category"] == annotation["category"]
|
||||
and (
|
||||
_iou(prediction["box_xyxy"], annotation["box_xyxy"]) >= 0.3
|
||||
or _overlap_over_smaller(
|
||||
prediction["box_xyxy"],
|
||||
annotation["box_xyxy"],
|
||||
)
|
||||
>= 0.7
|
||||
)
|
||||
for annotation in annotations
|
||||
)
|
||||
prediction.update(
|
||||
verdict="duplicate_false_positive" if duplicate else "false_positive",
|
||||
matched_object_id=None,
|
||||
match_iou=None,
|
||||
)
|
||||
for index, annotation in enumerate(annotations):
|
||||
if index in matched_annotations:
|
||||
continue
|
||||
annotation.update(
|
||||
verdict="false_negative",
|
||||
matched_prediction_index=None,
|
||||
match_iou=None,
|
||||
)
|
||||
|
||||
true_positive = sum(
|
||||
prediction["verdict"] == "true_positive" for prediction in predictions
|
||||
)
|
||||
class_mismatch = sum(
|
||||
prediction["verdict"] == "class_mismatch" for prediction in predictions
|
||||
)
|
||||
duplicate_false_positive = sum(
|
||||
prediction["verdict"] == "duplicate_false_positive"
|
||||
for prediction in predictions
|
||||
)
|
||||
unmatched_false_positive = sum(
|
||||
prediction["verdict"] == "false_positive" for prediction in predictions
|
||||
)
|
||||
unmatched_false_negative = sum(
|
||||
annotation["verdict"] == "false_negative" for annotation in annotations
|
||||
)
|
||||
false_positive = unmatched_false_positive + duplicate_false_positive + class_mismatch
|
||||
false_negative = unmatched_false_negative + class_mismatch
|
||||
summary = {
|
||||
"prediction_count": len(predictions),
|
||||
"reference_count": len(annotations),
|
||||
"true_positive": true_positive,
|
||||
"false_positive": false_positive,
|
||||
"false_negative": false_negative,
|
||||
"class_mismatch": class_mismatch,
|
||||
"duplicate_false_positive": duplicate_false_positive,
|
||||
"unmatched_false_positive": unmatched_false_positive,
|
||||
"unmatched_false_negative": unmatched_false_negative,
|
||||
"severity_score": (
|
||||
class_mismatch * 3
|
||||
+ duplicate_false_positive * 2
|
||||
+ unmatched_false_positive
|
||||
+ unmatched_false_negative * 2
|
||||
),
|
||||
}
|
||||
return {
|
||||
"schema_version": L34A_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": prediction_row["image_id"],
|
||||
"frame_index": prediction_row["frame_index"],
|
||||
"group_id": prediction_row["group_id"],
|
||||
"session_seconds": prediction_row["session_seconds"],
|
||||
"source_image_sha256": prediction_row["source_image_sha256"],
|
||||
"camera": {"width": 800, "height": 600},
|
||||
"predictions": predictions,
|
||||
"annotations": annotations,
|
||||
"matches": matches,
|
||||
"summary": summary,
|
||||
}
|
||||
|
||||
|
||||
def _aggregate(cases: tuple[dict[str, Any], ...]) -> dict[str, Any]:
|
||||
totals: defaultdict[str, int] = defaultdict(int)
|
||||
for case in cases:
|
||||
for key, value in _object(case.get("summary"), "case summary").items():
|
||||
if key != "severity_score":
|
||||
totals[key] += _integer(value, f"case summary {key}")
|
||||
true_positive = totals["true_positive"]
|
||||
false_positive = totals["false_positive"]
|
||||
false_negative = totals["false_negative"]
|
||||
precision = _ratio(true_positive, true_positive + false_positive)
|
||||
recall = _ratio(true_positive, true_positive + false_negative)
|
||||
f1 = _ratio(2 * precision * recall, precision + recall)
|
||||
custom_reference_count = sum(
|
||||
annotation["category"] == "unmapped"
|
||||
for case in cases
|
||||
for annotation in case["annotations"]
|
||||
)
|
||||
error_case_count = sum(
|
||||
case["summary"]["false_positive"] > 0
|
||||
or case["summary"]["false_negative"] > 0
|
||||
for case in cases
|
||||
)
|
||||
return {
|
||||
"frame_count": len(cases),
|
||||
**dict(totals),
|
||||
"precision_iou50": precision,
|
||||
"recall_iou50": recall,
|
||||
"f1_iou50": f1,
|
||||
"error_case_count": error_case_count,
|
||||
"custom_reference_count": custom_reference_count,
|
||||
}
|
||||
|
||||
|
||||
def _per_class(cases: tuple[dict[str, Any], ...]) -> dict[str, dict[str, Any]]:
|
||||
counts: defaultdict[str, defaultdict[str, int]] = defaultdict(
|
||||
lambda: defaultdict(int)
|
||||
)
|
||||
for case in cases:
|
||||
for prediction in case["predictions"]:
|
||||
category = prediction["category"]
|
||||
verdict = prediction["verdict"]
|
||||
if verdict == "true_positive":
|
||||
counts[category]["true_positive"] += 1
|
||||
elif verdict in {
|
||||
"false_positive",
|
||||
"duplicate_false_positive",
|
||||
"class_mismatch",
|
||||
}:
|
||||
counts[category]["false_positive"] += 1
|
||||
for annotation in case["annotations"]:
|
||||
category = annotation["display_category"]
|
||||
verdict = annotation["verdict"]
|
||||
counts[category]["reference_count"] += 1
|
||||
if verdict in {"false_negative", "class_mismatch"}:
|
||||
counts[category]["false_negative"] += 1
|
||||
projected: dict[str, dict[str, Any]] = {}
|
||||
for category, values in sorted(counts.items()):
|
||||
true_positive = values["true_positive"]
|
||||
false_positive = values["false_positive"]
|
||||
false_negative = values["false_negative"]
|
||||
precision = _ratio(true_positive, true_positive + false_positive)
|
||||
recall = _ratio(true_positive, true_positive + false_negative)
|
||||
projected[category] = {
|
||||
"reference_count": values["reference_count"],
|
||||
"true_positive": true_positive,
|
||||
"false_positive": false_positive,
|
||||
"false_negative": false_negative,
|
||||
"precision_iou50": precision,
|
||||
"recall_iou50": recall,
|
||||
}
|
||||
return projected
|
||||
|
||||
|
||||
def _display_reference_category(item: dict[str, Any]) -> str:
|
||||
if item.get("category") == "unmapped":
|
||||
return f"unmapped:{_text(item.get('proposed_label'), 'proposed label')}"
|
||||
return _text(item.get("category"), "annotation category")
|
||||
|
||||
|
||||
def _iou(left: list[float], right: list[float]) -> float:
|
||||
intersection_width = max(0.0, min(left[2], right[2]) - max(left[0], right[0]))
|
||||
intersection_height = max(0.0, min(left[3], right[3]) - max(left[1], right[1]))
|
||||
intersection = intersection_width * intersection_height
|
||||
left_area = (left[2] - left[0]) * (left[3] - left[1])
|
||||
right_area = (right[2] - right[0]) * (right[3] - right[1])
|
||||
union = left_area + right_area - intersection
|
||||
return intersection / union if union > 0 else 0.0
|
||||
|
||||
|
||||
def _overlap_over_smaller(left: list[float], right: list[float]) -> float:
|
||||
intersection_width = max(0.0, min(left[2], right[2]) - max(left[0], right[0]))
|
||||
intersection_height = max(0.0, min(left[3], right[3]) - max(left[1], right[1]))
|
||||
intersection = intersection_width * intersection_height
|
||||
smaller = min(
|
||||
(left[2] - left[0]) * (left[3] - left[1]),
|
||||
(right[2] - right[0]) * (right[3] - right[1]),
|
||||
)
|
||||
return intersection / smaller if smaller > 0 else 0.0
|
||||
|
||||
|
||||
def _ratio(numerator: float, denominator: float) -> float:
|
||||
return numerator / denominator if denominator > 0 else 0.0
|
||||
|
||||
|
||||
def _valid_box(value: object) -> bool:
|
||||
try:
|
||||
box = _box(value, "box")
|
||||
except L34AAssistedYoloxErrorAuditError:
|
||||
return False
|
||||
return 0 <= box[0] < box[2] <= 800 and 0 <= box[1] < box[3] <= 600
|
||||
|
||||
|
||||
def _box(value: object, label: str) -> list[float]:
|
||||
if not isinstance(value, list) or len(value) != 4:
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{label} is invalid")
|
||||
box = [_finite(item, label) for item in value]
|
||||
if not (0 <= box[0] < box[2] <= 800 and 0 <= box[1] < box[3] <= 600):
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{label} is invalid")
|
||||
return box
|
||||
|
||||
|
||||
def _finite(value: object, label: str) -> float:
|
||||
if (
|
||||
not isinstance(value, (int, float))
|
||||
or isinstance(value, bool)
|
||||
or not math.isfinite(float(value))
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{label} is invalid")
|
||||
return float(value)
|
||||
|
||||
|
||||
def _integer(value: object, label: str) -> int:
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _text(value: object, label: str) -> str:
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _object(value: object, label: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, artifact: dict[str, Any] | None) -> Path:
|
||||
if artifact is None:
|
||||
raise L34AAssistedYoloxErrorAuditError("L3.4A artifact is missing")
|
||||
path = (root / _text(artifact.get("path"), "artifact path")).resolve()
|
||||
if (
|
||||
path.parent != root
|
||||
or path.is_symlink()
|
||||
or not path.is_file()
|
||||
or path.stat().st_size != artifact.get("byte_length")
|
||||
or _sha256(path) != artifact.get("sha256")
|
||||
):
|
||||
raise L34AAssistedYoloxErrorAuditError("L3.4A artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError) as reason:
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
f"cannot read {path.name}"
|
||||
) from reason
|
||||
return _object(value, path.name)
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
try:
|
||||
rows = [
|
||||
json.loads(line)
|
||||
for line in path.read_text(encoding="utf-8").splitlines()
|
||||
if line
|
||||
]
|
||||
except (OSError, ValueError) as reason:
|
||||
raise L34AAssistedYoloxErrorAuditError(
|
||||
f"cannot read {path.name}"
|
||||
) from reason
|
||||
if any(not isinstance(row, dict) for row in rows):
|
||||
raise L34AAssistedYoloxErrorAuditError(f"{path.name} is invalid")
|
||||
return rows
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_text(
|
||||
json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, rows: tuple[dict[str, Any], ...]) -> None:
|
||||
path.write_text(
|
||||
"".join(
|
||||
json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n"
|
||||
for row in rows
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, object]:
|
||||
return {
|
||||
"role": role,
|
||||
"path": path.name,
|
||||
"media_type": (
|
||||
"application/x-ndjson" if path.suffix == ".jsonl" else "application/json"
|
||||
),
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(tz=UTC).isoformat(timespec="milliseconds").replace(
|
||||
"+00:00",
|
||||
"Z",
|
||||
)
|
||||
@@ -0,0 +1,623 @@
|
||||
"""Build an immutable L3.4B nested-box consolidation shadow.
|
||||
|
||||
The shadow applies one deliberately narrow post-processing rule to the exact
|
||||
L3.4 freeze: predictions of the same normalized category are consolidated only
|
||||
when at least 95 percent of the smaller box is covered by the other box. It is
|
||||
an assisted engineering diagnostic, never independent truth or an acceptance
|
||||
result.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from .l34_right_yolox_truth_island_freeze import (
|
||||
L34_MANIFEST_NAME,
|
||||
L34RightYoloxTruthIslandError,
|
||||
read_l34_right_yolox_truth_island_freeze,
|
||||
)
|
||||
from .l34a_assisted_yolox_error_audit import (
|
||||
L34A_MANIFEST_NAME,
|
||||
L34AAssistedYoloxErrorAuditError,
|
||||
_aggregate,
|
||||
_audit_case,
|
||||
_overlap_over_smaller,
|
||||
_validate_session,
|
||||
read_l34a_assisted_yolox_error_audit,
|
||||
)
|
||||
|
||||
L34B_RESULT_SCHEMA: Final = "missioncore.l34b-nested-box-consolidation-shadow/v1"
|
||||
L34B_REPORT_SCHEMA: Final = "missioncore.l34b-nested-box-consolidation-report/v1"
|
||||
L34B_CASE_SCHEMA: Final = "missioncore.l34b-nested-box-consolidation-case/v1"
|
||||
L34B_MANIFEST_NAME: Final = "manifest.json"
|
||||
L34B_REPORT_NAME: Final = "nested-box-consolidation-report.json"
|
||||
L34B_CASES_NAME: Final = "nested-box-consolidation-cases.jsonl"
|
||||
L34B_OVERLAP_THRESHOLD: Final = 0.95
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34b-nested-box-consolidation-shadow-[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L34BNestedBoxConsolidationError(RuntimeError):
|
||||
"""An L3.4B input or immutable result is invalid."""
|
||||
|
||||
|
||||
def consolidate_l34b_prediction_row(
|
||||
prediction_row: dict[str, Any],
|
||||
) -> tuple[dict[str, Any], tuple[dict[str, Any], ...]]:
|
||||
"""Consolidate connected same-category nested boxes deterministically."""
|
||||
|
||||
raw_predictions = prediction_row.get("predictions")
|
||||
if not isinstance(raw_predictions, list):
|
||||
raise L34BNestedBoxConsolidationError("L3.4 predictions are unavailable")
|
||||
predictions = [_prediction(value, index) for index, value in enumerate(raw_predictions)]
|
||||
parents = list(range(len(predictions)))
|
||||
|
||||
def find(index: int) -> int:
|
||||
while parents[index] != index:
|
||||
parents[index] = parents[parents[index]]
|
||||
index = parents[index]
|
||||
return index
|
||||
|
||||
def union(left: int, right: int) -> None:
|
||||
left_root = find(left)
|
||||
right_root = find(right)
|
||||
if left_root == right_root:
|
||||
return
|
||||
lower, upper = sorted((left_root, right_root))
|
||||
parents[upper] = lower
|
||||
|
||||
for left_index, left in enumerate(predictions):
|
||||
for right_index in range(left_index + 1, len(predictions)):
|
||||
right = predictions[right_index]
|
||||
if (
|
||||
left["label"] == right["label"]
|
||||
and _overlap_over_smaller(
|
||||
left["bbox_xyxy"],
|
||||
right["bbox_xyxy"],
|
||||
)
|
||||
>= L34B_OVERLAP_THRESHOLD
|
||||
):
|
||||
union(left_index, right_index)
|
||||
|
||||
grouped: dict[int, list[int]] = {}
|
||||
for index in range(len(predictions)):
|
||||
grouped.setdefault(find(index), []).append(index)
|
||||
|
||||
output_predictions: list[dict[str, Any]] = []
|
||||
consolidations: list[dict[str, Any]] = []
|
||||
for component in sorted(grouped.values(), key=min):
|
||||
members = [predictions[index] for index in component]
|
||||
source_indices = [index + 1 for index in component]
|
||||
if len(component) == 1:
|
||||
output = copy.deepcopy(members[0])
|
||||
else:
|
||||
boxes = [member["bbox_xyxy"] for member in members]
|
||||
output = {
|
||||
"label": members[0]["label"],
|
||||
"score": max(member["score"] for member in members),
|
||||
"bbox_xyxy": [
|
||||
min(box[0] for box in boxes),
|
||||
min(box[1] for box in boxes),
|
||||
max(box[2] for box in boxes),
|
||||
max(box[3] for box in boxes),
|
||||
],
|
||||
}
|
||||
consolidations.append(
|
||||
{
|
||||
"category": output["label"],
|
||||
"source_prediction_indices": source_indices,
|
||||
"source_scores": [member["score"] for member in members],
|
||||
"source_boxes_xyxy": copy.deepcopy(boxes),
|
||||
"merged_score": output["score"],
|
||||
"merged_box_xyxy": copy.deepcopy(output["bbox_xyxy"]),
|
||||
"minimum_overlap_over_smaller": min(
|
||||
_overlap_over_smaller(
|
||||
members[left]["bbox_xyxy"],
|
||||
members[right]["bbox_xyxy"],
|
||||
)
|
||||
for left in range(len(members))
|
||||
for right in range(left + 1, len(members))
|
||||
),
|
||||
"output_prediction_index": len(output_predictions) + 1,
|
||||
}
|
||||
)
|
||||
output["source_prediction_indices"] = source_indices
|
||||
output_predictions.append(output)
|
||||
|
||||
projected = copy.deepcopy(prediction_row)
|
||||
projected["predictions"] = output_predictions
|
||||
return projected, tuple(consolidations)
|
||||
|
||||
|
||||
def evaluate_l34b_shadow(
|
||||
*,
|
||||
prediction_rows: tuple[dict[str, Any], ...],
|
||||
annotation_frames: tuple[dict[str, Any], ...],
|
||||
before_cases: tuple[dict[str, Any], ...],
|
||||
) -> tuple[tuple[dict[str, Any], ...], dict[str, Any]]:
|
||||
"""Apply the rule and return source-bound before/after cases and metrics."""
|
||||
|
||||
annotations_by_sequence = {
|
||||
_integer(frame.get("truth_island_sequence"), "annotation sequence"): frame
|
||||
for frame in annotation_frames
|
||||
}
|
||||
before_by_sequence = {
|
||||
_integer(case.get("truth_island_sequence"), "before sequence"): case
|
||||
for case in before_cases
|
||||
}
|
||||
if (
|
||||
len(prediction_rows) != 32
|
||||
or len(annotations_by_sequence) != 32
|
||||
or len(before_by_sequence) != 32
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError("L3.4B requires 32 bound cases")
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
after_audits: list[dict[str, Any]] = []
|
||||
for row in prediction_rows:
|
||||
sequence = _integer(row.get("truth_island_sequence"), "prediction sequence")
|
||||
projected, consolidations = consolidate_l34b_prediction_row(row)
|
||||
after = _audit_case(
|
||||
prediction_row=projected,
|
||||
annotation_frame=annotations_by_sequence[sequence],
|
||||
)
|
||||
for prediction, source in zip(
|
||||
after["predictions"],
|
||||
projected["predictions"],
|
||||
strict=True,
|
||||
):
|
||||
prediction["source_prediction_indices"] = copy.deepcopy(
|
||||
source["source_prediction_indices"]
|
||||
)
|
||||
before = before_by_sequence[sequence]
|
||||
if (
|
||||
before.get("source_image_sha256") != after.get("source_image_sha256")
|
||||
or before.get("frame_index") != after.get("frame_index")
|
||||
or before.get("image_id") != after.get("image_id")
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError("L3.4B case binding changed")
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": L34B_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": after["image_id"],
|
||||
"frame_index": after["frame_index"],
|
||||
"group_id": after["group_id"],
|
||||
"session_seconds": after["session_seconds"],
|
||||
"source_image_sha256": after["source_image_sha256"],
|
||||
"camera": copy.deepcopy(after["camera"]),
|
||||
"before_predictions": copy.deepcopy(before["predictions"]),
|
||||
"after_predictions": copy.deepcopy(after["predictions"]),
|
||||
"annotations": copy.deepcopy(after["annotations"]),
|
||||
"consolidations": list(consolidations),
|
||||
"before_summary": copy.deepcopy(before["summary"]),
|
||||
"after_summary": copy.deepcopy(after["summary"]),
|
||||
}
|
||||
)
|
||||
after_audits.append(after)
|
||||
|
||||
before_metrics = _aggregate(tuple(before_cases))
|
||||
after_metrics = _aggregate(tuple(after_audits))
|
||||
delta = {
|
||||
key: after_metrics[key] - before_metrics[key]
|
||||
for key in (
|
||||
"prediction_count",
|
||||
"true_positive",
|
||||
"false_positive",
|
||||
"false_negative",
|
||||
"class_mismatch",
|
||||
"duplicate_false_positive",
|
||||
"unmatched_false_positive",
|
||||
"unmatched_false_negative",
|
||||
"precision_iou50",
|
||||
"recall_iou50",
|
||||
"f1_iou50",
|
||||
"error_case_count",
|
||||
)
|
||||
}
|
||||
consolidation_count = sum(len(case["consolidations"]) for case in cases)
|
||||
affected_case_count = sum(bool(case["consolidations"]) for case in cases)
|
||||
regression_free = (
|
||||
consolidation_count > 0
|
||||
and delta["true_positive"] >= 0
|
||||
and delta["false_positive"] < 0
|
||||
and delta["false_negative"] <= 0
|
||||
and delta["class_mismatch"] <= 0
|
||||
)
|
||||
metrics = {
|
||||
"before": before_metrics,
|
||||
"after": after_metrics,
|
||||
"delta": delta,
|
||||
"consolidation_count": consolidation_count,
|
||||
"affected_case_count": affected_case_count,
|
||||
"assisted_regression_free": regression_free,
|
||||
}
|
||||
return tuple(cases), metrics
|
||||
|
||||
|
||||
def build_l34b_nested_box_consolidation_shadow(
|
||||
*,
|
||||
l34_freeze_root: Path,
|
||||
l34a_audit_root: Path,
|
||||
annotation_session_path: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Build and publish one immutable L3.4B result."""
|
||||
|
||||
try:
|
||||
freeze = read_l34_right_yolox_truth_island_freeze(l34_freeze_root)
|
||||
l34a = read_l34a_assisted_yolox_error_audit(l34a_audit_root)
|
||||
except (L34RightYoloxTruthIslandError, L34AAssistedYoloxErrorAuditError) as reason:
|
||||
raise L34BNestedBoxConsolidationError("L3.4/L3.4A input is invalid") from reason
|
||||
session_path = annotation_session_path.expanduser().resolve(strict=True)
|
||||
if not session_path.is_file() or session_path.is_symlink():
|
||||
raise L34BNestedBoxConsolidationError("annotation session is unavailable")
|
||||
session = _read_json(session_path)
|
||||
try:
|
||||
_validate_session(session, freeze_result_id=freeze.result_id)
|
||||
except L34AAssistedYoloxErrorAuditError as reason:
|
||||
raise L34BNestedBoxConsolidationError("annotation session is invalid") from reason
|
||||
l34a_identity = _object(l34a["manifest"].get("identity"), "L3.4A identity")
|
||||
l34a_freeze = _object(l34a_identity.get("l34_freeze"), "L3.4A freeze")
|
||||
l34a_annotation = _object(
|
||||
l34a_identity.get("assisted_annotation"),
|
||||
"L3.4A annotation",
|
||||
)
|
||||
if (
|
||||
l34a_freeze.get("result_id") != freeze.result_id
|
||||
or l34a_annotation.get("session_id") != session.get("session_id")
|
||||
or l34a_annotation.get("session_sha256") != _sha256(session_path)
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError("L3.4B lineage differs")
|
||||
|
||||
cases, metrics = evaluate_l34b_shadow(
|
||||
prediction_rows=freeze.predictions,
|
||||
annotation_frames=tuple(session["frames"]),
|
||||
before_cases=l34a["cases"],
|
||||
)
|
||||
affected_sequences = [
|
||||
case["truth_island_sequence"] for case in cases if case["consolidations"]
|
||||
]
|
||||
case_order = affected_sequences + [
|
||||
case["truth_island_sequence"]
|
||||
for case in sorted(
|
||||
(item for item in cases if not item["consolidations"]),
|
||||
key=lambda item: (
|
||||
-int(item["after_summary"]["severity_score"]),
|
||||
int(item["truth_island_sequence"]),
|
||||
),
|
||||
)
|
||||
]
|
||||
report_basis = {
|
||||
"schema_version": L34B_REPORT_SCHEMA,
|
||||
"status": "completed-nested-box-consolidation-shadow-not-truth",
|
||||
"profile": {
|
||||
"profile_id": "l34b-nested-box-consolidation-shadow/v1",
|
||||
"category_policy": "same-normalized-category-only",
|
||||
"overlap_metric": "intersection-over-smaller-box-area",
|
||||
"overlap_threshold": L34B_OVERLAP_THRESHOLD,
|
||||
"geometry_policy": "union-box",
|
||||
"score_policy": "maximum-source-score",
|
||||
"scope": "frozen-prediction-postprocessing-only",
|
||||
},
|
||||
"metrics": metrics,
|
||||
"case_order": case_order,
|
||||
"decision": {
|
||||
"shadow_policy_accepted": metrics["assisted_regression_free"],
|
||||
"assisted_alignment_available": True,
|
||||
"blind_accuracy_available": False,
|
||||
"candidate_accepted": False,
|
||||
"model_retraining_authorized": False,
|
||||
"l35_blind_gate_open": False,
|
||||
"classic_iou_nms_fix_rejected": True,
|
||||
"remaining_l34a_duplicate_signals": metrics["after"][
|
||||
"duplicate_false_positive"
|
||||
],
|
||||
"next_action": (
|
||||
"preserve rectification_tile provenance and evaluate temporal "
|
||||
"left/front seam stitching; do not lower global IoU NMS"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
(
|
||||
"the comparison uses the same candidate-seeded assisted review "
|
||||
"as L3.4A and is not independent truth"
|
||||
),
|
||||
(
|
||||
"the accepted shadow rule changes one nested same-category pair "
|
||||
"on this 32-frame source scope"
|
||||
),
|
||||
(
|
||||
"four left/front tile seam splits remain and cannot be safely "
|
||||
"solved by lowering global IoU NMS"
|
||||
),
|
||||
"the L3.4 freeze does not retain rectification_tile on each prediction",
|
||||
"no live transport, hardware, LiDAR range, navigation or safety claim is made",
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "deterministic",
|
||||
"pipeline_id": "ravnoves00-right-yolox-nested-box-consolidation-shadow/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": freeze.result_id,
|
||||
"version": "L3.4 immutable candidate freeze",
|
||||
"role": "prediction substrate",
|
||||
"identity_sha256": _sha256(freeze.result_root / L34_MANIFEST_NAME),
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": l34a["result_id"],
|
||||
"version": "L3.4A assisted diagnostic; not truth",
|
||||
"role": "before-state and engineering comparison",
|
||||
"identity_sha256": _sha256(l34a["result_root"] / L34A_MANIFEST_NAME),
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "same-category-nested-box-union",
|
||||
"version": f"v1-overlap-{L34B_OVERLAP_THRESHOLD:.2f}",
|
||||
"role": "bounded post-normalization consolidation",
|
||||
"identity_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
},
|
||||
],
|
||||
}
|
||||
identity = {
|
||||
"schema_version": L34B_RESULT_SCHEMA,
|
||||
"l34_freeze": {
|
||||
"result_id": freeze.result_id,
|
||||
"manifest_sha256": _sha256(freeze.result_root / L34_MANIFEST_NAME),
|
||||
},
|
||||
"l34a_audit": {
|
||||
"result_id": l34a["result_id"],
|
||||
"manifest_sha256": _sha256(l34a["result_root"] / L34A_MANIFEST_NAME),
|
||||
},
|
||||
"assisted_annotation": {
|
||||
"session_id": session["session_id"],
|
||||
"session_sha256": _sha256(session_path),
|
||||
"independent_truth_eligible": False,
|
||||
},
|
||||
"method": method,
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34b-nested-box-consolidation-shadow-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l34b_nested_box_consolidation_shadow(destination)
|
||||
|
||||
created_at_utc = _utc_now()
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"method": method,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / L34B_REPORT_NAME, report)
|
||||
_write_jsonl(staging / L34B_CASES_NAME, cases)
|
||||
manifest = {
|
||||
"schema_version": L34B_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "accepted-assisted-shadow-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / L34B_REPORT_NAME, "nested-box-report"),
|
||||
_artifact(staging / L34B_CASES_NAME, "nested-box-cases"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
_write_json(staging / L34B_MANIFEST_NAME, manifest)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l34b_nested_box_consolidation_shadow(destination)
|
||||
|
||||
|
||||
def read_l34b_nested_box_consolidation_shadow(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully revalidate one immutable L3.4B result."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / L34B_MANIFEST_NAME)
|
||||
identity = _object(manifest.get("identity"), "L3.4B identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
if (
|
||||
manifest.get("schema_version") != L34B_RESULT_SCHEMA
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest() != identity_sha256
|
||||
or manifest.get("result_id") != f"l34b-nested-box-consolidation-shadow-{identity_sha256}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("acceptance_state") != "accepted-assisted-shadow-not-truth"
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError("L3.4B identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise L34BNestedBoxConsolidationError("L3.4B artifacts are invalid")
|
||||
artifact_by_role = {
|
||||
_text(item.get("role"), "artifact role"): _object(item, "artifact")
|
||||
for item in artifacts
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
report_path = _validated_artifact(resolved, artifact_by_role.get("nested-box-report"))
|
||||
cases_path = _validated_artifact(resolved, artifact_by_role.get("nested-box-cases"))
|
||||
report = _read_json(report_path)
|
||||
cases = tuple(_read_jsonl(cases_path))
|
||||
if (
|
||||
report.get("schema_version") != L34B_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("identity_sha256") != identity_sha256
|
||||
or report.get("status") != "completed-nested-box-consolidation-shadow-not-truth"
|
||||
or report.get("ground_truth") is not False
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != L34B_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest() != identity.get("cases_sha256")
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError("L3.4B result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _prediction(value: object, index: int) -> dict[str, Any]:
|
||||
item = _object(value, f"prediction {index + 1}")
|
||||
return {
|
||||
"label": _text(item.get("label"), "prediction label"),
|
||||
"score": _finite(item.get("score"), "prediction score"),
|
||||
"bbox_xyxy": _box(item.get("bbox_xyxy"), "prediction box"),
|
||||
}
|
||||
|
||||
|
||||
def _box(value: object, label: str) -> list[float]:
|
||||
if not isinstance(value, list) or len(value) != 4:
|
||||
raise L34BNestedBoxConsolidationError(f"{label} is invalid")
|
||||
box = [_finite(item, label) for item in value]
|
||||
if not (0 <= box[0] < box[2] <= 800 and 0 <= box[1] < box[3] <= 600):
|
||||
raise L34BNestedBoxConsolidationError(f"{label} is invalid")
|
||||
return box
|
||||
|
||||
|
||||
def _finite(value: object, label: str) -> float:
|
||||
if (
|
||||
not isinstance(value, (int, float))
|
||||
or isinstance(value, bool)
|
||||
or not math.isfinite(float(value))
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError(f"{label} is invalid")
|
||||
return float(value)
|
||||
|
||||
|
||||
def _integer(value: object, label: str) -> int:
|
||||
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
|
||||
raise L34BNestedBoxConsolidationError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _text(value: object, label: str) -> str:
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
raise L34BNestedBoxConsolidationError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _object(value: object, label: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise L34BNestedBoxConsolidationError(f"{label} is invalid")
|
||||
return value
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, artifact: dict[str, Any] | None) -> Path:
|
||||
if artifact is None:
|
||||
raise L34BNestedBoxConsolidationError("L3.4B artifact is missing")
|
||||
path = (root / _text(artifact.get("path"), "artifact path")).resolve()
|
||||
if (
|
||||
path.parent != root
|
||||
or path.is_symlink()
|
||||
or not path.is_file()
|
||||
or path.stat().st_size != artifact.get("byte_length")
|
||||
or _sha256(path) != artifact.get("sha256")
|
||||
):
|
||||
raise L34BNestedBoxConsolidationError("L3.4B artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError) as reason:
|
||||
raise L34BNestedBoxConsolidationError(f"cannot read {path.name}") from reason
|
||||
return _object(value, path.name)
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
try:
|
||||
rows = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
except (OSError, ValueError) as reason:
|
||||
raise L34BNestedBoxConsolidationError(f"cannot read {path.name}") from reason
|
||||
if any(not isinstance(row, dict) for row in rows):
|
||||
raise L34BNestedBoxConsolidationError(f"{path.name} is invalid")
|
||||
return rows
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_text(
|
||||
json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, rows: tuple[dict[str, Any], ...]) -> None:
|
||||
path.write_text(
|
||||
"".join(json.dumps(row, ensure_ascii=False, sort_keys=True) + "\n" for row in rows),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, object]:
|
||||
return {
|
||||
"role": role,
|
||||
"path": path.name,
|
||||
"media_type": "application/x-ndjson" if path.suffix == ".jsonl" else "application/json",
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as source:
|
||||
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,796 @@
|
||||
"""Freeze the cumulative L3.4B + L3.4C post-processing candidate.
|
||||
|
||||
The candidate composes only operations already admitted by the immutable
|
||||
L3.4B and L3.4C shadows. Both operation sets remain expressed against the
|
||||
original L3.4 prediction indices. Any overlap, lineage drift or operation
|
||||
payload mismatch fails closed instead of introducing an ordering policy.
|
||||
Assisted annotations are joined only after the deterministic projection and
|
||||
remain ineligible as independent truth.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from .l34a_assisted_yolox_error_audit import (
|
||||
L34A_MANIFEST_NAME,
|
||||
L34AAssistedYoloxErrorAuditError,
|
||||
_aggregate,
|
||||
_audit_case,
|
||||
_validate_session,
|
||||
read_l34a_assisted_yolox_error_audit,
|
||||
)
|
||||
from .l34b_nested_box_consolidation_shadow import (
|
||||
L34B_MANIFEST_NAME,
|
||||
L34BNestedBoxConsolidationError,
|
||||
read_l34b_nested_box_consolidation_shadow,
|
||||
)
|
||||
from .l34c_tile_seam_stitch_shadow import (
|
||||
L34C_MANIFEST_NAME,
|
||||
L34CTileSeamStitchError,
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_finite,
|
||||
_integer,
|
||||
_list,
|
||||
_object,
|
||||
_prediction,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_sha256,
|
||||
_text,
|
||||
_union_box,
|
||||
_utc_now,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
_write_jsonl,
|
||||
read_l34c_tile_seam_stitch_shadow,
|
||||
)
|
||||
|
||||
L34D_RESULT_SCHEMA: Final = (
|
||||
"missioncore.l34d-cumulative-postprocessing-candidate/v1"
|
||||
)
|
||||
L34D_REPORT_SCHEMA: Final = (
|
||||
"missioncore.l34d-cumulative-postprocessing-report/v1"
|
||||
)
|
||||
L34D_CASE_SCHEMA: Final = (
|
||||
"missioncore.l34d-cumulative-postprocessing-case/v1"
|
||||
)
|
||||
L34D_MANIFEST_NAME: Final = "manifest.json"
|
||||
L34D_REPORT_NAME: Final = "cumulative-postprocessing-report.json"
|
||||
L34D_CASES_NAME: Final = "cumulative-postprocessing-cases.jsonl"
|
||||
|
||||
_RESULT_ID = re.compile(
|
||||
r"^l34d-cumulative-postprocessing-candidate-[a-f0-9]{64}$"
|
||||
)
|
||||
_COUNT_METRICS: Final = (
|
||||
"prediction_count",
|
||||
"true_positive",
|
||||
"false_positive",
|
||||
"false_negative",
|
||||
"class_mismatch",
|
||||
"duplicate_false_positive",
|
||||
"unmatched_false_positive",
|
||||
"unmatched_false_negative",
|
||||
"error_case_count",
|
||||
)
|
||||
_DELTA_METRICS: Final = (
|
||||
*_COUNT_METRICS[:-1],
|
||||
"precision_iou50",
|
||||
"recall_iou50",
|
||||
"f1_iou50",
|
||||
"error_case_count",
|
||||
)
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L34DCumulativeCandidateError(RuntimeError):
|
||||
"""An L3.4D input, composition or immutable output is invalid."""
|
||||
|
||||
|
||||
def compose_l34d_prediction_row(
|
||||
provenance_row: dict[str, Any],
|
||||
*,
|
||||
consolidations: tuple[dict[str, Any], ...],
|
||||
stitches: tuple[dict[str, Any], ...],
|
||||
) -> tuple[dict[str, Any], tuple[dict[str, Any], ...]]:
|
||||
"""Compose source-indexed B/C operations with a fail-closed conflict gate."""
|
||||
|
||||
raw_predictions = _list(
|
||||
provenance_row.get("predictions"),
|
||||
"L3.4D provenance predictions",
|
||||
)
|
||||
predictions = [
|
||||
_prediction(value, index)
|
||||
for index, value in enumerate(raw_predictions, start=1)
|
||||
]
|
||||
normalized_operations = [
|
||||
_validated_operation(
|
||||
raw,
|
||||
predictions=predictions,
|
||||
provenance_predictions=raw_predictions,
|
||||
operation_type="nested-box-consolidation",
|
||||
)
|
||||
for raw in consolidations
|
||||
] + [
|
||||
_validated_operation(
|
||||
raw,
|
||||
predictions=predictions,
|
||||
provenance_predictions=raw_predictions,
|
||||
operation_type="temporal-tile-seam-stitch",
|
||||
)
|
||||
for raw in stitches
|
||||
]
|
||||
|
||||
consumed: set[int] = set()
|
||||
operation_by_first: dict[int, dict[str, Any]] = {}
|
||||
for operation in normalized_operations:
|
||||
source_indices = operation["source_prediction_indices"]
|
||||
overlap = consumed.intersection(source_indices)
|
||||
if overlap:
|
||||
raise L34DCumulativeCandidateError(
|
||||
"L3.4B/L3.4C operation sets overlap"
|
||||
)
|
||||
consumed.update(source_indices)
|
||||
operation_by_first[min(source_indices)] = operation
|
||||
|
||||
output: list[dict[str, Any]] = []
|
||||
for index, raw_prediction in enumerate(raw_predictions, start=1):
|
||||
if index in consumed and index not in operation_by_first:
|
||||
continue
|
||||
operation = operation_by_first.get(index)
|
||||
if operation is None:
|
||||
prediction = copy.deepcopy(_object(raw_prediction, "prediction"))
|
||||
prediction["source_prediction_indices"] = [index]
|
||||
prediction["source_rectification_tiles"] = [
|
||||
_text(
|
||||
prediction.get("rectification_tile"),
|
||||
"prediction rectification tile",
|
||||
)
|
||||
]
|
||||
prediction["operation_types"] = []
|
||||
output.append(prediction)
|
||||
continue
|
||||
output.append(
|
||||
{
|
||||
"label": operation["category"],
|
||||
"score": operation["merged_score"],
|
||||
"bbox_xyxy": copy.deepcopy(operation["merged_box_xyxy"]),
|
||||
"source_prediction_indices": copy.deepcopy(
|
||||
operation["source_prediction_indices"]
|
||||
),
|
||||
"source_rectification_tiles": copy.deepcopy(
|
||||
operation["source_tiles"]
|
||||
),
|
||||
"operation_types": [operation["operation_type"]],
|
||||
**(
|
||||
{
|
||||
"temporal_run_id": operation["temporal_run_id"],
|
||||
"temporal_run_length": operation["temporal_run_length"],
|
||||
}
|
||||
if operation["operation_type"]
|
||||
== "temporal-tile-seam-stitch"
|
||||
else {}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
projected = copy.deepcopy(provenance_row)
|
||||
projected["predictions"] = output
|
||||
return projected, tuple(normalized_operations)
|
||||
|
||||
|
||||
def evaluate_l34d_cumulative_candidate(
|
||||
*,
|
||||
provenance_rows: tuple[dict[str, Any], ...],
|
||||
annotation_frames: tuple[dict[str, Any], ...],
|
||||
before_cases: tuple[dict[str, Any], ...],
|
||||
l34b_cases: tuple[dict[str, Any], ...],
|
||||
l34c_cases: tuple[dict[str, Any], ...],
|
||||
l34b_metrics: dict[str, Any],
|
||||
l34c_metrics: dict[str, Any],
|
||||
) -> tuple[tuple[dict[str, Any], ...], dict[str, Any]]:
|
||||
"""Project and compare the cumulative candidate against assisted review."""
|
||||
|
||||
annotations_by_sequence = _sequence_map(annotation_frames, "annotation")
|
||||
before_by_sequence = _sequence_map(before_cases, "before")
|
||||
l34b_by_sequence = _sequence_map(l34b_cases, "L3.4B")
|
||||
l34c_by_sequence = _sequence_map(l34c_cases, "L3.4C")
|
||||
if (
|
||||
len(provenance_rows) != 32
|
||||
or len(annotations_by_sequence) != 32
|
||||
or len(before_by_sequence) != 32
|
||||
or len(l34b_by_sequence) != 32
|
||||
or len(l34c_by_sequence) != 32
|
||||
):
|
||||
raise L34DCumulativeCandidateError(
|
||||
"L3.4D requires 32 source-bound cases"
|
||||
)
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
after_audits: list[dict[str, Any]] = []
|
||||
for row in provenance_rows:
|
||||
sequence = _integer(
|
||||
row.get("truth_island_sequence"),
|
||||
"prediction sequence",
|
||||
)
|
||||
b_case = l34b_by_sequence[sequence]
|
||||
c_case = l34c_by_sequence[sequence]
|
||||
_validate_case_binding(row, b_case, "L3.4B")
|
||||
_validate_case_binding(row, c_case, "L3.4C")
|
||||
projected, operations = compose_l34d_prediction_row(
|
||||
row,
|
||||
consolidations=tuple(
|
||||
_object(value, "L3.4B consolidation")
|
||||
for value in _list(
|
||||
b_case.get("consolidations"),
|
||||
"L3.4B consolidations",
|
||||
)
|
||||
),
|
||||
stitches=tuple(
|
||||
_object(value, "L3.4C stitch")
|
||||
for value in _list(c_case.get("stitches"), "L3.4C stitches")
|
||||
),
|
||||
)
|
||||
after = _audit_case(
|
||||
prediction_row=projected,
|
||||
annotation_frame=annotations_by_sequence[sequence],
|
||||
)
|
||||
for prediction, source in zip(
|
||||
after["predictions"],
|
||||
projected["predictions"],
|
||||
strict=True,
|
||||
):
|
||||
prediction["source_prediction_indices"] = copy.deepcopy(
|
||||
source["source_prediction_indices"]
|
||||
)
|
||||
prediction["source_rectification_tiles"] = copy.deepcopy(
|
||||
source["source_rectification_tiles"]
|
||||
)
|
||||
prediction["operation_types"] = copy.deepcopy(
|
||||
source["operation_types"]
|
||||
)
|
||||
|
||||
provenance_by_index = {
|
||||
_integer(
|
||||
_object(value, "provenance prediction").get(
|
||||
"source_prediction_index"
|
||||
),
|
||||
"source prediction index",
|
||||
): _object(value, "provenance prediction")
|
||||
for value in _list(row.get("predictions"), "provenance predictions")
|
||||
}
|
||||
before = copy.deepcopy(before_by_sequence[sequence])
|
||||
for prediction in _list(
|
||||
before.get("predictions"),
|
||||
"before predictions",
|
||||
):
|
||||
current = _object(prediction, "before prediction")
|
||||
provenance = provenance_by_index.get(
|
||||
_integer(current.get("prediction_index"), "prediction index")
|
||||
)
|
||||
if provenance is None:
|
||||
raise L34DCumulativeCandidateError(
|
||||
"before prediction lost provenance"
|
||||
)
|
||||
current["rectification_tile"] = provenance["rectification_tile"]
|
||||
current["raw_label"] = provenance["raw_label"]
|
||||
current["class_id"] = provenance["class_id"]
|
||||
current["raw_center_xy"] = copy.deepcopy(provenance["raw_center_xy"])
|
||||
|
||||
_validate_case_binding(row, after, "L3.4D after")
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": L34D_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": after["image_id"],
|
||||
"frame_index": after["frame_index"],
|
||||
"group_id": after["group_id"],
|
||||
"session_seconds": after["session_seconds"],
|
||||
"source_image_sha256": after["source_image_sha256"],
|
||||
"camera": copy.deepcopy(after["camera"]),
|
||||
"before_predictions": copy.deepcopy(before["predictions"]),
|
||||
"after_predictions": copy.deepcopy(after["predictions"]),
|
||||
"annotations": copy.deepcopy(after["annotations"]),
|
||||
"operations": list(operations),
|
||||
"before_summary": copy.deepcopy(before["summary"]),
|
||||
"after_summary": copy.deepcopy(after["summary"]),
|
||||
}
|
||||
)
|
||||
after_audits.append(after)
|
||||
|
||||
before_metrics = _aggregate(tuple(before_cases))
|
||||
after_metrics = _aggregate(tuple(after_audits))
|
||||
delta = {
|
||||
key: after_metrics[key] - before_metrics[key]
|
||||
for key in _DELTA_METRICS
|
||||
}
|
||||
operation_types = [
|
||||
operation["operation_type"]
|
||||
for case in cases
|
||||
for operation in case["operations"]
|
||||
]
|
||||
nested_count = operation_types.count("nested-box-consolidation")
|
||||
stitch_count = operation_types.count("temporal-tile-seam-stitch")
|
||||
expected_count_delta = {
|
||||
key: int(l34b_metrics["delta"][key])
|
||||
+ int(l34c_metrics["delta"][key])
|
||||
for key in _COUNT_METRICS
|
||||
}
|
||||
count_effects_additive = all(
|
||||
int(delta[key]) == expected_count_delta[key]
|
||||
for key in _COUNT_METRICS
|
||||
)
|
||||
regression_free = (
|
||||
nested_count > 0
|
||||
and stitch_count > 0
|
||||
and count_effects_additive
|
||||
and delta["true_positive"] >= 0
|
||||
and delta["false_positive"] < 0
|
||||
and delta["false_negative"] <= 0
|
||||
and delta["class_mismatch"] <= 0
|
||||
)
|
||||
return tuple(cases), {
|
||||
"before": before_metrics,
|
||||
"after": after_metrics,
|
||||
"delta": delta,
|
||||
"nested_consolidation_count": nested_count,
|
||||
"temporal_stitch_count": stitch_count,
|
||||
"cumulative_operation_count": len(operation_types),
|
||||
"affected_case_count": sum(bool(case["operations"]) for case in cases),
|
||||
"operation_conflict_count": 0,
|
||||
"count_effects_additive": count_effects_additive,
|
||||
"assisted_regression_free": regression_free,
|
||||
}
|
||||
|
||||
|
||||
def build_l34d_cumulative_postprocessing_candidate(
|
||||
*,
|
||||
l34a_audit_root: Path,
|
||||
l34b_shadow_root: Path,
|
||||
l34c_shadow_root: Path,
|
||||
annotation_session_path: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Build and publish one immutable cumulative candidate freeze."""
|
||||
|
||||
try:
|
||||
l34a = read_l34a_assisted_yolox_error_audit(l34a_audit_root)
|
||||
l34b = read_l34b_nested_box_consolidation_shadow(l34b_shadow_root)
|
||||
l34c = read_l34c_tile_seam_stitch_shadow(l34c_shadow_root)
|
||||
except (
|
||||
L34AAssistedYoloxErrorAuditError,
|
||||
L34BNestedBoxConsolidationError,
|
||||
L34CTileSeamStitchError,
|
||||
) as reason:
|
||||
raise L34DCumulativeCandidateError(
|
||||
"L3.4A/B/C input is invalid"
|
||||
) from reason
|
||||
|
||||
session_path = annotation_session_path.expanduser().resolve(strict=True)
|
||||
if not session_path.is_file() or session_path.is_symlink():
|
||||
raise L34DCumulativeCandidateError(
|
||||
"annotation session is unavailable"
|
||||
)
|
||||
session = _read_json(session_path)
|
||||
l34b_identity = _object(l34b["manifest"].get("identity"), "L3.4B identity")
|
||||
l34c_identity = _object(l34c["manifest"].get("identity"), "L3.4C identity")
|
||||
l34a_id = l34a["result_id"]
|
||||
freeze_id = _object(
|
||||
l34c_identity.get("l34_freeze"),
|
||||
"L3.4C freeze",
|
||||
).get("result_id")
|
||||
try:
|
||||
_validate_session(session, freeze_result_id=_text(freeze_id, "freeze id"))
|
||||
except L34AAssistedYoloxErrorAuditError as reason:
|
||||
raise L34DCumulativeCandidateError(
|
||||
"annotation session is invalid"
|
||||
) from reason
|
||||
if (
|
||||
_object(l34b_identity.get("l34_freeze"), "L3.4B freeze")
|
||||
!= _object(l34c_identity.get("l34_freeze"), "L3.4C freeze")
|
||||
or _object(l34b_identity.get("l34a_audit"), "L3.4B audit").get(
|
||||
"result_id"
|
||||
)
|
||||
!= l34a_id
|
||||
or _object(l34c_identity.get("l34a_audit"), "L3.4C audit").get(
|
||||
"result_id"
|
||||
)
|
||||
!= l34a_id
|
||||
or _object(
|
||||
l34b_identity.get("assisted_annotation"),
|
||||
"L3.4B annotation",
|
||||
).get("session_sha256")
|
||||
!= _sha256(session_path)
|
||||
or _object(
|
||||
l34c_identity.get("assisted_annotation"),
|
||||
"L3.4C annotation",
|
||||
).get("session_sha256")
|
||||
!= _sha256(session_path)
|
||||
or l34b["report"]["decision"].get("shadow_policy_accepted") is not True
|
||||
or l34c["report"]["decision"].get("shadow_policy_accepted") is not True
|
||||
):
|
||||
raise L34DCumulativeCandidateError("L3.4D lineage differs")
|
||||
|
||||
cases, metrics = evaluate_l34d_cumulative_candidate(
|
||||
provenance_rows=l34c["provenance"],
|
||||
annotation_frames=tuple(session["frames"]),
|
||||
before_cases=l34a["cases"],
|
||||
l34b_cases=l34b["cases"],
|
||||
l34c_cases=l34c["cases"],
|
||||
l34b_metrics=l34b["report"]["metrics"],
|
||||
l34c_metrics=l34c["report"]["metrics"],
|
||||
)
|
||||
if not metrics["assisted_regression_free"]:
|
||||
raise L34DCumulativeCandidateError(
|
||||
"cumulative candidate failed the assisted regression gate"
|
||||
)
|
||||
affected_sequences = [
|
||||
case["truth_island_sequence"] for case in cases if case["operations"]
|
||||
]
|
||||
case_order = affected_sequences + [
|
||||
case["truth_island_sequence"]
|
||||
for case in sorted(
|
||||
(item for item in cases if not item["operations"]),
|
||||
key=lambda item: (
|
||||
-int(item["after_summary"]["severity_score"]),
|
||||
int(item["truth_island_sequence"]),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "deterministic",
|
||||
"pipeline_id": "ravnoves00-right-yolox-cumulative-postprocessing/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": l34b["result_id"],
|
||||
"version": "accepted L3.4B nested-box shadow",
|
||||
"role": "source-indexed nested-box operations",
|
||||
"identity_sha256": _sha256(
|
||||
l34b["result_root"] / L34B_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": l34c["result_id"],
|
||||
"version": "accepted L3.4C temporal seam shadow",
|
||||
"role": "tile provenance and source-indexed seam operations",
|
||||
"identity_sha256": _sha256(
|
||||
l34c["result_root"] / L34C_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": l34a_id,
|
||||
"version": "L3.4A assisted diagnostic; not truth",
|
||||
"role": "after-freeze engineering comparison",
|
||||
"identity_sha256": _sha256(
|
||||
l34a["result_root"] / L34A_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "source-indexed-order-independent-composition",
|
||||
"version": "v1-fail-closed-on-conflict",
|
||||
"role": "compose accepted B/C operations and freeze one candidate",
|
||||
"identity_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
},
|
||||
],
|
||||
}
|
||||
report_basis = {
|
||||
"schema_version": L34D_REPORT_SCHEMA,
|
||||
"status": "completed-cumulative-postprocessing-candidate-freeze-not-truth",
|
||||
"profile": {
|
||||
"profile_id": "l34d-source-indexed-cumulative-candidate/v1",
|
||||
"operation_order": "simultaneous-original-source-indices",
|
||||
"conflict_policy": "reject-any-overlapping-source-index",
|
||||
"geometry_policy": "accepted-l34b-and-l34c-union-boxes-only",
|
||||
"score_policy": "accepted-maximum-source-score",
|
||||
"scope": "recorded-right-camera-frozen-prediction-candidate-only",
|
||||
},
|
||||
"metrics": metrics,
|
||||
"case_order": case_order,
|
||||
"decision": {
|
||||
"cumulative_shadow_accepted": True,
|
||||
"candidate_frozen": True,
|
||||
"candidate_accepted": False,
|
||||
"prediction_provenance_preserved": True,
|
||||
"operation_sets_disjoint": True,
|
||||
"global_nms_unchanged": True,
|
||||
"independent_truth_available": False,
|
||||
"l35_blind_gate_open": False,
|
||||
"next_action": (
|
||||
"collect prediction-hidden independent labels against this exact "
|
||||
"frozen candidate, then evaluate once without retuning"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
"the comparison reuses candidate-seeded assisted review and is not independent truth",
|
||||
"the frozen candidate changes only five of 32 RIGHT-camera cases on one route",
|
||||
"the composition is valid only while L3.4B and L3.4C source-index sets remain disjoint",
|
||||
"no model weights, global NMS or detector inference are changed",
|
||||
(
|
||||
"no live transport, hardware, left camera, LiDAR range, "
|
||||
"navigation or safety claim is made"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": L34D_RESULT_SCHEMA,
|
||||
"l34_freeze": copy.deepcopy(l34c_identity["l34_freeze"]),
|
||||
"l34a_audit": {
|
||||
"result_id": l34a_id,
|
||||
"manifest_sha256": _sha256(
|
||||
l34a["result_root"] / L34A_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
"l34b_shadow": {
|
||||
"result_id": l34b["result_id"],
|
||||
"manifest_sha256": _sha256(
|
||||
l34b["result_root"] / L34B_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
"l34c_shadow": {
|
||||
"result_id": l34c["result_id"],
|
||||
"manifest_sha256": _sha256(
|
||||
l34c["result_root"] / L34C_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
"assisted_annotation": {
|
||||
"session_id": session["session_id"],
|
||||
"session_sha256": _sha256(session_path),
|
||||
"independent_truth_eligible": False,
|
||||
},
|
||||
"method": method,
|
||||
"report_sha256": hashlib.sha256(
|
||||
_canonical_json(report_basis)
|
||||
).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34d-cumulative-postprocessing-candidate-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l34d_cumulative_postprocessing_candidate(destination)
|
||||
|
||||
created_at_utc = _utc_now()
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"method": method,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / L34D_REPORT_NAME, report)
|
||||
_write_jsonl(staging / L34D_CASES_NAME, cases)
|
||||
manifest = {
|
||||
"schema_version": L34D_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "frozen-assisted-cumulative-candidate-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / L34D_REPORT_NAME, "cumulative-report"),
|
||||
_artifact(staging / L34D_CASES_NAME, "cumulative-cases"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
_write_json(staging / L34D_MANIFEST_NAME, manifest)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l34d_cumulative_postprocessing_candidate(destination)
|
||||
|
||||
|
||||
def read_l34d_cumulative_postprocessing_candidate(
|
||||
root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Read and fully revalidate one immutable L3.4D candidate."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / L34D_MANIFEST_NAME)
|
||||
identity = _object(manifest.get("identity"), "L3.4D identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
if (
|
||||
manifest.get("schema_version") != L34D_RESULT_SCHEMA
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
!= identity_sha256
|
||||
or manifest.get("result_id")
|
||||
!= f"l34d-cumulative-postprocessing-candidate-{identity_sha256}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("acceptance_state")
|
||||
!= "frozen-assisted-cumulative-candidate-not-truth"
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L34DCumulativeCandidateError("L3.4D identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise L34DCumulativeCandidateError("L3.4D artifacts are invalid")
|
||||
artifact_by_role = {
|
||||
_text(item.get("role"), "artifact role"): _object(item, "artifact")
|
||||
for item in artifacts
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
report = _read_json(
|
||||
_validated_artifact(
|
||||
resolved,
|
||||
artifact_by_role.get("cumulative-report"),
|
||||
)
|
||||
)
|
||||
cases = tuple(
|
||||
_read_jsonl(
|
||||
_validated_artifact(
|
||||
resolved,
|
||||
artifact_by_role.get("cumulative-cases"),
|
||||
)
|
||||
)
|
||||
)
|
||||
if (
|
||||
report.get("schema_version") != L34D_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("identity_sha256") != identity_sha256
|
||||
or report.get("status")
|
||||
!= "completed-cumulative-postprocessing-candidate-freeze-not-truth"
|
||||
or report.get("ground_truth") is not False
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != L34D_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest()
|
||||
!= identity.get("cases_sha256")
|
||||
):
|
||||
raise L34DCumulativeCandidateError("L3.4D result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _validated_operation(
|
||||
raw: dict[str, Any],
|
||||
*,
|
||||
predictions: list[dict[str, Any]],
|
||||
provenance_predictions: list[Any],
|
||||
operation_type: str,
|
||||
) -> dict[str, Any]:
|
||||
operation = copy.deepcopy(_object(raw, "L3.4D operation"))
|
||||
raw_indices = _list(
|
||||
operation.get("source_prediction_indices"),
|
||||
"operation source indices",
|
||||
)
|
||||
source_indices = sorted(
|
||||
_integer(value, "operation source index") for value in raw_indices
|
||||
)
|
||||
if (
|
||||
len(source_indices) < 2
|
||||
or len(set(source_indices)) != len(source_indices)
|
||||
or source_indices[0] < 1
|
||||
or source_indices[-1] > len(predictions)
|
||||
):
|
||||
raise L34DCumulativeCandidateError(
|
||||
"operation source indices are invalid"
|
||||
)
|
||||
members = [predictions[index - 1] for index in source_indices]
|
||||
category = _text(operation.get("category"), "operation category")
|
||||
if any(member["label"] != category for member in members):
|
||||
raise L34DCumulativeCandidateError("operation category changed")
|
||||
source_scores = [
|
||||
_finite(value, "operation source score")
|
||||
for value in _list(operation.get("source_scores"), "operation scores")
|
||||
]
|
||||
source_boxes = [
|
||||
[
|
||||
_finite(coordinate, "operation source box")
|
||||
for coordinate in _list(value, "operation source box")
|
||||
]
|
||||
for value in _list(operation.get("source_boxes_xyxy"), "operation boxes")
|
||||
]
|
||||
if (
|
||||
source_scores != [member["score"] for member in members]
|
||||
or source_boxes != [member["bbox_xyxy"] for member in members]
|
||||
):
|
||||
raise L34DCumulativeCandidateError("operation source payload changed")
|
||||
merged_box = source_boxes[0]
|
||||
for source_box in source_boxes[1:]:
|
||||
merged_box = _union_box(merged_box, source_box)
|
||||
declared_merged_box = [
|
||||
_finite(value, "operation merged box")
|
||||
for value in _list(operation.get("merged_box_xyxy"), "merged box")
|
||||
]
|
||||
merged_score = _finite(operation.get("merged_score"), "merged score")
|
||||
if declared_merged_box != merged_box or merged_score != max(source_scores):
|
||||
raise L34DCumulativeCandidateError("operation projection changed")
|
||||
source_tiles = [
|
||||
_text(
|
||||
_object(provenance_predictions[index - 1], "provenance prediction").get(
|
||||
"rectification_tile"
|
||||
),
|
||||
"source tile",
|
||||
)
|
||||
for index in source_indices
|
||||
]
|
||||
if operation_type == "temporal-tile-seam-stitch":
|
||||
if source_tiles != _list(operation.get("source_tiles"), "stitch tiles"):
|
||||
raise L34DCumulativeCandidateError("stitch tile provenance changed")
|
||||
operation["temporal_run_id"] = _text(
|
||||
operation.get("temporal_run_id"),
|
||||
"temporal run id",
|
||||
)
|
||||
operation["temporal_run_length"] = _integer(
|
||||
operation.get("temporal_run_length"),
|
||||
"temporal run length",
|
||||
)
|
||||
operation["source_prediction_indices"] = source_indices
|
||||
operation["source_tiles"] = source_tiles
|
||||
operation["source_scores"] = source_scores
|
||||
operation["source_boxes_xyxy"] = source_boxes
|
||||
operation["merged_box_xyxy"] = declared_merged_box
|
||||
operation["merged_score"] = merged_score
|
||||
operation["operation_type"] = operation_type
|
||||
operation.pop("output_prediction_index", None)
|
||||
return operation
|
||||
|
||||
|
||||
def _sequence_map(
|
||||
values: tuple[dict[str, Any], ...],
|
||||
label: str,
|
||||
) -> dict[int, dict[str, Any]]:
|
||||
result = {
|
||||
_integer(value.get("truth_island_sequence"), f"{label} sequence"): value
|
||||
for value in values
|
||||
}
|
||||
if len(result) != len(values):
|
||||
raise L34DCumulativeCandidateError(f"{label} sequences are not unique")
|
||||
return result
|
||||
|
||||
|
||||
def _validate_case_binding(
|
||||
source: dict[str, Any],
|
||||
candidate: dict[str, Any],
|
||||
label: str,
|
||||
) -> None:
|
||||
if any(
|
||||
source.get(key) != candidate.get(key)
|
||||
for key in (
|
||||
"truth_island_sequence",
|
||||
"image_id",
|
||||
"frame_index",
|
||||
"group_id",
|
||||
"source_image_sha256",
|
||||
)
|
||||
):
|
||||
raise L34DCumulativeCandidateError(f"{label} case binding changed")
|
||||
@@ -0,0 +1,827 @@
|
||||
"""Build the L3.4E diagnostic comparison against a manual self-review.
|
||||
|
||||
L3.4E deliberately does not promote the self-review to ground truth. The
|
||||
reviewer has seen the candidate identity and the boxes are coarse enough that
|
||||
strict IoU50 alone would confuse annotation geometry with detector quality.
|
||||
The artifact therefore preserves the strict metric for reproducibility and
|
||||
adds a second, explicitly diagnostic association pass for visual triage.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import hashlib
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from .l34a_assisted_yolox_error_audit import (
|
||||
L34AAssistedYoloxErrorAuditError,
|
||||
_aggregate,
|
||||
_audit_case,
|
||||
_iou,
|
||||
_overlap_over_smaller,
|
||||
_per_class,
|
||||
)
|
||||
from .l34c_tile_seam_stitch_shadow import (
|
||||
_artifact,
|
||||
_canonical_json,
|
||||
_integer,
|
||||
_list,
|
||||
_object,
|
||||
_read_json,
|
||||
_read_jsonl,
|
||||
_sha256,
|
||||
_text,
|
||||
_utc_now,
|
||||
_validated_artifact,
|
||||
_write_json,
|
||||
_write_jsonl,
|
||||
)
|
||||
from .l34d_cumulative_postprocessing_candidate import (
|
||||
L34D_MANIFEST_NAME,
|
||||
L34DCumulativeCandidateError,
|
||||
read_l34d_cumulative_postprocessing_candidate,
|
||||
)
|
||||
|
||||
L34E_RESULT_SCHEMA: Final = "missioncore.l34e-self-review-diagnostic/v1"
|
||||
L34E_REPORT_SCHEMA: Final = (
|
||||
"missioncore.l34e-self-review-diagnostic-report/v1"
|
||||
)
|
||||
L34E_CASE_SCHEMA: Final = "missioncore.l34e-self-review-diagnostic-case/v1"
|
||||
L34E_MANIFEST_NAME: Final = "manifest.json"
|
||||
L34E_REPORT_NAME: Final = "self-review-diagnostic-report.json"
|
||||
L34E_CASES_NAME: Final = "self-review-diagnostic-cases.jsonl"
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34e-self-review-diagnostic-[a-f0-9]{64}$")
|
||||
_SESSION_ID = re.compile(r"^l34-annotation-session-[a-f0-9]{64}$")
|
||||
_ANNOTATION_SCHEMA: Final = "missioncore.l34-annotation-session/v3"
|
||||
_STRICT_IOU: Final = 0.5
|
||||
_LOOSE_IOU: Final = 0.1
|
||||
_LOOSE_OVERLAP: Final = 0.3
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L34ESelfReviewDiagnosticError(RuntimeError):
|
||||
"""An L3.4E source, result identity or immutable artifact is invalid."""
|
||||
|
||||
|
||||
def evaluate_l34e_self_review_diagnostic(
|
||||
*,
|
||||
l34d_cases: tuple[dict[str, Any], ...],
|
||||
annotation_frames: tuple[dict[str, Any], ...],
|
||||
) -> tuple[tuple[dict[str, Any], ...], dict[str, Any]]:
|
||||
"""Evaluate strict IoU50 and diagnostic associations for 32 cases."""
|
||||
|
||||
if len(l34d_cases) != 32 or len(annotation_frames) != 32:
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"L3.4E requires exactly 32 candidate and review frames"
|
||||
)
|
||||
candidate_by_sequence = _sequence_map(l34d_cases, "candidate")
|
||||
review_by_sequence = _sequence_map(annotation_frames, "review")
|
||||
if set(candidate_by_sequence) != set(range(1, 33)) or set(
|
||||
review_by_sequence
|
||||
) != set(range(1, 33)):
|
||||
raise L34ESelfReviewDiagnosticError("L3.4E frame coverage differs")
|
||||
|
||||
cases: list[dict[str, Any]] = []
|
||||
strict_cases: list[dict[str, Any]] = []
|
||||
for sequence in range(1, 33):
|
||||
source = candidate_by_sequence[sequence]
|
||||
review = review_by_sequence[sequence]
|
||||
prediction_row = {
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": source.get("image_id"),
|
||||
"frame_index": source.get("frame_index"),
|
||||
"group_id": source.get("group_id"),
|
||||
"session_seconds": source.get("session_seconds"),
|
||||
"source_image_sha256": source.get("source_image_sha256"),
|
||||
"predictions": [
|
||||
{
|
||||
"label": item.get("category"),
|
||||
"score": item.get("score"),
|
||||
"bbox_xyxy": copy.deepcopy(item.get("box_xyxy")),
|
||||
}
|
||||
for item in (
|
||||
_object(value, "L3.4D after prediction")
|
||||
for value in _list(
|
||||
source.get("after_predictions"),
|
||||
"L3.4D after predictions",
|
||||
)
|
||||
)
|
||||
],
|
||||
}
|
||||
try:
|
||||
strict = _audit_case(
|
||||
prediction_row=prediction_row,
|
||||
annotation_frame=review,
|
||||
)
|
||||
except L34AAssistedYoloxErrorAuditError as reason:
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"L3.4E source binding or box contract is invalid"
|
||||
) from reason
|
||||
|
||||
source_predictions = _list(
|
||||
source.get("after_predictions"),
|
||||
"L3.4D after predictions",
|
||||
)
|
||||
for prediction, source_prediction in zip(
|
||||
strict["predictions"],
|
||||
source_predictions,
|
||||
strict=True,
|
||||
):
|
||||
provenance = _object(source_prediction, "L3.4D prediction")
|
||||
prediction["source_prediction_indices"] = copy.deepcopy(
|
||||
provenance.get("source_prediction_indices", [])
|
||||
)
|
||||
prediction["source_rectification_tiles"] = copy.deepcopy(
|
||||
provenance.get("source_rectification_tiles", [])
|
||||
)
|
||||
prediction["operation_types"] = copy.deepcopy(
|
||||
provenance.get("operation_types", [])
|
||||
)
|
||||
|
||||
associations = _diagnostic_associations(
|
||||
strict["predictions"],
|
||||
strict["annotations"],
|
||||
)
|
||||
summary = _diagnostic_case_summary(
|
||||
strict["predictions"],
|
||||
strict["annotations"],
|
||||
associations,
|
||||
)
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": L34E_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": strict["image_id"],
|
||||
"frame_index": strict["frame_index"],
|
||||
"group_id": strict["group_id"],
|
||||
"session_seconds": strict["session_seconds"],
|
||||
"source_image_sha256": strict["source_image_sha256"],
|
||||
"camera": copy.deepcopy(strict["camera"]),
|
||||
"predictions": copy.deepcopy(strict["predictions"]),
|
||||
"references": copy.deepcopy(strict["annotations"]),
|
||||
"associations": associations,
|
||||
"strict_summary": copy.deepcopy(strict["summary"]),
|
||||
"diagnostic_summary": summary,
|
||||
}
|
||||
)
|
||||
strict_cases.append(strict)
|
||||
|
||||
strict_metrics = _aggregate(tuple(strict_cases))
|
||||
strict_metrics["per_class"] = _per_class(tuple(strict_cases))
|
||||
diagnostic = _aggregate_diagnostic(tuple(cases))
|
||||
temporal = _temporal_count_diagnostics(tuple(cases))
|
||||
return tuple(cases), {
|
||||
"strict_iou50": strict_metrics,
|
||||
"diagnostic_association": diagnostic,
|
||||
"frame_count_disagreement": {
|
||||
"candidate_surplus_lower_bound": sum(
|
||||
max(
|
||||
0,
|
||||
case["diagnostic_summary"]["prediction_count"]
|
||||
- case["diagnostic_summary"]["reference_count"],
|
||||
)
|
||||
for case in cases
|
||||
),
|
||||
"reference_surplus_lower_bound": sum(
|
||||
max(
|
||||
0,
|
||||
case["diagnostic_summary"]["reference_count"]
|
||||
- case["diagnostic_summary"]["prediction_count"],
|
||||
)
|
||||
for case in cases
|
||||
),
|
||||
"equal_count_frame_count": sum(
|
||||
case["diagnostic_summary"]["prediction_count"]
|
||||
== case["diagnostic_summary"]["reference_count"]
|
||||
for case in cases
|
||||
),
|
||||
},
|
||||
"temporal_groups": temporal,
|
||||
"reference_quality": "not-metric-grade-self-review",
|
||||
}
|
||||
|
||||
|
||||
def build_l34e_self_review_diagnostic(
|
||||
*,
|
||||
l34d_candidate_root: Path,
|
||||
annotation_session_path: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Build and publish one immutable L3.4E diagnostic result."""
|
||||
|
||||
try:
|
||||
candidate = read_l34d_cumulative_postprocessing_candidate(
|
||||
l34d_candidate_root
|
||||
)
|
||||
except L34DCumulativeCandidateError as reason:
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"L3.4D candidate is invalid"
|
||||
) from reason
|
||||
session_path = annotation_session_path.expanduser().resolve(strict=True)
|
||||
if not session_path.is_file() or session_path.is_symlink():
|
||||
raise L34ESelfReviewDiagnosticError("self-review session is unavailable")
|
||||
session = _read_json(session_path)
|
||||
_validate_self_review_session(session, candidate["result_id"])
|
||||
|
||||
cases, metrics = evaluate_l34e_self_review_diagnostic(
|
||||
l34d_cases=candidate["cases"],
|
||||
annotation_frames=tuple(session["frames"]),
|
||||
)
|
||||
case_order = [
|
||||
case["truth_island_sequence"]
|
||||
for case in sorted(
|
||||
cases,
|
||||
key=lambda item: (
|
||||
-int(item["diagnostic_summary"]["severity_score"]),
|
||||
int(item["truth_island_sequence"]),
|
||||
),
|
||||
)
|
||||
]
|
||||
method = {
|
||||
"schema_version": "missioncore.laboratory-method/v1",
|
||||
"completeness": "complete",
|
||||
"execution_class": "deterministic",
|
||||
"pipeline_id": "ravnoves00-right-yolox-self-review-diagnostic/v1",
|
||||
"components": [
|
||||
{
|
||||
"kind": "source",
|
||||
"name": candidate["result_id"],
|
||||
"version": "L3.4D frozen cumulative candidate",
|
||||
"role": "candidate boxes and immutable source binding",
|
||||
"identity_sha256": _sha256(
|
||||
candidate["result_root"] / L34D_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
{
|
||||
"kind": "source",
|
||||
"name": session["session_id"],
|
||||
"version": "prediction-hidden manual self-review; not truth",
|
||||
"role": "coarse human reference boxes for diagnostic triage",
|
||||
"identity_sha256": _sha256(session_path),
|
||||
},
|
||||
{
|
||||
"kind": "algorithm",
|
||||
"name": "strict-plus-diagnostic-spatial-association",
|
||||
"version": "v1-iou50-then-iou10-or-overlap30",
|
||||
"role": "separate object association from localization disagreement",
|
||||
"identity_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
},
|
||||
],
|
||||
}
|
||||
report_basis = {
|
||||
"schema_version": L34E_REPORT_SCHEMA,
|
||||
"status": "completed-self-review-diagnostic-not-truth",
|
||||
"profile": {
|
||||
"profile_id": "l34e-self-review-diagnostic/v1",
|
||||
"strict_matcher": "greedy-maximum-iou-0.50",
|
||||
"diagnostic_matcher": (
|
||||
"strict-first-then-greedy-iou-0.10-or-overlap-over-smaller-0.30"
|
||||
),
|
||||
"class_policy": "spatial-association-first-then-class-verdict",
|
||||
"reference_policy": "manual-self-review-coarse-boxes-not-metric-grade",
|
||||
"scope": "recorded-right-camera-32-frozen-frames",
|
||||
},
|
||||
"metrics": metrics,
|
||||
"case_order": case_order,
|
||||
"decision": {
|
||||
"self_review_complete": True,
|
||||
"diagnostic_alignment_available": True,
|
||||
"metric_grade_reference_available": False,
|
||||
"independent_truth_available": False,
|
||||
"detector_retuning_authorized": False,
|
||||
"candidate_accepted": False,
|
||||
"l35_blind_gate_open": False,
|
||||
"next_action": (
|
||||
"refine and adjudicate the visual disagreement gallery, then obtain "
|
||||
"an independent reviewer before one-shot candidate acceptance"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
(
|
||||
"the reviewer had seen the candidate identity, so this result is "
|
||||
"diagnostic self-review and not independent truth"
|
||||
),
|
||||
(
|
||||
"manual boxes are coarse, especially for small distant vehicles; "
|
||||
"strict IoU50 is not detector accuracy"
|
||||
),
|
||||
(
|
||||
"the loose association pass is a visual-triage heuristic and must "
|
||||
"not be used as an acceptance metric"
|
||||
),
|
||||
"the sample contains only 32 recorded RIGHT-camera frames on one route",
|
||||
(
|
||||
"no live transport, hardware, left camera, LiDAR range, navigation "
|
||||
"or safety claim is made"
|
||||
),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": L34E_RESULT_SCHEMA,
|
||||
"l34d_candidate": {
|
||||
"result_id": candidate["result_id"],
|
||||
"manifest_sha256": _sha256(
|
||||
candidate["result_root"] / L34D_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
"self_review": {
|
||||
"session_id": session["session_id"],
|
||||
"session_sha256": _sha256(session_path),
|
||||
"revision": session["revision"],
|
||||
"independent_truth_eligible": False,
|
||||
"metric_grade_reference": False,
|
||||
},
|
||||
"method": method,
|
||||
"report_sha256": hashlib.sha256(
|
||||
_canonical_json(report_basis)
|
||||
).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34e-self-review-diagnostic-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l34e_self_review_diagnostic(destination)
|
||||
|
||||
created_at_utc = _utc_now()
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
"method": method,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / L34E_REPORT_NAME, report)
|
||||
_write_jsonl(staging / L34E_CASES_NAME, cases)
|
||||
manifest = {
|
||||
"schema_version": L34E_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "diagnostic-self-review-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": [
|
||||
_artifact(staging / L34E_REPORT_NAME, "diagnostic-report"),
|
||||
_artifact(staging / L34E_CASES_NAME, "diagnostic-cases"),
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
_write_json(staging / L34E_MANIFEST_NAME, manifest)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l34e_self_review_diagnostic(destination)
|
||||
|
||||
|
||||
def read_l34e_self_review_diagnostic(root: Path) -> dict[str, Any]:
|
||||
"""Read and fully revalidate one immutable L3.4E result."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / L34E_MANIFEST_NAME)
|
||||
identity = _object(manifest.get("identity"), "L3.4E identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
if (
|
||||
manifest.get("schema_version") != L34E_RESULT_SCHEMA
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
!= identity_sha256
|
||||
or manifest.get("result_id")
|
||||
!= f"l34e-self-review-diagnostic-{identity_sha256}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("acceptance_state") != "diagnostic-self-review-not-truth"
|
||||
or manifest.get("ground_truth") is not False
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L34ESelfReviewDiagnosticError("L3.4E identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise L34ESelfReviewDiagnosticError("L3.4E artifacts are invalid")
|
||||
artifact_by_role = {
|
||||
_text(item.get("role"), "artifact role"): _object(item, "artifact")
|
||||
for item in artifacts
|
||||
if isinstance(item, dict)
|
||||
}
|
||||
report = _read_json(
|
||||
_validated_artifact(
|
||||
resolved,
|
||||
artifact_by_role.get("diagnostic-report"),
|
||||
)
|
||||
)
|
||||
cases = tuple(
|
||||
_read_jsonl(
|
||||
_validated_artifact(
|
||||
resolved,
|
||||
artifact_by_role.get("diagnostic-cases"),
|
||||
)
|
||||
)
|
||||
)
|
||||
if (
|
||||
report.get("schema_version") != L34E_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("identity_sha256") != identity_sha256
|
||||
or report.get("status") != "completed-self-review-diagnostic-not-truth"
|
||||
or report.get("ground_truth") is not False
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != L34E_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest()
|
||||
!= identity.get("cases_sha256")
|
||||
):
|
||||
raise L34ESelfReviewDiagnosticError("L3.4E result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _validate_self_review_session(
|
||||
session: dict[str, Any],
|
||||
candidate_result_id: str,
|
||||
) -> None:
|
||||
assistance = _object(session.get("assistance"), "self-review assistance")
|
||||
blindness = _object(session.get("blindness"), "self-review blindness")
|
||||
frames = session.get("frames")
|
||||
if (
|
||||
session.get("schema_version") != _ANNOTATION_SCHEMA
|
||||
or not isinstance(session.get("session_id"), str)
|
||||
or _SESSION_ID.fullmatch(session["session_id"]) is None
|
||||
or session.get("result_id") != candidate_result_id
|
||||
or session.get("contract_id") != "l34d-prediction-hidden-review/v1"
|
||||
or session.get("state") != "saved"
|
||||
or not isinstance(session.get("revision"), int)
|
||||
or session["revision"] < 1
|
||||
or assistance
|
||||
!= {
|
||||
"mode": "prediction-hidden-manual",
|
||||
"independent_truth_eligible": False,
|
||||
}
|
||||
or blindness
|
||||
!= {
|
||||
"candidate_identity_seen": True,
|
||||
"model_prelabels_seen": False,
|
||||
"model_predictions_seen": False,
|
||||
"model_scores_seen": False,
|
||||
}
|
||||
or session.get("authority") != _AUTHORITY
|
||||
or not isinstance(frames, list)
|
||||
or len(frames) != 32
|
||||
):
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"self-review session contract is invalid"
|
||||
)
|
||||
sequences: set[int] = set()
|
||||
for raw_frame in frames:
|
||||
frame = _object(raw_frame, "self-review frame")
|
||||
sequence = _integer(frame.get("truth_island_sequence"), "review sequence")
|
||||
objects = frame.get("objects")
|
||||
if (
|
||||
not 1 <= sequence <= 32
|
||||
or sequence in sequences
|
||||
or frame.get("reviewed") is not True
|
||||
or not isinstance(frame.get("source_sha256"), str)
|
||||
or not isinstance(objects, list)
|
||||
):
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"self-review coverage is incomplete"
|
||||
)
|
||||
sequences.add(sequence)
|
||||
for raw_object in objects:
|
||||
item = _object(raw_object, "self-review object")
|
||||
if item.get("origin") != "manual":
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"self-review contains seeded objects"
|
||||
)
|
||||
category = item.get("category")
|
||||
if (
|
||||
not isinstance(item.get("object_id"), str)
|
||||
or not isinstance(category, str)
|
||||
or not _valid_box(item.get("box_xyxy"))
|
||||
or (
|
||||
category == "unmapped"
|
||||
and not isinstance(item.get("proposed_label"), str)
|
||||
)
|
||||
or (
|
||||
category != "unmapped"
|
||||
and item.get("proposed_label") is not None
|
||||
)
|
||||
):
|
||||
raise L34ESelfReviewDiagnosticError(
|
||||
"self-review object contract is invalid"
|
||||
)
|
||||
|
||||
|
||||
def _diagnostic_associations(
|
||||
predictions: list[dict[str, Any]],
|
||||
references: list[dict[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
remaining_predictions = set(range(len(predictions)))
|
||||
remaining_references = set(range(len(references)))
|
||||
associations: list[dict[str, Any]] = []
|
||||
|
||||
strict_candidates = sorted(
|
||||
(
|
||||
(_iou(prediction["box_xyxy"], reference["box_xyxy"]), p, r)
|
||||
for p, prediction in enumerate(predictions)
|
||||
for r, reference in enumerate(references)
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
for iou, prediction_index, reference_index in strict_candidates:
|
||||
if iou < _STRICT_IOU:
|
||||
break
|
||||
if (
|
||||
prediction_index not in remaining_predictions
|
||||
or reference_index not in remaining_references
|
||||
):
|
||||
continue
|
||||
classification = (
|
||||
"strict_alignment"
|
||||
if predictions[prediction_index]["category"]
|
||||
== references[reference_index]["category"]
|
||||
else "strict_class_mismatch"
|
||||
)
|
||||
associations.append(
|
||||
_association(
|
||||
predictions,
|
||||
references,
|
||||
prediction_index,
|
||||
reference_index,
|
||||
classification,
|
||||
)
|
||||
)
|
||||
remaining_predictions.remove(prediction_index)
|
||||
remaining_references.remove(reference_index)
|
||||
|
||||
loose_candidates = sorted(
|
||||
(
|
||||
(
|
||||
max(
|
||||
_iou(
|
||||
predictions[prediction_index]["box_xyxy"],
|
||||
references[reference_index]["box_xyxy"],
|
||||
),
|
||||
_overlap_over_smaller(
|
||||
predictions[prediction_index]["box_xyxy"],
|
||||
references[reference_index]["box_xyxy"],
|
||||
),
|
||||
),
|
||||
prediction_index,
|
||||
reference_index,
|
||||
)
|
||||
for prediction_index in remaining_predictions
|
||||
for reference_index in remaining_references
|
||||
),
|
||||
reverse=True,
|
||||
)
|
||||
for _, prediction_index, reference_index in loose_candidates:
|
||||
if (
|
||||
prediction_index not in remaining_predictions
|
||||
or reference_index not in remaining_references
|
||||
):
|
||||
continue
|
||||
prediction_box = predictions[prediction_index]["box_xyxy"]
|
||||
reference_box = references[reference_index]["box_xyxy"]
|
||||
iou = _iou(prediction_box, reference_box)
|
||||
overlap = _overlap_over_smaller(prediction_box, reference_box)
|
||||
if iou < _LOOSE_IOU and overlap < _LOOSE_OVERLAP:
|
||||
continue
|
||||
classification = (
|
||||
"localization_disagreement"
|
||||
if predictions[prediction_index]["category"]
|
||||
== references[reference_index]["category"]
|
||||
else "class_and_localization_disagreement"
|
||||
)
|
||||
associations.append(
|
||||
_association(
|
||||
predictions,
|
||||
references,
|
||||
prediction_index,
|
||||
reference_index,
|
||||
classification,
|
||||
)
|
||||
)
|
||||
remaining_predictions.remove(prediction_index)
|
||||
remaining_references.remove(reference_index)
|
||||
|
||||
for prediction in predictions:
|
||||
prediction["diagnostic_verdict"] = "prediction_only"
|
||||
prediction["associated_object_id"] = None
|
||||
prediction["association_iou"] = None
|
||||
prediction["association_overlap_over_smaller"] = None
|
||||
for reference in references:
|
||||
reference["diagnostic_verdict"] = "reference_only"
|
||||
reference["associated_prediction_index"] = None
|
||||
reference["association_iou"] = None
|
||||
reference["association_overlap_over_smaller"] = None
|
||||
for association in associations:
|
||||
prediction = predictions[association["prediction_index"] - 1]
|
||||
reference = next(
|
||||
item
|
||||
for item in references
|
||||
if item["object_id"] == association["object_id"]
|
||||
)
|
||||
prediction["diagnostic_verdict"] = association["classification"]
|
||||
prediction["associated_object_id"] = association["object_id"]
|
||||
prediction["association_iou"] = association["iou"]
|
||||
prediction["association_overlap_over_smaller"] = association[
|
||||
"overlap_over_smaller"
|
||||
]
|
||||
reference["diagnostic_verdict"] = association["classification"]
|
||||
reference["associated_prediction_index"] = association[
|
||||
"prediction_index"
|
||||
]
|
||||
reference["association_iou"] = association["iou"]
|
||||
reference["association_overlap_over_smaller"] = association[
|
||||
"overlap_over_smaller"
|
||||
]
|
||||
return sorted(associations, key=lambda item: item["prediction_index"])
|
||||
|
||||
|
||||
def _association(
|
||||
predictions: list[dict[str, Any]],
|
||||
references: list[dict[str, Any]],
|
||||
prediction_index: int,
|
||||
reference_index: int,
|
||||
classification: str,
|
||||
) -> dict[str, Any]:
|
||||
prediction = predictions[prediction_index]
|
||||
reference = references[reference_index]
|
||||
return {
|
||||
"prediction_index": prediction["prediction_index"],
|
||||
"object_id": reference["object_id"],
|
||||
"prediction_category": prediction["category"],
|
||||
"reference_category": reference["display_category"],
|
||||
"iou": _iou(prediction["box_xyxy"], reference["box_xyxy"]),
|
||||
"overlap_over_smaller": _overlap_over_smaller(
|
||||
prediction["box_xyxy"], reference["box_xyxy"]
|
||||
),
|
||||
"classification": classification,
|
||||
}
|
||||
|
||||
|
||||
def _diagnostic_case_summary(
|
||||
predictions: list[dict[str, Any]],
|
||||
references: list[dict[str, Any]],
|
||||
associations: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
counts: defaultdict[str, int] = defaultdict(int)
|
||||
for association in associations:
|
||||
counts[association["classification"]] += 1
|
||||
prediction_only = sum(
|
||||
item["diagnostic_verdict"] == "prediction_only" for item in predictions
|
||||
)
|
||||
reference_only = sum(
|
||||
item["diagnostic_verdict"] == "reference_only" for item in references
|
||||
)
|
||||
associated = len(associations)
|
||||
return {
|
||||
"prediction_count": len(predictions),
|
||||
"reference_count": len(references),
|
||||
"associated_pair_count": associated,
|
||||
"strict_alignment": counts["strict_alignment"],
|
||||
"strict_class_mismatch": counts["strict_class_mismatch"],
|
||||
"localization_disagreement": counts["localization_disagreement"],
|
||||
"class_and_localization_disagreement": counts[
|
||||
"class_and_localization_disagreement"
|
||||
],
|
||||
"prediction_only": prediction_only,
|
||||
"reference_only": reference_only,
|
||||
"candidate_association_coverage": (
|
||||
associated / len(predictions) if predictions else 0.0
|
||||
),
|
||||
"reference_association_coverage": (
|
||||
associated / len(references) if references else 0.0
|
||||
),
|
||||
"severity_score": (
|
||||
prediction_only
|
||||
+ reference_only * 2
|
||||
+ counts["localization_disagreement"]
|
||||
+ counts["strict_class_mismatch"] * 3
|
||||
+ counts["class_and_localization_disagreement"] * 4
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _aggregate_diagnostic(cases: tuple[dict[str, Any], ...]) -> dict[str, Any]:
|
||||
keys = (
|
||||
"prediction_count",
|
||||
"reference_count",
|
||||
"associated_pair_count",
|
||||
"strict_alignment",
|
||||
"strict_class_mismatch",
|
||||
"localization_disagreement",
|
||||
"class_and_localization_disagreement",
|
||||
"prediction_only",
|
||||
"reference_only",
|
||||
)
|
||||
totals = {
|
||||
key: sum(int(case["diagnostic_summary"][key]) for case in cases)
|
||||
for key in keys
|
||||
}
|
||||
associated = totals["associated_pair_count"]
|
||||
return {
|
||||
**totals,
|
||||
"candidate_association_coverage": (
|
||||
associated / totals["prediction_count"]
|
||||
if totals["prediction_count"]
|
||||
else 0.0
|
||||
),
|
||||
"reference_association_coverage": (
|
||||
associated / totals["reference_count"]
|
||||
if totals["reference_count"]
|
||||
else 0.0
|
||||
),
|
||||
"error_case_count": sum(
|
||||
case["diagnostic_summary"]["strict_class_mismatch"] > 0
|
||||
or case["diagnostic_summary"]["localization_disagreement"] > 0
|
||||
or case["diagnostic_summary"]["class_and_localization_disagreement"]
|
||||
> 0
|
||||
or case["diagnostic_summary"]["prediction_only"] > 0
|
||||
or case["diagnostic_summary"]["reference_only"] > 0
|
||||
for case in cases
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _temporal_count_diagnostics(
|
||||
cases: tuple[dict[str, Any], ...],
|
||||
) -> list[dict[str, Any]]:
|
||||
grouped: dict[str, list[dict[str, Any]]] = defaultdict(list)
|
||||
for case in cases:
|
||||
grouped[_text(case.get("group_id"), "group id")].append(case)
|
||||
result: list[dict[str, Any]] = []
|
||||
for group_id, members in sorted(grouped.items()):
|
||||
ordered = sorted(members, key=lambda item: item["truth_island_sequence"])
|
||||
candidate_counts = [
|
||||
item["diagnostic_summary"]["prediction_count"] for item in ordered
|
||||
]
|
||||
reference_counts = [
|
||||
item["diagnostic_summary"]["reference_count"] for item in ordered
|
||||
]
|
||||
result.append(
|
||||
{
|
||||
"group_id": group_id,
|
||||
"sequences": [item["truth_island_sequence"] for item in ordered],
|
||||
"candidate_counts": candidate_counts,
|
||||
"reference_counts": reference_counts,
|
||||
"candidate_count_range": max(candidate_counts)
|
||||
- min(candidate_counts),
|
||||
"reference_count_range": max(reference_counts)
|
||||
- min(reference_counts),
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def _sequence_map(
|
||||
rows: tuple[dict[str, Any], ...],
|
||||
label: str,
|
||||
) -> dict[int, dict[str, Any]]:
|
||||
result: dict[int, dict[str, Any]] = {}
|
||||
for raw in rows:
|
||||
item = _object(raw, f"{label} row")
|
||||
sequence = _integer(item.get("truth_island_sequence"), f"{label} sequence")
|
||||
if sequence in result:
|
||||
raise L34ESelfReviewDiagnosticError(f"duplicate {label} sequence")
|
||||
result[sequence] = item
|
||||
return result
|
||||
|
||||
|
||||
def _valid_box(value: object) -> bool:
|
||||
if not isinstance(value, list) or len(value) != 4:
|
||||
return False
|
||||
if any(
|
||||
not isinstance(item, (int, float)) or isinstance(item, bool)
|
||||
for item in value
|
||||
):
|
||||
return False
|
||||
left, top, right, bottom = (float(item) for item in value)
|
||||
return 0 <= left < right <= 800 and 0 <= top < bottom <= 600
|
||||
@@ -0,0 +1,350 @@
|
||||
"""Freeze one candidate-visible L3.4F engineering reference.
|
||||
|
||||
The artifact records human adjudication of the L3.4E disagreement gallery.
|
||||
It is deliberately not independent truth and grants no detector, command,
|
||||
navigation, or safety authority.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from k1link.compute.l34e_self_review_diagnostic import (
|
||||
L34E_MANIFEST_NAME,
|
||||
read_l34e_self_review_diagnostic,
|
||||
)
|
||||
|
||||
L34F_RESULT_SCHEMA: Final = "missioncore.l34f-adjudicated-reference/v1"
|
||||
L34F_REPORT_SCHEMA: Final = "missioncore.l34f-adjudicated-reference-report/v1"
|
||||
L34F_CASE_SCHEMA: Final = "missioncore.l34f-adjudicated-reference-case/v1"
|
||||
L34F_MANIFEST_NAME: Final = "manifest.json"
|
||||
L34F_REPORT_NAME: Final = "adjudicated-reference-report.json"
|
||||
L34F_CASES_NAME: Final = "adjudicated-reference-cases.jsonl"
|
||||
L34F_SESSION_SCHEMA: Final = "missioncore.l34f-adjudication-session/v1"
|
||||
|
||||
_RESULT_ID = re.compile(r"^l34f-adjudicated-reference-[a-f0-9]{64}$")
|
||||
_SESSION_ID = re.compile(r"^l34f-adjudication-session-[a-f0-9]{64}$")
|
||||
_AUTHORITY: Final = {
|
||||
"ground_truth": False,
|
||||
"independent_truth": False,
|
||||
"metric_grade_reference": False,
|
||||
"candidate_accepted": False,
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L34FAdjudicatedReferenceError(ValueError):
|
||||
"""Raised when an L3.4F source or immutable artifact is invalid."""
|
||||
|
||||
|
||||
def build_l34f_adjudicated_reference(
|
||||
*,
|
||||
diagnostic_root: Path,
|
||||
adjudication_session_path: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
diagnostic = read_l34e_self_review_diagnostic(diagnostic_root)
|
||||
session_path = adjudication_session_path.expanduser().resolve(strict=True)
|
||||
if not session_path.is_file() or session_path.is_symlink():
|
||||
raise L34FAdjudicatedReferenceError("adjudication session unavailable")
|
||||
session = _read_json(session_path)
|
||||
_validate_session(session, diagnostic["result_id"])
|
||||
|
||||
source_cases = {int(case["truth_island_sequence"]): case for case in diagnostic["cases"]}
|
||||
cases: list[dict[str, Any]] = []
|
||||
totals = {
|
||||
"frame_count": 32,
|
||||
"source_reference_count": 0,
|
||||
"adjudicated_reference_count": 0,
|
||||
"unchanged_object_count": 0,
|
||||
"geometry_changed_object_count": 0,
|
||||
"class_changed_object_count": 0,
|
||||
"attribute_changed_object_count": 0,
|
||||
"added_object_count": 0,
|
||||
"deleted_object_count": 0,
|
||||
"changed_frame_count": 0,
|
||||
}
|
||||
for frame in sorted(session["frames"], key=lambda row: row["truth_island_sequence"]):
|
||||
sequence = int(frame["truth_island_sequence"])
|
||||
source = source_cases[sequence]
|
||||
source_by_id = {item["object_id"]: item for item in source["references"]}
|
||||
final_by_id = {item["object_id"]: item for item in frame["objects"]}
|
||||
changes: list[dict[str, Any]] = []
|
||||
for object_id, original in source_by_id.items():
|
||||
final = final_by_id.get(object_id)
|
||||
if final is None:
|
||||
changes.append({"type": "delete", "object_id": object_id})
|
||||
totals["deleted_object_count"] += 1
|
||||
continue
|
||||
changed = False
|
||||
if original["box_xyxy"] != final["box_xyxy"]:
|
||||
changes.append(
|
||||
{
|
||||
"type": "geometry",
|
||||
"object_id": object_id,
|
||||
"before": original["box_xyxy"],
|
||||
"after": final["box_xyxy"],
|
||||
}
|
||||
)
|
||||
totals["geometry_changed_object_count"] += 1
|
||||
changed = True
|
||||
if original["category"] != final["category"] or original.get(
|
||||
"proposed_label"
|
||||
) != final.get("proposed_label"):
|
||||
changes.append(
|
||||
{
|
||||
"type": "class",
|
||||
"object_id": object_id,
|
||||
"before": {
|
||||
"category": original["category"],
|
||||
"proposed_label": original.get("proposed_label"),
|
||||
},
|
||||
"after": {
|
||||
"category": final["category"],
|
||||
"proposed_label": final.get("proposed_label"),
|
||||
},
|
||||
}
|
||||
)
|
||||
totals["class_changed_object_count"] += 1
|
||||
changed = True
|
||||
if bool(original["occluded"]) != bool(final["occluded"]) or bool(
|
||||
original["truncated"]
|
||||
) != bool(final["truncated"]):
|
||||
changes.append({"type": "attributes", "object_id": object_id})
|
||||
totals["attribute_changed_object_count"] += 1
|
||||
changed = True
|
||||
if not changed:
|
||||
totals["unchanged_object_count"] += 1
|
||||
for object_id in final_by_id.keys() - source_by_id.keys():
|
||||
changes.append({"type": "add", "object_id": object_id})
|
||||
totals["added_object_count"] += 1
|
||||
if changes:
|
||||
totals["changed_frame_count"] += 1
|
||||
totals["source_reference_count"] += len(source_by_id)
|
||||
totals["adjudicated_reference_count"] += len(final_by_id)
|
||||
cases.append(
|
||||
{
|
||||
"schema_version": L34F_CASE_SCHEMA,
|
||||
"truth_island_sequence": sequence,
|
||||
"image_id": int(source["image_id"]),
|
||||
"frame_index": int(source["frame_index"]),
|
||||
"group_id": str(source["group_id"]),
|
||||
"session_seconds": float(source["session_seconds"]),
|
||||
"source_image_sha256": str(source["source_image_sha256"]),
|
||||
"references": frame["objects"],
|
||||
"source_reference_count": len(source_by_id),
|
||||
"change_count": len(changes),
|
||||
"changes": changes,
|
||||
}
|
||||
)
|
||||
|
||||
report_basis = {
|
||||
"schema_version": L34F_REPORT_SCHEMA,
|
||||
"status": "completed-candidate-visible-adjudication-not-truth",
|
||||
"metrics": totals,
|
||||
"decision": {
|
||||
"adjudication_complete": True,
|
||||
"engineering_reference_available": True,
|
||||
"metric_grade_reference_available": False,
|
||||
"independent_truth_available": False,
|
||||
"candidate_accepted": False,
|
||||
"detector_retuning_authorized": False,
|
||||
"l35_blind_gate_open": False,
|
||||
"next_action": (
|
||||
"obtain two prediction-free independent reviewer submissions "
|
||||
"and explicit adjudication before E48 and one-shot L3.5"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
"candidate predictions were visible during adjudication",
|
||||
"the artifact is an engineering reference and not independent ground truth",
|
||||
"the sample contains 32 recorded RIGHT-camera frames on one route",
|
||||
"no live, left-camera, LiDAR-range, navigation, command, or safety claim is made",
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
"ground_truth": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": L34F_RESULT_SCHEMA,
|
||||
"l34e_diagnostic": {
|
||||
"result_id": diagnostic["result_id"],
|
||||
"manifest_sha256": _sha256(diagnostic["result_root"] / L34E_MANIFEST_NAME),
|
||||
},
|
||||
"adjudication_session": {
|
||||
"session_id": session["session_id"],
|
||||
"session_sha256": _sha256(session_path),
|
||||
"revision": session["revision"],
|
||||
},
|
||||
"report_sha256": hashlib.sha256(_canonical_json(report_basis)).hexdigest(),
|
||||
"cases_sha256": hashlib.sha256(_canonical_json(cases)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l34f-adjudicated-reference-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l34f_adjudicated_reference(destination)
|
||||
created_at_utc = _utc_now()
|
||||
report = {
|
||||
**report_basis,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"created_at_utc": created_at_utc,
|
||||
"source_session_id": "RAVNOVES00",
|
||||
"camera_source_id": "sensor.camera.right",
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / L34F_REPORT_NAME, report)
|
||||
_write_jsonl(staging / L34F_CASES_NAME, cases)
|
||||
artifacts = [
|
||||
_artifact(staging / L34F_REPORT_NAME, "adjudication-report"),
|
||||
_artifact(staging / L34F_CASES_NAME, "adjudicated-cases"),
|
||||
]
|
||||
_write_json(
|
||||
staging / L34F_MANIFEST_NAME,
|
||||
{
|
||||
"schema_version": L34F_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": created_at_utc,
|
||||
"acceptance_state": "engineering-reference-not-truth",
|
||||
"ground_truth": False,
|
||||
"artifacts": artifacts,
|
||||
"authority": _AUTHORITY,
|
||||
},
|
||||
)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l34f_adjudicated_reference(destination)
|
||||
|
||||
|
||||
def read_l34f_adjudicated_reference(root: Path) -> dict[str, Any]:
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / L34F_MANIFEST_NAME)
|
||||
identity = manifest.get("identity")
|
||||
if not isinstance(identity, dict):
|
||||
raise L34FAdjudicatedReferenceError("L3.4F identity invalid")
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
if (
|
||||
manifest.get("schema_version") != L34F_RESULT_SCHEMA
|
||||
or manifest.get("identity_sha256") != identity_sha256
|
||||
or manifest.get("result_id") != f"l34f-adjudicated-reference-{identity_sha256}"
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or _RESULT_ID.fullmatch(resolved.name) is None
|
||||
or manifest.get("acceptance_state") != "engineering-reference-not-truth"
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
or manifest.get("ground_truth") is not False
|
||||
):
|
||||
raise L34FAdjudicatedReferenceError("L3.4F identity invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 2:
|
||||
raise L34FAdjudicatedReferenceError("L3.4F artifacts invalid")
|
||||
by_role = {item.get("role"): item for item in artifacts if isinstance(item, dict)}
|
||||
report = _read_json(_validated_artifact(resolved, by_role.get("adjudication-report")))
|
||||
cases = tuple(_read_jsonl(_validated_artifact(resolved, by_role.get("adjudicated-cases"))))
|
||||
if (
|
||||
report.get("schema_version") != L34F_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("status") != "completed-candidate-visible-adjudication-not-truth"
|
||||
or report.get("authority") != _AUTHORITY
|
||||
or report.get("ground_truth") is not False
|
||||
or len(cases) != 32
|
||||
or any(case.get("schema_version") != L34F_CASE_SCHEMA for case in cases)
|
||||
or hashlib.sha256(_canonical_json(cases)).hexdigest() != identity.get("cases_sha256")
|
||||
):
|
||||
raise L34FAdjudicatedReferenceError("L3.4F result changed")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
"cases": cases,
|
||||
}
|
||||
|
||||
|
||||
def _validate_session(session: dict[str, Any], diagnostic_result_id: str) -> None:
|
||||
frames = session.get("frames")
|
||||
if (
|
||||
session.get("schema_version") != L34F_SESSION_SCHEMA
|
||||
or not isinstance(session.get("session_id"), str)
|
||||
or _SESSION_ID.fullmatch(session["session_id"]) is None
|
||||
or session.get("diagnostic_result_id") != diagnostic_result_id
|
||||
or session.get("state") != "saved"
|
||||
or not isinstance(session.get("revision"), int)
|
||||
or session["revision"] < 1
|
||||
or session.get("authority") != _AUTHORITY
|
||||
or not isinstance(frames, list)
|
||||
or len(frames) != 32
|
||||
or any(frame.get("reviewed") is not True for frame in frames if isinstance(frame, dict))
|
||||
):
|
||||
raise L34FAdjudicatedReferenceError("adjudication session incomplete")
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, Any]:
|
||||
return {
|
||||
"path": path.name,
|
||||
"role": role,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _validated_artifact(root: Path, raw: object) -> Path:
|
||||
if not isinstance(raw, dict) or not isinstance(raw.get("path"), str):
|
||||
raise L34FAdjudicatedReferenceError("L3.4F artifact invalid")
|
||||
path = (root / raw["path"]).resolve(strict=True)
|
||||
if path.parent != root or path.is_symlink() or _sha256(path) != raw.get("sha256"):
|
||||
raise L34FAdjudicatedReferenceError("L3.4F artifact changed")
|
||||
return path
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False
|
||||
).encode()
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(value, dict):
|
||||
raise L34FAdjudicatedReferenceError("expected JSON object")
|
||||
return value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
values = [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines() if line]
|
||||
if any(not isinstance(value, dict) for value in values):
|
||||
raise L34FAdjudicatedReferenceError("expected JSONL objects")
|
||||
return values
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_bytes(_canonical_json(value) + b"\n")
|
||||
|
||||
|
||||
def _write_jsonl(path: Path, values: list[dict[str, Any]]) -> None:
|
||||
path.write_bytes(b"".join(_canonical_json(value) + b"\n" for value in values))
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z")
|
||||
@@ -0,0 +1,407 @@
|
||||
"""Evaluate the exact L3.4 YOLOX freeze after an accepted E48 truth seal."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Final
|
||||
|
||||
from .e46_detector_truth_island import (
|
||||
E46_MANIFEST_NAME,
|
||||
E46DetectorTruthIslandError,
|
||||
read_e46_detector_truth_island,
|
||||
)
|
||||
from .e48_detector_truth_seal import (
|
||||
E48_MANIFEST_NAME,
|
||||
E48DetectorTruthSealError,
|
||||
read_e48_detector_truth_seal,
|
||||
)
|
||||
from .e49_detector_truth_evaluation import (
|
||||
E49DetectorTruthEvaluationError,
|
||||
evaluate_frozen_detector_candidates,
|
||||
read_valid_fov_mask,
|
||||
)
|
||||
from .l34_right_yolox_truth_island_freeze import (
|
||||
L34_MANIFEST_NAME,
|
||||
L34_PREDICTION_SCHEMA,
|
||||
L34RightYoloxTruthIslandError,
|
||||
read_l34_right_yolox_truth_island_freeze,
|
||||
)
|
||||
|
||||
L35_RESULT_SCHEMA: Final = "missioncore.l35-right-yolox-truth-evaluation/v1"
|
||||
L35_REPORT_SCHEMA: Final = "missioncore.l35-right-yolox-evaluation-report/v1"
|
||||
L35_MANIFEST_NAME: Final = "manifest.json"
|
||||
L35_REPORT_NAME: Final = "right-yolox-evaluation-report.json"
|
||||
|
||||
_RESULT_ID: Final = re.compile(
|
||||
r"^l35-right-yolox-truth-evaluation-[a-f0-9]{64}$"
|
||||
)
|
||||
_AUTHORITY: Final = {
|
||||
"commands_enabled": False,
|
||||
"navigation_or_safety_accepted": False,
|
||||
}
|
||||
|
||||
|
||||
class L35RightYoloxTruthEvaluationError(RuntimeError):
|
||||
"""The L3.4 freeze cannot be joined to accepted independent truth."""
|
||||
|
||||
|
||||
def build_l35_right_yolox_truth_evaluation(
|
||||
*,
|
||||
truth_island_root: Path,
|
||||
truth_seal_root: Path,
|
||||
l34_freeze_root: Path,
|
||||
valid_fov_root: Path,
|
||||
output_root: Path,
|
||||
) -> dict[str, Any]:
|
||||
"""Join L3.4 to E48 only after truth was sealed after the exact freeze."""
|
||||
|
||||
try:
|
||||
truth_island = read_e46_detector_truth_island(truth_island_root)
|
||||
truth_seal = read_e48_detector_truth_seal(truth_seal_root)
|
||||
l34_freeze = read_l34_right_yolox_truth_island_freeze(l34_freeze_root)
|
||||
except (
|
||||
E46DetectorTruthIslandError,
|
||||
E48DetectorTruthSealError,
|
||||
L34RightYoloxTruthIslandError,
|
||||
) as reason:
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
"L3.5 evaluation input is invalid"
|
||||
) from reason
|
||||
|
||||
seal_report = _object(truth_seal.get("report"), "E48 report")
|
||||
seal_identity = _object(
|
||||
_object(truth_seal.get("manifest"), "E48 manifest").get("identity"),
|
||||
"E48 identity",
|
||||
)
|
||||
sealed_island = _object(
|
||||
seal_identity.get("truth_island"),
|
||||
"E48 truth island",
|
||||
)
|
||||
l34_identity = _object(l34_freeze.manifest.get("identity"), "L3.4 identity")
|
||||
l34_island = _object(
|
||||
l34_identity.get("truth_island"),
|
||||
"L3.4 truth island",
|
||||
)
|
||||
if (
|
||||
seal_report.get("status") != "sealed-adjudicated-independent-truth"
|
||||
or _object(seal_report.get("decision"), "E48 decision").get(
|
||||
"candidate_comparison_authorized"
|
||||
)
|
||||
is not True
|
||||
or sealed_island.get("result_id") != truth_island.result_id
|
||||
or l34_island.get("result_id") != truth_island.result_id
|
||||
):
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
"truth island, seal and L3.4 identities differ"
|
||||
)
|
||||
provenance = _object(truth_seal.get("provenance"), "E48 provenance")
|
||||
_require_freeze_before_truth_seal(
|
||||
freeze_created_at_utc=l34_freeze.manifest.get("created_at_utc"),
|
||||
truth_sealed_at_utc=provenance.get("sealed_at_utc"),
|
||||
)
|
||||
|
||||
source = _object(
|
||||
_object(truth_island.manifest.get("identity"), "E46 identity").get(
|
||||
"source"
|
||||
),
|
||||
"E46 source",
|
||||
)
|
||||
try:
|
||||
valid_fov = read_valid_fov_mask(
|
||||
valid_fov_root,
|
||||
calibration_sha256=str(source["calibration_sha256"]),
|
||||
calibration_slot=str(source["calibration_slot"]),
|
||||
)
|
||||
prediction_rows = l34_rows_for_sealed_truth(l34_freeze.predictions)
|
||||
metrics = evaluate_frozen_detector_candidates(
|
||||
truth_rows=tuple(truth_seal["truth_rows"]),
|
||||
prediction_rows=prediction_rows,
|
||||
valid_fov_mask=valid_fov["mask"],
|
||||
)
|
||||
except (KeyError, E49DetectorTruthEvaluationError) as reason:
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
"L3.4 predictions cannot be evaluated against sealed truth"
|
||||
) from reason
|
||||
|
||||
profile = {
|
||||
"profile_id": "l35-ravnoves00-right-yolox-evaluation/v1",
|
||||
"metric_engine": "e49-frozen-detector-metrics/v1",
|
||||
"candidate_selection_policy": "no-automatic-winner",
|
||||
"model_retraining_authorized": False,
|
||||
}
|
||||
identity = {
|
||||
"schema_version": L35_RESULT_SCHEMA,
|
||||
"truth_island": {
|
||||
"result_id": truth_island.result_id,
|
||||
"manifest_sha256": _sha256(
|
||||
truth_island.result_root / E46_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
"truth_seal": {
|
||||
"result_id": truth_seal["result_id"],
|
||||
"manifest_sha256": _sha256(
|
||||
truth_seal["result_root"] / E48_MANIFEST_NAME
|
||||
),
|
||||
},
|
||||
"l34_freeze": {
|
||||
"result_id": l34_freeze.result_id,
|
||||
"manifest_sha256": _sha256(
|
||||
l34_freeze.result_root / L34_MANIFEST_NAME
|
||||
),
|
||||
"prediction_rows_sha256": _object(
|
||||
l34_identity.get("candidate"),
|
||||
"L3.4 candidate identity",
|
||||
)["prediction_rows_sha256"],
|
||||
},
|
||||
"valid_fov": valid_fov["identity"],
|
||||
"profile": profile,
|
||||
"metrics_sha256": hashlib.sha256(_canonical_json(metrics)).hexdigest(),
|
||||
"producer_sha256": _sha256(Path(__file__).resolve(strict=True)),
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"l35-right-yolox-truth-evaluation-{identity_sha256}"
|
||||
destination = output_root.expanduser().absolute() / result_id
|
||||
if destination.exists():
|
||||
return read_l35_right_yolox_truth_evaluation(destination)
|
||||
|
||||
report = {
|
||||
"schema_version": L35_REPORT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"status": "completed-sealed-truth-right-yolox-evaluation",
|
||||
"frame_count": len(truth_seal["truth_rows"]),
|
||||
"candidate_count": len(metrics),
|
||||
"profile": profile,
|
||||
"candidates": metrics,
|
||||
"decision": {
|
||||
"truth_join_performed": True,
|
||||
"accuracy_metrics_available": True,
|
||||
"candidate_winner_selected": False,
|
||||
"model_retraining_authorized": False,
|
||||
"next_gate": (
|
||||
"review the preregistered metrics and explicitly accept or "
|
||||
"reject the frozen right-camera YOLOX candidate"
|
||||
),
|
||||
},
|
||||
"limitations": [
|
||||
"source-scoped to the 32-frame RAVNOVES00 right-camera Truth Island",
|
||||
"recorded replay only; live transport and hardware are out of scope",
|
||||
"truth evaluation does not authorize navigation or safety claims",
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
destination.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
||||
staging = destination.parent / f".{result_id}.{uuid.uuid4().hex}.tmp"
|
||||
staging.mkdir(mode=0o700, exist_ok=False)
|
||||
try:
|
||||
_write_json(staging / L35_REPORT_NAME, report)
|
||||
manifest = {
|
||||
"schema_version": L35_RESULT_SCHEMA,
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": _utc_now(),
|
||||
"acceptance_state": "accepted-metrics-only-no-candidate-decision",
|
||||
"artifacts": [
|
||||
_artifact(staging / L35_REPORT_NAME, "evaluation-report")
|
||||
],
|
||||
"authority": _AUTHORITY,
|
||||
}
|
||||
_write_json(staging / L35_MANIFEST_NAME, manifest)
|
||||
os.replace(staging, destination)
|
||||
except BaseException:
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
raise
|
||||
return read_l35_right_yolox_truth_evaluation(destination)
|
||||
|
||||
|
||||
def l34_rows_for_sealed_truth(
|
||||
rows: tuple[dict[str, Any], ...],
|
||||
) -> tuple[dict[str, Any], ...]:
|
||||
"""Adapt immutable L3.4 rows to the shared E49 metric engine contract."""
|
||||
|
||||
converted: list[dict[str, Any]] = []
|
||||
for row in rows:
|
||||
predictions = row.get("predictions")
|
||||
if (
|
||||
row.get("schema_version") != L34_PREDICTION_SCHEMA
|
||||
or row.get("truth_joined") is not False
|
||||
or not isinstance(predictions, list)
|
||||
):
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
"L3.4 prediction row is invalid"
|
||||
)
|
||||
converted.append(
|
||||
{
|
||||
"candidate_id": row.get("candidate_id"),
|
||||
"truth_island_sequence": row.get("truth_island_sequence"),
|
||||
"image_id": row.get("image_id"),
|
||||
"frame_index": row.get("frame_index"),
|
||||
"session_seconds": row.get("session_seconds"),
|
||||
"source_image_sha256": row.get("source_image_sha256"),
|
||||
"predictions": [
|
||||
{
|
||||
"category": prediction.get("label"),
|
||||
"score": prediction.get("score"),
|
||||
"box_xyxy": prediction.get("bbox_xyxy"),
|
||||
}
|
||||
for prediction in predictions
|
||||
if isinstance(prediction, dict)
|
||||
],
|
||||
"truth_joined": False,
|
||||
}
|
||||
)
|
||||
if len(converted[-1]["predictions"]) != len(predictions):
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
"L3.4 prediction entry is invalid"
|
||||
)
|
||||
return tuple(converted)
|
||||
|
||||
|
||||
def read_l35_right_yolox_truth_evaluation(root: Path) -> dict[str, Any]:
|
||||
"""Read and revalidate an immutable L3.5 evaluation result."""
|
||||
|
||||
resolved = root.resolve(strict=True)
|
||||
manifest = _read_json(resolved / L35_MANIFEST_NAME)
|
||||
identity = _object(manifest.get("identity"), "L3.5 identity")
|
||||
identity_sha256 = manifest.get("identity_sha256")
|
||||
if (
|
||||
manifest.get("schema_version") != L35_RESULT_SCHEMA
|
||||
or not isinstance(identity_sha256, str)
|
||||
or hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
!= identity_sha256
|
||||
or manifest.get("result_id")
|
||||
!= f"l35-right-yolox-truth-evaluation-{identity_sha256}"
|
||||
or not _RESULT_ID.fullmatch(resolved.name)
|
||||
or resolved.name != manifest.get("result_id")
|
||||
or manifest.get("acceptance_state")
|
||||
!= "accepted-metrics-only-no-candidate-decision"
|
||||
or manifest.get("authority") != _AUTHORITY
|
||||
):
|
||||
raise L35RightYoloxTruthEvaluationError("L3.5 identity is invalid")
|
||||
artifacts = manifest.get("artifacts")
|
||||
if not isinstance(artifacts, list) or len(artifacts) != 1:
|
||||
raise L35RightYoloxTruthEvaluationError("L3.5 artifacts are invalid")
|
||||
artifact = _object(artifacts[0], "L3.5 report artifact")
|
||||
report_path = resolved / str(artifact.get("path"))
|
||||
if (
|
||||
report_path.parent != resolved
|
||||
or not report_path.is_file()
|
||||
or report_path.is_symlink()
|
||||
or artifact.get("byte_length") != report_path.stat().st_size
|
||||
or artifact.get("sha256") != _sha256(report_path)
|
||||
):
|
||||
raise L35RightYoloxTruthEvaluationError("L3.5 report changed")
|
||||
report = _read_json(report_path)
|
||||
if (
|
||||
report.get("schema_version") != L35_REPORT_SCHEMA
|
||||
or report.get("result_id") != resolved.name
|
||||
or report.get("identity_sha256") != identity_sha256
|
||||
or report.get("status")
|
||||
!= "completed-sealed-truth-right-yolox-evaluation"
|
||||
or hashlib.sha256(_canonical_json(report.get("candidates"))).hexdigest()
|
||||
!= identity.get("metrics_sha256")
|
||||
):
|
||||
raise L35RightYoloxTruthEvaluationError("L3.5 report is invalid")
|
||||
return {
|
||||
"result_id": resolved.name,
|
||||
"result_root": resolved,
|
||||
"manifest": manifest,
|
||||
"report": report,
|
||||
}
|
||||
|
||||
|
||||
def _require_freeze_before_truth_seal(
|
||||
*,
|
||||
freeze_created_at_utc: object,
|
||||
truth_sealed_at_utc: object,
|
||||
) -> None:
|
||||
freeze = _utc_timestamp(freeze_created_at_utc, "L3.4 created_at_utc")
|
||||
seal = _utc_timestamp(truth_sealed_at_utc, "E48 sealed_at_utc")
|
||||
if freeze > seal:
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
"L3.4 prediction freeze postdates the independent truth seal"
|
||||
)
|
||||
|
||||
|
||||
def _utc_timestamp(value: object, field: str) -> datetime:
|
||||
if not isinstance(value, str) or not value.endswith("Z"):
|
||||
raise L35RightYoloxTruthEvaluationError(f"{field} is invalid")
|
||||
try:
|
||||
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
except ValueError as reason:
|
||||
raise L35RightYoloxTruthEvaluationError(f"{field} is invalid") from reason
|
||||
if parsed.tzinfo is None:
|
||||
raise L35RightYoloxTruthEvaluationError(f"{field} is invalid")
|
||||
return parsed.astimezone(UTC)
|
||||
|
||||
|
||||
def _object(value: object, field: str) -> dict[str, Any]:
|
||||
if not isinstance(value, dict):
|
||||
raise L35RightYoloxTruthEvaluationError(f"{field} must be an object")
|
||||
return value
|
||||
|
||||
|
||||
def _read_json(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
value = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, ValueError) as reason:
|
||||
raise L35RightYoloxTruthEvaluationError(
|
||||
f"cannot read {path.name}"
|
||||
) from reason
|
||||
return _object(value, path.name)
|
||||
|
||||
|
||||
def _canonical_json(value: object) -> bytes:
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as stream:
|
||||
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def _artifact(path: Path, role: str) -> dict[str, Any]:
|
||||
return {
|
||||
"path": path.name,
|
||||
"role": role,
|
||||
"byte_length": path.stat().st_size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
|
||||
|
||||
def _write_json(path: Path, value: object) -> None:
|
||||
path.write_text(
|
||||
json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
|
||||
def _utc_now() -> str:
|
||||
return datetime.now(UTC).isoformat(timespec="milliseconds").replace(
|
||||
"+00:00", "Z"
|
||||
)
|
||||
@@ -568,21 +568,25 @@ def _semantic_support(
|
||||
reason = "camera-semantic-without-qualified-occupied-lidar-support"
|
||||
if occupied_count:
|
||||
ranges = projected.depths_m[clustered_rows]
|
||||
range_m = float(np.median(ranges))
|
||||
range_estimate_m = float(np.median(ranges))
|
||||
centroid = np.median(occupied_points, axis=0).astype(np.float64).tolist()
|
||||
height_range = [
|
||||
float(np.min(point_height_m[occupied_indices])),
|
||||
float(np.max(point_height_m[occupied_indices])),
|
||||
]
|
||||
else:
|
||||
range_m = None
|
||||
range_estimate_m = None
|
||||
centroid = None
|
||||
height_range = None
|
||||
range_m = range_estimate_m if support_agrees else None
|
||||
base.update(
|
||||
{
|
||||
"geometry_status": status,
|
||||
"geometry_reason": reason,
|
||||
"range_m": range_m,
|
||||
"range_estimate_m": range_estimate_m,
|
||||
"range_estimate_available": range_estimate_m is not None,
|
||||
"range_support_qualified": support_agrees,
|
||||
"occupied_centroid_map_xyz_m": centroid,
|
||||
"occupied_height_range_m": height_range,
|
||||
"support": {
|
||||
@@ -893,6 +897,9 @@ def _empty_geometry(status: str, reason: str) -> dict[str, object]:
|
||||
"geometry_status": status,
|
||||
"geometry_reason": reason,
|
||||
"range_m": None,
|
||||
"range_estimate_m": None,
|
||||
"range_estimate_available": False,
|
||||
"range_support_qualified": False,
|
||||
"occupied_centroid_map_xyz_m": None,
|
||||
"occupied_height_range_m": None,
|
||||
"support": {
|
||||
|
||||
Reference in New Issue
Block a user