feat(perception): qualify inline temporal stability
This commit is contained in:
@@ -39,6 +39,7 @@ from .lab_instances import (
|
||||
PublishedTemporalLabInstance,
|
||||
publish_e21_lab_instance,
|
||||
publish_e22_lab_instance,
|
||||
publish_e23_lab_instance,
|
||||
publish_integrated_lab_instance,
|
||||
)
|
||||
from .live_perception import (
|
||||
@@ -148,6 +149,7 @@ __all__ = [
|
||||
"validate_integrated_perception_result",
|
||||
"publish_e21_lab_instance",
|
||||
"publish_e22_lab_instance",
|
||||
"publish_e23_lab_instance",
|
||||
"publish_integrated_lab_instance",
|
||||
"validate_multirate_perception_qualification_result",
|
||||
"prepare_recorded_qualification_slice",
|
||||
|
||||
@@ -0,0 +1,628 @@
|
||||
"""Bounded inline temporal state for the warm perception worker.
|
||||
|
||||
This module intentionally depends only on the Python standard library and
|
||||
NumPy so the exact implementation can be included in the minimal, hashed
|
||||
worker package. It has no command or navigation authority.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import time
|
||||
from collections import deque
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
|
||||
PROFILE_SCHEMA = "missioncore.e23-inline-temporal-profile/v1"
|
||||
PIPELINE_ID = "warm-worker-inline-bounded-temporal-2d-3d-semantic/v1"
|
||||
TARGET_CLASS_COUNT = 16
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _TrackState:
|
||||
canonical_id: int
|
||||
source_ids: set[int]
|
||||
label: str
|
||||
group: str
|
||||
center: np.ndarray
|
||||
size: np.ndarray
|
||||
raw_center: np.ndarray
|
||||
velocity: np.ndarray
|
||||
last_frame: int
|
||||
last_seconds: float
|
||||
last_observed_frame: int
|
||||
score: float
|
||||
template: dict[str, Any]
|
||||
cuboid_center: np.ndarray | None = None
|
||||
cuboid_size: np.ndarray | None = None
|
||||
cuboid_yaw: float | None = None
|
||||
cuboid_velocity: np.ndarray | None = None
|
||||
cuboid_seconds: float | None = None
|
||||
distance_m: float | None = None
|
||||
|
||||
|
||||
class TemporalStabilizer:
|
||||
"""One-pass bounded state machine for 2D tracks and map-frame cuboids."""
|
||||
|
||||
def __init__(self, profile: dict[str, Any]) -> None:
|
||||
self.profile = profile
|
||||
self.states: dict[int, _TrackState] = {}
|
||||
self.aliases: dict[int, int] = {}
|
||||
self.next_id = 1
|
||||
self.peak_states = 0
|
||||
self.stitched_tracks = 0
|
||||
self.held_2d = 0
|
||||
self.held_3d = 0
|
||||
self.reset_2d = 0
|
||||
self.reset_3d = 0
|
||||
|
||||
def update(
|
||||
self,
|
||||
*,
|
||||
frame_index: int,
|
||||
session_seconds: float,
|
||||
objects: list[dict[str, Any]],
|
||||
) -> list[dict[str, Any]]:
|
||||
self._prune(session_seconds)
|
||||
assigned: set[int] = set()
|
||||
result: list[dict[str, Any]] = []
|
||||
for source in sorted(objects, key=lambda item: int(item["track_id"])):
|
||||
state, stitched = self._resolve(source, frame_index, assigned)
|
||||
assigned.add(state.canonical_id)
|
||||
result.append(
|
||||
self._observe(
|
||||
state,
|
||||
source,
|
||||
frame_index=frame_index,
|
||||
session_seconds=session_seconds,
|
||||
stitched=stitched,
|
||||
)
|
||||
)
|
||||
|
||||
hold_frames = int(self.profile["tracking_2d"]["hold_frames"])
|
||||
for state in sorted(self.states.values(), key=lambda value: value.canonical_id):
|
||||
if state.canonical_id in assigned:
|
||||
continue
|
||||
gap = frame_index - state.last_observed_frame
|
||||
if not 1 <= gap <= hold_frames:
|
||||
continue
|
||||
held = json.loads(json.dumps(state.template))
|
||||
state.center = state.center + state.velocity
|
||||
state.last_frame = frame_index
|
||||
held["track_id"] = state.canonical_id
|
||||
held["bbox_xyxy"] = _box(state.center, state.size)
|
||||
held["score"] = max(0.0, state.score * (0.82**gap))
|
||||
held["temporal_2d_status"] = f"held-{gap}-frame"
|
||||
held["temporal_source_track_id"] = min(state.source_ids)
|
||||
self.held_2d += 1
|
||||
if not self._hold_cuboid(held, state, session_seconds):
|
||||
_clear_cuboid(held, "rejected-temporal-hold-expired-e23")
|
||||
result.append(held)
|
||||
|
||||
self.peak_states = max(self.peak_states, len(self.states))
|
||||
return sorted(result, key=lambda item: int(item["track_id"]))
|
||||
|
||||
def snapshot(self) -> dict[str, int]:
|
||||
return {
|
||||
"active_track_states": len(self.states),
|
||||
"peak_track_states": self.peak_states,
|
||||
"alias_count": len(self.aliases),
|
||||
"stitched_tracks": self.stitched_tracks,
|
||||
"held_2d": self.held_2d,
|
||||
"held_3d": self.held_3d,
|
||||
"reset_2d": self.reset_2d,
|
||||
"reset_3d": self.reset_3d,
|
||||
}
|
||||
|
||||
def _resolve(
|
||||
self,
|
||||
source: dict[str, Any],
|
||||
frame_index: int,
|
||||
assigned: set[int],
|
||||
) -> tuple[_TrackState, bool]:
|
||||
source_id = int(source["track_id"])
|
||||
canonical = self.aliases.get(source_id)
|
||||
if canonical is not None and canonical in self.states:
|
||||
return self.states[canonical], False
|
||||
|
||||
bbox = np.asarray(source["bbox_xyxy"], dtype=np.float64)
|
||||
center, size = _center_size(bbox)
|
||||
config = self.profile["tracking_2d"]
|
||||
best: tuple[float, _TrackState] | None = None
|
||||
for state in self.states.values():
|
||||
gap = frame_index - state.last_observed_frame
|
||||
if (
|
||||
state.canonical_id in assigned
|
||||
or state.label != str(source["label"])
|
||||
or not 1 <= gap <= int(config["stitch_gap_frames"])
|
||||
):
|
||||
continue
|
||||
predicted = state.center + state.velocity * gap
|
||||
predicted_box = np.asarray(_box(predicted, state.size), dtype=np.float64)
|
||||
iou = _iou(predicted_box, bbox)
|
||||
scale = max(1.0, math.sqrt(float(np.prod(np.maximum(state.size, 1.0)))))
|
||||
normalized_distance = float(np.linalg.norm(center - predicted) / scale)
|
||||
if iou < float(config["stitch_minimum_iou"]) and normalized_distance > float(
|
||||
config["stitch_maximum_normalized_center_distance"]
|
||||
):
|
||||
continue
|
||||
score = iou - 0.25 * normalized_distance
|
||||
if best is None or score > best[0]:
|
||||
best = (score, state)
|
||||
if best is not None:
|
||||
state = best[1]
|
||||
state.source_ids.add(source_id)
|
||||
self.aliases[source_id] = state.canonical_id
|
||||
self.stitched_tracks += 1
|
||||
return state, True
|
||||
|
||||
canonical_id = source_id
|
||||
if canonical_id in self.states:
|
||||
canonical_id = max(self.next_id, max(self.states, default=0) + 1)
|
||||
self.next_id = max(self.next_id, canonical_id + 1)
|
||||
state = _TrackState(
|
||||
canonical_id=canonical_id,
|
||||
source_ids={source_id},
|
||||
label=str(source["label"]),
|
||||
group=str(source.get("association_group", source["label"])),
|
||||
center=center.copy(),
|
||||
size=size.copy(),
|
||||
raw_center=center.copy(),
|
||||
velocity=np.zeros(2, dtype=np.float64),
|
||||
last_frame=frame_index,
|
||||
last_seconds=0.0,
|
||||
last_observed_frame=frame_index,
|
||||
score=float(source["score"]),
|
||||
template=json.loads(json.dumps(source)),
|
||||
)
|
||||
self.states[canonical_id] = state
|
||||
self.aliases[source_id] = canonical_id
|
||||
return state, False
|
||||
|
||||
def _observe(
|
||||
self,
|
||||
state: _TrackState,
|
||||
source: dict[str, Any],
|
||||
*,
|
||||
frame_index: int,
|
||||
session_seconds: float,
|
||||
stitched: bool,
|
||||
) -> dict[str, Any]:
|
||||
bbox = np.asarray(source["bbox_xyxy"], dtype=np.float64)
|
||||
observed_center, observed_size = _center_size(bbox)
|
||||
gap = max(1, frame_index - state.last_observed_frame)
|
||||
config = self.profile["tracking_2d"]
|
||||
predicted = state.center + state.velocity * gap
|
||||
scale = max(1.0, math.sqrt(float(np.prod(np.maximum(state.size, 1.0)))))
|
||||
innovation = float(np.linalg.norm(observed_center - predicted) / scale)
|
||||
if innovation > float(config["maximum_normalized_innovation"]):
|
||||
state.center = observed_center
|
||||
state.size = observed_size
|
||||
state.velocity.fill(0.0)
|
||||
status = "reset-large-innovation"
|
||||
self.reset_2d += 1
|
||||
else:
|
||||
blend = _adaptive(
|
||||
innovation,
|
||||
float(config["adaptive_innovation_low"]),
|
||||
float(config["adaptive_innovation_high"]),
|
||||
)
|
||||
center_alpha = _lerp(
|
||||
float(config["center_alpha_low"]),
|
||||
float(config["center_alpha_high"]),
|
||||
blend,
|
||||
)
|
||||
size_alpha = _lerp(
|
||||
float(config["size_alpha_low"]),
|
||||
float(config["size_alpha_high"]),
|
||||
blend,
|
||||
)
|
||||
state.center = predicted + center_alpha * (observed_center - predicted)
|
||||
state.size = state.size + size_alpha * (observed_size - state.size)
|
||||
observed_velocity = (observed_center - state.raw_center) / gap
|
||||
velocity_alpha = float(config["velocity_alpha"])
|
||||
state.velocity = (
|
||||
1.0 - velocity_alpha
|
||||
) * state.velocity + velocity_alpha * observed_velocity
|
||||
status = "stitched-observed" if stitched else "observed"
|
||||
state.raw_center = observed_center
|
||||
state.last_frame = frame_index
|
||||
state.last_observed_frame = frame_index
|
||||
state.last_seconds = session_seconds
|
||||
state.score = float(source["score"])
|
||||
state.template = json.loads(json.dumps(source))
|
||||
|
||||
normalized = json.loads(json.dumps(source))
|
||||
source_id = int(source["track_id"])
|
||||
normalized["track_id"] = state.canonical_id
|
||||
normalized["temporal_source_track_id"] = source_id
|
||||
normalized["bbox_xyxy"] = _box(state.center, state.size)
|
||||
normalized["temporal_2d_status"] = status
|
||||
self._observe_or_hold_cuboid(normalized, state, session_seconds)
|
||||
state.template = json.loads(json.dumps(normalized))
|
||||
return normalized
|
||||
|
||||
def _observe_or_hold_cuboid(
|
||||
self,
|
||||
item: dict[str, Any],
|
||||
state: _TrackState,
|
||||
session_seconds: float,
|
||||
) -> None:
|
||||
if str(item.get("cuboid_status", "")).startswith("accepted-"):
|
||||
self._observe_cuboid(item, state, session_seconds)
|
||||
else:
|
||||
self._hold_cuboid(item, state, session_seconds)
|
||||
|
||||
def _observe_cuboid(
|
||||
self,
|
||||
item: dict[str, Any],
|
||||
state: _TrackState,
|
||||
session_seconds: float,
|
||||
) -> None:
|
||||
center = np.asarray(item["cuboid_center_map"], dtype=np.float64)
|
||||
size = np.asarray(item["cuboid_half_size"], dtype=np.float64)
|
||||
yaw = _yaw(np.asarray(item["cuboid_quaternion_xyzw"], dtype=np.float64))
|
||||
config = self.profile["cuboids_3d"]
|
||||
status = "observed"
|
||||
if (
|
||||
state.cuboid_center is not None
|
||||
and state.cuboid_size is not None
|
||||
and state.cuboid_yaw is not None
|
||||
and state.cuboid_seconds is not None
|
||||
):
|
||||
dt = max(1e-3, session_seconds - state.cuboid_seconds)
|
||||
velocity = (
|
||||
np.zeros(3, dtype=np.float64)
|
||||
if state.cuboid_velocity is None
|
||||
else state.cuboid_velocity
|
||||
)
|
||||
predicted = state.cuboid_center + velocity * dt
|
||||
innovation = float(np.linalg.norm(center - predicted))
|
||||
yaw_delta = _yaw_delta(yaw, state.cuboid_yaw)
|
||||
if innovation > float(config["maximum_center_innovation_m"]) or abs(
|
||||
math.degrees(yaw_delta)
|
||||
) > float(config["maximum_yaw_innovation_degrees"]):
|
||||
status = "reset-large-innovation"
|
||||
self.reset_3d += 1
|
||||
velocity = np.zeros(3, dtype=np.float64)
|
||||
else:
|
||||
raw_velocity = (center - state.cuboid_center) / dt
|
||||
velocity_alpha = float(config["velocity_alpha"])
|
||||
velocity = (1.0 - velocity_alpha) * velocity + velocity_alpha * raw_velocity
|
||||
center = predicted + float(config["center_alpha"]) * (center - predicted)
|
||||
size = state.cuboid_size + float(config["size_alpha"]) * (size - state.cuboid_size)
|
||||
yaw = state.cuboid_yaw + float(config["yaw_alpha"]) * yaw_delta
|
||||
state.cuboid_velocity = velocity
|
||||
else:
|
||||
state.cuboid_velocity = np.zeros(3, dtype=np.float64)
|
||||
state.cuboid_center = center
|
||||
state.cuboid_size = size
|
||||
state.cuboid_yaw = yaw
|
||||
state.cuboid_seconds = session_seconds
|
||||
distance = item.get("distance_smoothed_m")
|
||||
if isinstance(distance, int | float) and not isinstance(distance, bool):
|
||||
state.distance_m = float(distance)
|
||||
item["cuboid_center_map"] = center.tolist()
|
||||
item["cuboid_half_size"] = size.tolist()
|
||||
item["cuboid_quaternion_xyzw"] = _quaternion(yaw)
|
||||
item["cuboid_status"] = "accepted-temporally-stabilized-e23-v1"
|
||||
item["temporal_status"] = f"e23-{status}"
|
||||
|
||||
def _hold_cuboid(
|
||||
self,
|
||||
item: dict[str, Any],
|
||||
state: _TrackState,
|
||||
session_seconds: float,
|
||||
) -> bool:
|
||||
if (
|
||||
state.cuboid_center is None
|
||||
or state.cuboid_size is None
|
||||
or state.cuboid_yaw is None
|
||||
or state.cuboid_seconds is None
|
||||
or session_seconds - state.cuboid_seconds
|
||||
> float(self.profile["cuboids_3d"]["hold_seconds"])
|
||||
):
|
||||
return False
|
||||
age_ms = max(0.0, (session_seconds - state.cuboid_seconds) * 1000.0)
|
||||
item["cuboid_center_map"] = state.cuboid_center.tolist()
|
||||
item["cuboid_half_size"] = state.cuboid_size.tolist()
|
||||
item["cuboid_quaternion_xyzw"] = _quaternion(state.cuboid_yaw)
|
||||
item["cuboid_status"] = "accepted-temporal-hold-e23-v1"
|
||||
item["geometry"] = "temporally-held-last-supported-cuboid"
|
||||
item["distance_smoothed_m"] = state.distance_m
|
||||
item["temporal_status"] = f"e23-held-{age_ms:.0f}ms"
|
||||
item["observed_cuboid_center_map"] = None
|
||||
item["observed_cuboid_half_size"] = None
|
||||
item["observed_cuboid_quaternion_xyzw"] = None
|
||||
self.held_3d += 1
|
||||
return True
|
||||
|
||||
def _prune(self, now: float) -> None:
|
||||
maximum_idle = float(self.profile["bounds"]["maximum_track_idle_seconds"])
|
||||
stale = [
|
||||
key
|
||||
for key, state in self.states.items()
|
||||
if state.last_seconds > 0 and now - state.last_seconds > maximum_idle
|
||||
]
|
||||
for key in stale:
|
||||
state = self.states.pop(key)
|
||||
for source_id in state.source_ids:
|
||||
self.aliases.pop(source_id, None)
|
||||
maximum = int(self.profile["bounds"]["maximum_track_states"])
|
||||
if len(self.states) <= maximum:
|
||||
return
|
||||
for state in sorted(self.states.values(), key=lambda value: value.last_seconds)[
|
||||
: len(self.states) - maximum
|
||||
]:
|
||||
self.states.pop(state.canonical_id, None)
|
||||
for source_id in state.source_ids:
|
||||
self.aliases.pop(source_id, None)
|
||||
|
||||
|
||||
class StreamingSemanticStabilizer:
|
||||
"""One-mask semantic hysteresis with bounded diagnostic samples."""
|
||||
|
||||
def __init__(self, profile: dict[str, Any]) -> None:
|
||||
self.minimum_same_label_neighbors = int(profile["semantic"]["minimum_same_label_neighbors"])
|
||||
self.previous_raw: np.ndarray | None = None
|
||||
self.previous_stabilized: np.ndarray | None = None
|
||||
self.baseline_unsupported = deque(maxlen=4096)
|
||||
self.stabilized_unsupported = deque(maxlen=4096)
|
||||
self.processing_ms = deque(maxlen=4096)
|
||||
self.frames = 0
|
||||
|
||||
def update(self, mask: np.ndarray) -> np.ndarray:
|
||||
if mask.dtype != np.uint8 or mask.shape != (600, 800):
|
||||
raise RuntimeError("LAB E23 semantic mask is invalid")
|
||||
started = time.perf_counter()
|
||||
if self.previous_raw is None or self.previous_stabilized is None:
|
||||
stabilized = mask.copy()
|
||||
else:
|
||||
support = _same_label_neighbor_count(mask)
|
||||
baseline_change = mask != self.previous_raw
|
||||
unsupported_baseline = baseline_change & (support < self.minimum_same_label_neighbors)
|
||||
stabilized = mask.copy()
|
||||
hold = (mask != self.previous_stabilized) & (
|
||||
support < self.minimum_same_label_neighbors
|
||||
)
|
||||
stabilized[hold] = self.previous_stabilized[hold]
|
||||
stabilized_support = _same_label_neighbor_count(stabilized)
|
||||
unsupported_stabilized = (stabilized != self.previous_stabilized) & (
|
||||
stabilized_support < self.minimum_same_label_neighbors
|
||||
)
|
||||
self.baseline_unsupported.append(float(np.mean(unsupported_baseline)))
|
||||
self.stabilized_unsupported.append(float(np.mean(unsupported_stabilized)))
|
||||
self.previous_raw = mask.copy()
|
||||
self.previous_stabilized = stabilized
|
||||
self.frames += 1
|
||||
self.processing_ms.append((time.perf_counter() - started) * 1000.0)
|
||||
return stabilized
|
||||
|
||||
def snapshot(self) -> dict[str, Any]:
|
||||
baseline = _percentiles(self.baseline_unsupported)
|
||||
stabilized = _percentiles(self.stabilized_unsupported)
|
||||
baseline_mean = float(baseline["mean"])
|
||||
stabilized_mean = float(stabilized["mean"])
|
||||
reduction = (
|
||||
0.0
|
||||
if baseline_mean <= 0
|
||||
else max(0.0, (baseline_mean - stabilized_mean) / baseline_mean)
|
||||
)
|
||||
return {
|
||||
"frames": self.frames,
|
||||
"history_masks": int(self.previous_stabilized is not None),
|
||||
"diagnostic_sample_capacity": self.processing_ms.maxlen,
|
||||
"baseline_unsupported_change_fraction": baseline,
|
||||
"stabilized_unsupported_change_fraction": stabilized,
|
||||
"unsupported_change_reduction_fraction": reduction,
|
||||
"processing_ms": _percentiles(self.processing_ms),
|
||||
}
|
||||
|
||||
|
||||
def read_inline_profile(path: Path) -> tuple[dict[str, Any], str]:
|
||||
resolved = path.expanduser().resolve(strict=True)
|
||||
profile = json.loads(resolved.read_text(encoding="utf-8-sig"))
|
||||
if not isinstance(profile, dict):
|
||||
raise RuntimeError("LAB E23 inline temporal profile is invalid")
|
||||
source = profile.get("source")
|
||||
tracking = profile.get("tracking_2d")
|
||||
cuboids = profile.get("cuboids_3d")
|
||||
semantic = profile.get("semantic")
|
||||
bounds = profile.get("bounds")
|
||||
acceptance = profile.get("acceptance")
|
||||
authority = profile.get("authority")
|
||||
if (
|
||||
profile.get("schema_version") != PROFILE_SCHEMA
|
||||
or profile.get("mode") != "inline-shadow-qualification"
|
||||
or profile.get("stage") != "warm-worker-after-fusion-before-result-publication"
|
||||
or not all(
|
||||
isinstance(value, dict)
|
||||
for value in (
|
||||
source,
|
||||
tracking,
|
||||
cuboids,
|
||||
semantic,
|
||||
bounds,
|
||||
acceptance,
|
||||
)
|
||||
)
|
||||
or source.get("source_id") != "sensor.camera.right"
|
||||
or source.get("resolution") != [800, 600]
|
||||
or source.get("calibration_slot") != "camera_1"
|
||||
or semantic.get("mode") != "spatially-supported-streaming-hysteresis-v1"
|
||||
or int(semantic.get("class_count", 0)) != TARGET_CLASS_COUNT
|
||||
or authority != {"commands_enabled": False, "navigation_or_safety_accepted": False}
|
||||
or not 1 <= int(bounds.get("maximum_track_states", 0)) <= 512
|
||||
or int(bounds.get("semantic_history_masks", 0)) != 1
|
||||
):
|
||||
raise RuntimeError("LAB E23 inline temporal profile is invalid")
|
||||
for owner, keys in (
|
||||
(
|
||||
tracking,
|
||||
(
|
||||
"center_alpha_low",
|
||||
"center_alpha_high",
|
||||
"size_alpha_low",
|
||||
"size_alpha_high",
|
||||
"velocity_alpha",
|
||||
),
|
||||
),
|
||||
(cuboids, ("center_alpha", "size_alpha", "yaw_alpha", "velocity_alpha")),
|
||||
):
|
||||
if any(not 0 < float(owner.get(key, 0)) <= 1 for key in keys):
|
||||
raise RuntimeError("LAB E23 smoothing coefficient is invalid")
|
||||
if any(
|
||||
float(acceptance.get(key, 0)) <= 0
|
||||
for key in (
|
||||
"maximum_camera_frame_processing_p95_ms",
|
||||
"maximum_semantic_frame_processing_p95_ms",
|
||||
"maximum_rss_growth_mib",
|
||||
)
|
||||
):
|
||||
raise RuntimeError("LAB E23 acceptance contract is invalid")
|
||||
return profile, _sha256(resolved)
|
||||
|
||||
|
||||
def stabilize_world_state(
|
||||
source: dict[str, Any],
|
||||
fusion_objects: list[dict[str, Any]],
|
||||
memory: dict[int, dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
world = json.loads(json.dumps(source))
|
||||
source_objects = {
|
||||
int(item["track_id"]): item
|
||||
for item in source.get("objects", [])
|
||||
if isinstance(item, dict) and isinstance(item.get("track_id"), int)
|
||||
}
|
||||
objects: list[dict[str, Any]] = []
|
||||
active: set[int] = set()
|
||||
for fusion in fusion_objects:
|
||||
if not str(fusion.get("cuboid_status", "")).startswith("accepted-"):
|
||||
continue
|
||||
canonical = int(fusion["track_id"])
|
||||
source_id = int(fusion.get("temporal_source_track_id", canonical))
|
||||
template = source_objects.get(source_id) or memory.get(canonical) or {}
|
||||
item = json.loads(json.dumps(template))
|
||||
item.update(
|
||||
{
|
||||
"track_id": canonical,
|
||||
"class": str(fusion.get("association_group", "object")),
|
||||
"detector_label": str(fusion.get("label", "object")),
|
||||
"confidence": float(fusion.get("score", 0.0)),
|
||||
"position_map_m": fusion["cuboid_center_map"],
|
||||
"orientation_map_xyzw": fusion["cuboid_quaternion_xyzw"],
|
||||
"size_m": [2.0 * float(value) for value in fusion["cuboid_half_size"]],
|
||||
"range_m": fusion.get("distance_smoothed_m"),
|
||||
"support_points": int(fusion.get("clustered_points", 0)),
|
||||
"geometry": fusion.get("geometry"),
|
||||
"temporal_status": fusion.get("temporal_status"),
|
||||
}
|
||||
)
|
||||
if "held" in str(fusion.get("temporal_status", "")):
|
||||
item["velocity_status"] = "temporally-held-diagnostic"
|
||||
memory[canonical] = json.loads(json.dumps(item))
|
||||
active.add(canonical)
|
||||
objects.append(item)
|
||||
for canonical in set(memory) - active:
|
||||
memory.pop(canonical, None)
|
||||
world["objects"] = objects
|
||||
world["object_count"] = len(objects)
|
||||
world.setdefault("delivery", {})["temporal_stability"] = "e23-inline-bounded"
|
||||
return world
|
||||
|
||||
|
||||
def _same_label_neighbor_count(mask: np.ndarray) -> np.ndarray:
|
||||
padded = np.pad(mask, 1, mode="edge")
|
||||
count = np.zeros(mask.shape, dtype=np.uint8)
|
||||
for y in range(3):
|
||||
for x in range(3):
|
||||
if y == 1 and x == 1:
|
||||
continue
|
||||
count += padded[y : y + mask.shape[0], x : x + mask.shape[1]] == mask
|
||||
return count
|
||||
|
||||
|
||||
def _center_size(box: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
||||
return (box[:2] + box[2:]) * 0.5, np.maximum(box[2:] - box[:2], 1.0)
|
||||
|
||||
|
||||
def _box(center: np.ndarray, size: np.ndarray) -> list[float]:
|
||||
half = np.maximum(size, 1.0) * 0.5
|
||||
values = np.concatenate((center - half, center + half))
|
||||
values[[0, 2]] = np.clip(values[[0, 2]], 0.0, 799.0)
|
||||
values[[1, 3]] = np.clip(values[[1, 3]], 0.0, 599.0)
|
||||
return [round(float(value), 6) for value in values]
|
||||
|
||||
|
||||
def _iou(left: np.ndarray, right: np.ndarray) -> float:
|
||||
intersection_min = np.maximum(left[:2], right[:2])
|
||||
intersection_max = np.minimum(left[2:], right[2:])
|
||||
intersection_size = np.maximum(0.0, intersection_max - intersection_min)
|
||||
intersection = float(np.prod(intersection_size))
|
||||
left_area = float(np.prod(np.maximum(0.0, left[2:] - left[:2])))
|
||||
right_area = float(np.prod(np.maximum(0.0, right[2:] - right[:2])))
|
||||
union = left_area + right_area - intersection
|
||||
return 0.0 if union <= 0 else intersection / union
|
||||
|
||||
|
||||
def _adaptive(value: float, lower: float, upper: float) -> float:
|
||||
if upper <= lower:
|
||||
return 1.0
|
||||
return min(1.0, max(0.0, (value - lower) / (upper - lower)))
|
||||
|
||||
|
||||
def _lerp(lower: float, upper: float, fraction: float) -> float:
|
||||
return lower + (upper - lower) * fraction
|
||||
|
||||
|
||||
def _yaw(quaternion: np.ndarray) -> float:
|
||||
x, y, z, w = quaternion
|
||||
return math.atan2(2.0 * (w * z + x * y), 1.0 - 2.0 * (y * y + z * z))
|
||||
|
||||
|
||||
def _yaw_delta(value: float, reference: float) -> float:
|
||||
return math.atan2(math.sin(value - reference), math.cos(value - reference))
|
||||
|
||||
|
||||
def _quaternion(yaw: float) -> list[float]:
|
||||
return [0.0, 0.0, math.sin(yaw * 0.5), math.cos(yaw * 0.5)]
|
||||
|
||||
|
||||
def _clear_cuboid(item: dict[str, Any], status: str) -> None:
|
||||
for key in (
|
||||
"cuboid_center_map",
|
||||
"cuboid_half_size",
|
||||
"cuboid_quaternion_xyzw",
|
||||
"observed_cuboid_center_map",
|
||||
"observed_cuboid_half_size",
|
||||
"observed_cuboid_quaternion_xyzw",
|
||||
):
|
||||
item[key] = None
|
||||
item["cuboid_status"] = status
|
||||
item["temporal_status"] = status
|
||||
|
||||
|
||||
def _percentiles(values: Any) -> dict[str, float | int]:
|
||||
samples = np.asarray(list(values), dtype=np.float64)
|
||||
if samples.size == 0:
|
||||
return {"count": 0, "mean": 0.0, "p50": 0.0, "p95": 0.0, "max": 0.0}
|
||||
return {
|
||||
"count": int(samples.size),
|
||||
"mean": round(float(np.mean(samples)), 6),
|
||||
"p50": round(float(np.percentile(samples, 50)), 6),
|
||||
"p95": round(float(np.percentile(samples, 95)), 6),
|
||||
"max": round(float(np.max(samples)), 6),
|
||||
}
|
||||
|
||||
|
||||
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()
|
||||
@@ -22,6 +22,7 @@ from k1link.sessions import (
|
||||
publish_lab_replay_cache,
|
||||
)
|
||||
|
||||
from .inline_temporal import StreamingSemanticStabilizer, read_inline_profile
|
||||
from .integrated_perception import (
|
||||
IntegratedPerceptionResult,
|
||||
validate_integrated_perception_result,
|
||||
@@ -29,6 +30,7 @@ from .integrated_perception import (
|
||||
from .jobs import CameraComputeJob, validate_camera_compute_job
|
||||
from .temporal_stability import (
|
||||
TemporalStabilityBuild,
|
||||
_quality_metrics,
|
||||
build_temporal_stability_result,
|
||||
)
|
||||
|
||||
@@ -224,9 +226,7 @@ def publish_e21_lab_instance(
|
||||
source_result_id=str(e21_document["result_id"]),
|
||||
config_sha256=str(e21_report["identity"]["profile_sha256"]),
|
||||
run_created_at_utc=str(e21_report["created_at_utc"]),
|
||||
duration_seconds=(
|
||||
validated.timeline_end_seconds - validated.timeline_start_seconds
|
||||
),
|
||||
duration_seconds=(validated.timeline_end_seconds - validated.timeline_start_seconds),
|
||||
include_recorded_media=False,
|
||||
provenance={
|
||||
"schema_version": "missioncore.e21-lab-publication/v1",
|
||||
@@ -297,20 +297,14 @@ def publish_e22_lab_instance(
|
||||
)
|
||||
if not validated.accepted:
|
||||
failed = [
|
||||
name
|
||||
for name, accepted in build.report["acceptance"]["checks"].items()
|
||||
if not accepted
|
||||
name for name, accepted in build.report["acceptance"]["checks"].items() if not accepted
|
||||
]
|
||||
raise SessionIntegrityError(
|
||||
f"E22 temporal acceptance failed: {', '.join(failed)}"
|
||||
)
|
||||
raise SessionIntegrityError(f"E22 temporal acceptance failed: {', '.join(failed)}")
|
||||
|
||||
store = SessionStore(root)
|
||||
source_lab = store.get_lab_instance(source.job.session_id)
|
||||
source_session_id = (
|
||||
source.job.session_id
|
||||
if source_lab is None
|
||||
else source_lab.source_session_id
|
||||
source.job.session_id if source_lab is None else source_lab.source_session_id
|
||||
)
|
||||
publish_lab_replay_cache(
|
||||
store.data_dir,
|
||||
@@ -330,26 +324,22 @@ def publish_e22_lab_instance(
|
||||
source_result_id=source.result_id,
|
||||
config_sha256=build.profile_sha256,
|
||||
run_created_at_utc=validated.created_at_utc,
|
||||
duration_seconds=(
|
||||
validated.timeline_end_seconds - validated.timeline_start_seconds
|
||||
),
|
||||
duration_seconds=(validated.timeline_end_seconds - validated.timeline_start_seconds),
|
||||
include_recorded_media=False,
|
||||
provenance={
|
||||
"schema_version": "missioncore.e22-lab-publication/v1",
|
||||
"storage_mode": "bounded-derived-replay-and-temporal-projection",
|
||||
"source_result_id": source.result_id,
|
||||
"source_lab_session_id": (
|
||||
None if source_lab is None else source_lab.session_id
|
||||
),
|
||||
"source_lab_session_id": (None if source_lab is None else source_lab.session_id),
|
||||
"source_payloads_mutated": False,
|
||||
"lookahead_frames": 0,
|
||||
"peak_track_states": metrics["runtime"]["peak_track_states"],
|
||||
"camera_frame_processing_p95_ms": metrics["runtime"][
|
||||
"camera_frame_processing_ms"
|
||||
]["p95"],
|
||||
"semantic_frame_processing_p95_ms": metrics["runtime"][
|
||||
"semantic_frame_processing_ms"
|
||||
]["p95"],
|
||||
"camera_frame_processing_p95_ms": metrics["runtime"]["camera_frame_processing_ms"][
|
||||
"p95"
|
||||
],
|
||||
"semantic_frame_processing_p95_ms": metrics["runtime"]["semantic_frame_processing_ms"][
|
||||
"p95"
|
||||
],
|
||||
"quality_reductions": metrics["reductions"],
|
||||
},
|
||||
)
|
||||
@@ -361,6 +351,210 @@ def publish_e22_lab_instance(
|
||||
)
|
||||
|
||||
|
||||
def publish_e23_lab_instance(
|
||||
*,
|
||||
repository_root: Path,
|
||||
reference_result_root: Path,
|
||||
worker_result_root: Path,
|
||||
source_report_path: Path,
|
||||
profile_path: Path,
|
||||
lab_session_id: str,
|
||||
lab_id: str,
|
||||
display_name: str,
|
||||
) -> PublishedIntegratedLabInstance:
|
||||
"""Publish one accepted inline-temporal 1x worker run as an exact LAB replay."""
|
||||
|
||||
root = repository_root.expanduser().resolve(strict=True)
|
||||
jobs_root = root / ".runtime" / "compute-jobs"
|
||||
results_root = root / ".runtime" / "compute-experiments" / "e10" / "worker-results"
|
||||
packs_root = root / ".runtime" / "compute-experiments" / "e10" / "lidar-packs"
|
||||
reference_path = reference_result_root.expanduser().resolve(strict=True)
|
||||
reference_document = _read_object(reference_path / "result.json", reference_path)
|
||||
reference_identity = reference_document.get("identity")
|
||||
if not isinstance(reference_identity, dict) or not isinstance(
|
||||
reference_identity.get("job_id"), str
|
||||
):
|
||||
raise SessionIntegrityError("E23 semantic reference has no job identity")
|
||||
reference = validate_integrated_perception_result(
|
||||
jobs_root / reference_identity["job_id"],
|
||||
reference_path,
|
||||
packs_root,
|
||||
)
|
||||
if not reference.accepted or reference.source_start_frame_index != 0:
|
||||
raise SessionIntegrityError("E23 reference is not an accepted zero-based run")
|
||||
|
||||
profile, profile_sha256 = read_inline_profile(profile_path)
|
||||
worker_root = worker_result_root.expanduser().resolve(strict=True)
|
||||
source_path = source_report_path.expanduser().resolve(strict=True)
|
||||
worker_document, worker_report, source_report = _validate_e23_inputs(
|
||||
worker_root,
|
||||
source_path,
|
||||
profile_sha256,
|
||||
)
|
||||
frame_count = int(source_report["events_selected"]["camera-frame"])
|
||||
if frame_count != reference.frame_count:
|
||||
raise SessionIntegrityError("E23 source and reference frame counts differ")
|
||||
|
||||
lab_job = _publish_lab_job(reference.job, jobs_root, lab_session_id)
|
||||
lab_pack = _publish_e21_pack(
|
||||
reference,
|
||||
lab_job,
|
||||
packs_root,
|
||||
lab_session_id,
|
||||
frame_count,
|
||||
visual_projection="accepted-e23-inline-envelope/v1",
|
||||
)
|
||||
lab_result, quality = _publish_e23_visual_result(
|
||||
reference=reference,
|
||||
lab_job=lab_job,
|
||||
lab_pack=lab_pack,
|
||||
results_root=results_root,
|
||||
lab_session_id=lab_session_id,
|
||||
frame_count=frame_count,
|
||||
worker_root=worker_root,
|
||||
worker_document=worker_document,
|
||||
worker_report=worker_report,
|
||||
source_report=source_report,
|
||||
profile=profile,
|
||||
profile_sha256=profile_sha256,
|
||||
)
|
||||
validated = validate_integrated_perception_result(
|
||||
lab_job.job_root,
|
||||
lab_result,
|
||||
packs_root,
|
||||
)
|
||||
if not validated.accepted or not all(quality["checks"].values()):
|
||||
failed = [name for name, accepted in quality["checks"].items() if not accepted]
|
||||
raise SessionIntegrityError(f"E23 inline temporal acceptance failed: {', '.join(failed)}")
|
||||
|
||||
store = SessionStore(root)
|
||||
source_lab = store.get_lab_instance(reference.job.session_id)
|
||||
source_session_id = (
|
||||
reference.job.session_id if source_lab is None else source_lab.source_session_id
|
||||
)
|
||||
publish_lab_replay_cache(
|
||||
store.data_dir,
|
||||
source_session_id=source_session_id,
|
||||
lab_session_id=lab_session_id,
|
||||
timeline_start_ns=round(validated.timeline_start_seconds * 1_000_000_000),
|
||||
timeline_end_ns=round(validated.timeline_end_seconds * 1_000_000_000),
|
||||
)
|
||||
temporal = worker_report["metrics"]["temporal_stability"]
|
||||
binding = store.publish_lab_instance(
|
||||
session_id=lab_session_id,
|
||||
source_session_id=source_session_id,
|
||||
display_name=display_name,
|
||||
lab_id=lab_id,
|
||||
result_kind="e23-inline-temporal-stability",
|
||||
result_id=validated.result_id,
|
||||
source_result_id=str(worker_document["result_id"]),
|
||||
config_sha256=profile_sha256,
|
||||
run_created_at_utc=str(worker_report["created_at_utc"]),
|
||||
duration_seconds=(validated.timeline_end_seconds - validated.timeline_start_seconds),
|
||||
include_recorded_media=False,
|
||||
provenance={
|
||||
"schema_version": "missioncore.e23-lab-publication/v1",
|
||||
"storage_mode": "bounded-inline-worker-result-and-immutable-source-replay",
|
||||
"worker_result_id": worker_document["result_id"],
|
||||
"source_report_sha256": _sha256(source_path),
|
||||
"reference_result_id": reference.result_id,
|
||||
"source_payloads_mutated": False,
|
||||
"lookahead_frames": 0,
|
||||
"speed": 1.0,
|
||||
"quality_reductions": quality["reductions"],
|
||||
"temporal_2d_3d_p95_ms": worker_report["metrics"]["latency_ms"]["temporal_2d_3d_ms"][
|
||||
"p95"
|
||||
],
|
||||
"semantic_temporal_p95_ms": temporal["semantic"]["processing_ms"]["p95"],
|
||||
"peak_track_states": temporal["tracking_2d_3d"]["peak_track_states"],
|
||||
"rss_growth_mib": worker_report["metrics"]["runtime_telemetry"]["rss_growth_mib"],
|
||||
},
|
||||
)
|
||||
return PublishedIntegratedLabInstance(
|
||||
binding=binding,
|
||||
job=lab_job,
|
||||
result=validated,
|
||||
)
|
||||
|
||||
|
||||
def _validate_e23_inputs(
|
||||
worker_root: Path,
|
||||
source_report_path: Path,
|
||||
profile_sha256: str,
|
||||
) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
||||
worker_document = _read_object(worker_root / "result.json", worker_root)
|
||||
worker_report = _read_object(worker_root / "run-report.json", worker_root)
|
||||
source_report = _read_object(source_report_path, source_report_path.parent)
|
||||
worker_identity = worker_document.get("identity")
|
||||
report_identity = worker_report.get("identity")
|
||||
if (
|
||||
worker_document.get("schema_version") != "missioncore.e15-shadow-inference-result/v1"
|
||||
or worker_document.get("result_id") != worker_root.name
|
||||
or worker_document.get("acceptance_state") != "accepted"
|
||||
or worker_document.get("publication_scope") != "live-shadow-diagnostic-only"
|
||||
or not isinstance(worker_identity, dict)
|
||||
or worker_identity.get("pipeline")
|
||||
!= "warm-worker-inline-bounded-temporal-2d-3d-semantic/v1"
|
||||
or worker_identity.get("profiles", {}).get("stability_sha256") != profile_sha256
|
||||
or worker_report.get("schema_version") != "missioncore.e15-shadow-inference-report/v1"
|
||||
or worker_report.get("result_id") != worker_root.name
|
||||
or worker_report.get("state") != "accepted"
|
||||
or report_identity != worker_identity
|
||||
or not all(worker_report.get("acceptance", {}).get("checks", {}).values())
|
||||
or source_report.get("schema_version") != "missioncore.e23-replay-source-report/v1"
|
||||
or source_report.get("state") != "completed"
|
||||
or source_report.get("session_id") != worker_identity.get("session_id")
|
||||
or source_report.get("source", {}).get("speed") != 1.0
|
||||
or source_report.get("authority", {}).get("mode") != "shadow-diagnostic-only"
|
||||
or source_report.get("authority", {}).get("commands_enabled") is not False
|
||||
or source_report.get("authority", {}).get("navigation_or_safety_accepted") is not False
|
||||
):
|
||||
raise SessionIntegrityError("E23 accepted worker/source identity is inconsistent")
|
||||
selected = source_report.get("events_selected")
|
||||
diagnostics = source_report.get("diagnostic_results")
|
||||
if (
|
||||
not isinstance(selected, dict)
|
||||
or selected.get("camera-frame") != 601
|
||||
or selected.get("lidar") != 585
|
||||
or selected.get("pose") != 600
|
||||
or not isinstance(diagnostics, dict)
|
||||
or int(diagnostics.get("received", 0)) < 590
|
||||
):
|
||||
raise SessionIntegrityError("E23 source replay coverage is incomplete")
|
||||
|
||||
required = {
|
||||
"e15-semantic-frames": "semantic-frames.jsonl",
|
||||
"e23-raw-fusion-frames": "raw-fusion-frames.jsonl",
|
||||
"e15-fusion-frames": "fusion-frames.jsonl",
|
||||
"e15-world-state": "world-state.jsonl",
|
||||
"worker-gpu-telemetry": "gpu-telemetry.jsonl",
|
||||
"worker-runtime-telemetry": "runtime-telemetry.jsonl",
|
||||
"e15-run-report": "run-report.json",
|
||||
}
|
||||
artifacts = worker_document.get("artifacts")
|
||||
descriptors = (
|
||||
{
|
||||
value.get("kind"): value
|
||||
for value in artifacts
|
||||
if isinstance(value, dict) and value.get("kind") in required
|
||||
}
|
||||
if isinstance(artifacts, list)
|
||||
else {}
|
||||
)
|
||||
if set(descriptors) != set(required):
|
||||
raise SessionIntegrityError("E23 worker artifacts are incomplete")
|
||||
for kind, name in required.items():
|
||||
descriptor = descriptors[kind]
|
||||
path = worker_root / name
|
||||
if (
|
||||
descriptor.get("path") != name
|
||||
or descriptor.get("byte_length") != path.stat().st_size
|
||||
or descriptor.get("sha256") != _sha256(path)
|
||||
):
|
||||
raise SessionIntegrityError("E23 worker artifact identity changed")
|
||||
return worker_document, worker_report, source_report
|
||||
|
||||
|
||||
def _validate_e21_inputs(
|
||||
e21_root: Path,
|
||||
worker_root: Path,
|
||||
@@ -426,6 +620,8 @@ def _publish_e21_pack(
|
||||
packs_root: Path,
|
||||
lab_session_id: str,
|
||||
frame_count: int,
|
||||
*,
|
||||
visual_projection: str = "accepted-e21-envelope/v1",
|
||||
) -> Path:
|
||||
source_manifest = _read_object(reference.pack_root / "manifest.json", reference.pack_root)
|
||||
with np.load(reference.pack_root / "lidar-pack.npz", allow_pickle=False) as arrays:
|
||||
@@ -438,9 +634,9 @@ def _publish_e21_pack(
|
||||
"cloud_offsets": arrays["cloud_offsets"][: frame_count + 1].copy(),
|
||||
"cloud_points_map": arrays["cloud_points_map"][:cloud_end].copy(),
|
||||
"pose_positions_map": arrays["pose_positions_map"][:frame_count].copy(),
|
||||
"pose_quaternions_map_from_lidar": arrays[
|
||||
"pose_quaternions_map_from_lidar"
|
||||
][:frame_count].copy(),
|
||||
"pose_quaternions_map_from_lidar": arrays["pose_quaternions_map_from_lidar"][
|
||||
:frame_count
|
||||
].copy(),
|
||||
"lidar_camera_delta_ms": arrays["lidar_camera_delta_ms"][:frame_count].copy(),
|
||||
"pose_point_delta_ms": arrays["pose_point_delta_ms"][:frame_count].copy(),
|
||||
"intrinsic_fx_fy_cx_cy": arrays["intrinsic_fx_fy_cx_cy"].copy(),
|
||||
@@ -460,7 +656,7 @@ def _publish_e21_pack(
|
||||
"point_count": int(payload["cloud_points_map"].shape[0]),
|
||||
"timeline_start_seconds": float(payload["session_seconds"][0]),
|
||||
"timeline_end_seconds": float(payload["session_seconds"][-1]),
|
||||
"visual_projection": "accepted-e21-envelope/v1",
|
||||
"visual_projection": visual_projection,
|
||||
}
|
||||
)
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
@@ -580,9 +776,7 @@ def _publish_e21_visual_result(
|
||||
)
|
||||
fusion_rows.append(normalized_fusion)
|
||||
world_rows.append(normalized_world)
|
||||
expected_drops = int(
|
||||
e21_report["metrics"]["worker"]["detector"]["queue"]["dropped_overflow"]
|
||||
)
|
||||
expected_drops = int(e21_report["metrics"]["worker"]["detector"]["queue"]["dropped_overflow"])
|
||||
if len(dropped_indices) != expected_drops:
|
||||
raise SessionIntegrityError("E21 detector replacement accounting changed")
|
||||
|
||||
@@ -762,6 +956,329 @@ def _publish_e21_visual_result(
|
||||
return destination
|
||||
|
||||
|
||||
def _publish_e23_visual_result(
|
||||
*,
|
||||
reference: IntegratedPerceptionResult,
|
||||
lab_job: CameraComputeJob,
|
||||
lab_pack: Path,
|
||||
results_root: Path,
|
||||
lab_session_id: str,
|
||||
frame_count: int,
|
||||
worker_root: Path,
|
||||
worker_document: dict[str, Any],
|
||||
worker_report: dict[str, Any],
|
||||
source_report: dict[str, Any],
|
||||
profile: dict[str, Any],
|
||||
profile_sha256: str,
|
||||
) -> tuple[Path, dict[str, Any]]:
|
||||
with np.load(reference.arrays_path, allow_pickle=False) as arrays:
|
||||
frame_times = arrays["frame_times_ns"][:frame_count].copy()
|
||||
reference_semantic_indices = arrays["semantic_frame_indices"]
|
||||
selected = reference_semantic_indices < frame_count
|
||||
reference_indices = reference_semantic_indices[selected].copy()
|
||||
reference_masks = arrays["semantic_masks"][selected].copy()
|
||||
|
||||
semantic_rows = _read_jsonl(worker_root / "semantic-frames.jsonl")
|
||||
if [row.get("frame_index") for row in semantic_rows] != reference_indices.tolist():
|
||||
raise SessionIntegrityError("E23 semantic frame schedule changed")
|
||||
semantic_stabilizer = StreamingSemanticStabilizer(profile)
|
||||
stabilized_masks = np.stack(
|
||||
[semantic_stabilizer.update(mask) for mask in reference_masks]
|
||||
).astype(np.uint8, copy=False)
|
||||
for row, mask in zip(semantic_rows, stabilized_masks, strict=True):
|
||||
if row.get("mask_sha256") != hashlib.sha256(mask.tobytes()).hexdigest():
|
||||
raise SessionIntegrityError("E23 semantic mask does not match inline reconstruction")
|
||||
row["schema_version"] = "missioncore.e10-semantic-frame/v1"
|
||||
row["session_seconds"] = float(frame_times[int(row["frame_index"])]) / 1_000_000_000
|
||||
row["temporal_status"] = "e23-inline-spatially-supported-hysteresis"
|
||||
|
||||
raw_worker_rows = _read_jsonl(worker_root / "raw-fusion-frames.jsonl")
|
||||
stable_worker_rows = _read_jsonl(worker_root / "fusion-frames.jsonl")
|
||||
fusion_source = {int(row["source_frame_index"]): row for row in stable_worker_rows}
|
||||
world_source = {
|
||||
int(row["source_frame_index"]): row
|
||||
for row in _read_jsonl(worker_root / "world-state.jsonl")
|
||||
}
|
||||
if set(fusion_source) != set(world_source):
|
||||
raise SessionIntegrityError("E23 fusion and world timelines differ")
|
||||
fusion_rows: list[dict[str, Any]] = []
|
||||
world_rows: list[dict[str, Any]] = []
|
||||
dropped_indices: list[int] = []
|
||||
for index in range(frame_count):
|
||||
session_seconds = float(frame_times[index]) / 1_000_000_000
|
||||
fusion = fusion_source.get(index)
|
||||
world = world_source.get(index)
|
||||
if fusion is None or world is None:
|
||||
dropped_indices.append(index)
|
||||
fusion_rows.append(
|
||||
{
|
||||
"schema_version": "missioncore.e10-fusion-frame/v1",
|
||||
"frame_index": index,
|
||||
"source_frame_index": index,
|
||||
"session_seconds": session_seconds,
|
||||
"fusion_state": "detector-dropped-latest-wins",
|
||||
"semantic_source_frame_index": None,
|
||||
"semantic_status": "unavailable",
|
||||
"objects": [],
|
||||
}
|
||||
)
|
||||
world_rows.append(_dropped_world_row(index, session_seconds))
|
||||
continue
|
||||
normalized_fusion = json.loads(json.dumps(fusion))
|
||||
normalized_fusion.update(
|
||||
{
|
||||
"schema_version": "missioncore.e10-fusion-frame/v1",
|
||||
"frame_index": index,
|
||||
"source_frame_index": index,
|
||||
"session_seconds": session_seconds,
|
||||
}
|
||||
)
|
||||
normalized_world = json.loads(json.dumps(world))
|
||||
normalized_world.update(
|
||||
{
|
||||
"frame_index": index,
|
||||
"source_frame_index": index,
|
||||
"session_seconds": session_seconds,
|
||||
}
|
||||
)
|
||||
fusion_rows.append(normalized_fusion)
|
||||
world_rows.append(normalized_world)
|
||||
expected_drops = int(worker_report["metrics"]["detector"]["queue"]["dropped_overflow"])
|
||||
if len(dropped_indices) != expected_drops:
|
||||
raise SessionIntegrityError("E23 detector replacement accounting changed")
|
||||
|
||||
baseline = _quality_metrics(raw_worker_rows, reference_masks)
|
||||
stabilized = _quality_metrics(stable_worker_rows, stabilized_masks)
|
||||
reductions = {
|
||||
"tracking_2d_acceleration_p95_fraction": _fraction_reduction(
|
||||
baseline["tracking_2d"]["normalized_acceleration"]["p95"],
|
||||
stabilized["tracking_2d"]["normalized_acceleration"]["p95"],
|
||||
),
|
||||
"tracking_2d_size_step_p95_fraction": _fraction_reduction(
|
||||
baseline["tracking_2d"]["normalized_size_step"]["p95"],
|
||||
stabilized["tracking_2d"]["normalized_size_step"]["p95"],
|
||||
),
|
||||
"cuboid_center_step_p95_fraction": _fraction_reduction(
|
||||
baseline["cuboids_3d"]["center_step_m"]["p95"],
|
||||
stabilized["cuboids_3d"]["center_step_m"]["p95"],
|
||||
),
|
||||
"cuboid_size_step_p95_fraction": _fraction_reduction(
|
||||
baseline["cuboids_3d"]["half_size_step_m"]["p95"],
|
||||
stabilized["cuboids_3d"]["half_size_step_m"]["p95"],
|
||||
),
|
||||
"cuboid_yaw_step_p95_fraction": _fraction_reduction(
|
||||
baseline["cuboids_3d"]["yaw_step_degrees"]["p95"],
|
||||
stabilized["cuboids_3d"]["yaw_step_degrees"]["p95"],
|
||||
),
|
||||
"semantic_unsupported_change_fraction": float(
|
||||
worker_report["metrics"]["temporal_stability"]["semantic"][
|
||||
"unsupported_change_reduction_fraction"
|
||||
]
|
||||
),
|
||||
}
|
||||
temporal = worker_report["metrics"]["temporal_stability"]
|
||||
acceptance = profile["acceptance"]
|
||||
quality_checks = {
|
||||
"worker_runtime_accepted": worker_report["state"] == "accepted"
|
||||
and all(worker_report["acceptance"]["checks"].values()),
|
||||
"source_is_complete_1x": source_report["state"] == "completed"
|
||||
and source_report["source"]["speed"] == 1.0,
|
||||
"minimum_2d_acceleration_reduction": reductions["tracking_2d_acceleration_p95_fraction"]
|
||||
>= float(acceptance["minimum_2d_acceleration_p95_reduction_fraction"]),
|
||||
"minimum_3d_center_reduction": reductions["cuboid_center_step_p95_fraction"]
|
||||
>= float(acceptance["minimum_3d_center_step_p95_reduction_fraction"]),
|
||||
"minimum_3d_yaw_reduction": reductions["cuboid_yaw_step_p95_fraction"]
|
||||
>= float(acceptance["minimum_3d_yaw_step_p95_reduction_fraction"]),
|
||||
"minimum_semantic_unsupported_change_reduction": reductions[
|
||||
"semantic_unsupported_change_fraction"
|
||||
]
|
||||
>= float(acceptance["minimum_semantic_unsupported_change_reduction_fraction"]),
|
||||
"maximum_camera_frame_processing_p95": float(
|
||||
worker_report["metrics"]["latency_ms"]["temporal_2d_3d_ms"]["p95"]
|
||||
)
|
||||
<= float(acceptance["maximum_camera_frame_processing_p95_ms"]),
|
||||
"maximum_semantic_frame_processing_p95": float(temporal["semantic"]["processing_ms"]["p95"])
|
||||
<= float(acceptance["maximum_semantic_frame_processing_p95_ms"]),
|
||||
"maximum_track_states": int(temporal["tracking_2d_3d"]["peak_track_states"])
|
||||
<= int(acceptance["maximum_track_states_observed"]),
|
||||
"maximum_rss_growth": float(worker_report["metrics"]["runtime_telemetry"]["rss_growth_mib"])
|
||||
<= float(acceptance["maximum_rss_growth_mib"]),
|
||||
}
|
||||
quality = {
|
||||
"schema_version": "missioncore.e23-inline-quality/v1",
|
||||
"baseline": baseline,
|
||||
"stabilized": stabilized,
|
||||
"reductions": reductions,
|
||||
"checks": quality_checks,
|
||||
}
|
||||
if not all(quality_checks.values()):
|
||||
failed = [name for name, accepted in quality_checks.items() if not accepted]
|
||||
raise SessionIntegrityError(f"E23 inline temporal quality failed: {', '.join(failed)}")
|
||||
|
||||
box_offsets = [0]
|
||||
centers: list[list[float]] = []
|
||||
half_sizes: list[list[float]] = []
|
||||
quaternions: list[list[float]] = []
|
||||
colors: list[list[int]] = []
|
||||
for row in fusion_rows:
|
||||
for item in row["objects"]:
|
||||
if not str(item.get("cuboid_status", "")).startswith("accepted-"):
|
||||
continue
|
||||
centers.append(item["cuboid_center_map"])
|
||||
half_sizes.append(item["cuboid_half_size"])
|
||||
quaternions.append(item["cuboid_quaternion_xyzw"])
|
||||
colors.append(_cuboid_color(item))
|
||||
box_offsets.append(len(centers))
|
||||
|
||||
worker_identity = worker_document["identity"]
|
||||
configuration = {
|
||||
"pipeline": "e23-inline-temporal-envelope-visual-projection/v1",
|
||||
"profile_sha256": profile_sha256,
|
||||
"profile": profile,
|
||||
"worker_result_id": worker_document["result_id"],
|
||||
"source_report": {
|
||||
"schema_version": source_report["schema_version"],
|
||||
"session_id": source_report["session_id"],
|
||||
"speed": source_report["source"]["speed"],
|
||||
},
|
||||
"semantic_mask_materialization": {
|
||||
"mode": "inline-reconstruction-from-immutable-reference-exact-sha256",
|
||||
"reference_result_id": reference.result_id,
|
||||
"matched_masks": len(semantic_rows),
|
||||
},
|
||||
"quality": quality,
|
||||
}
|
||||
selection = {
|
||||
"frame_count": frame_count,
|
||||
"source_start_frame_index": 0,
|
||||
"source_end_frame_index": frame_count - 1,
|
||||
"timeline_start_seconds": float(frame_times[0]) / 1_000_000_000,
|
||||
"timeline_end_seconds": float(frame_times[-1]) / 1_000_000_000,
|
||||
"timeline_sha256": hashlib.sha256(frame_times.tobytes()).hexdigest(),
|
||||
}
|
||||
identity = {
|
||||
"schema_version": "missioncore.e10-integrated-perception-identity/v1",
|
||||
"job_id": lab_job.job_id,
|
||||
"input_sha256": lab_job.input_sha256,
|
||||
"session_id": lab_session_id,
|
||||
"source_id": lab_job.source_id,
|
||||
"lidar_pack_id": lab_pack.name,
|
||||
"selection": selection,
|
||||
"configuration": configuration,
|
||||
"models": worker_identity["models"],
|
||||
}
|
||||
identity_sha256 = hashlib.sha256(_canonical_json(identity)).hexdigest()
|
||||
result_id = f"e10-integrated-perception-{identity_sha256}"
|
||||
destination = results_root / result_id
|
||||
if destination.exists():
|
||||
existing = _read_object(destination / "result.json", destination)
|
||||
if existing.get("identity") != identity:
|
||||
raise SessionIntegrityError("E23 LAB visual result id collides")
|
||||
return destination, quality
|
||||
|
||||
staging = _staging_directory(results_root, result_id)
|
||||
try:
|
||||
semantic_path = staging / "semantic-frames.jsonl"
|
||||
fusion_path = staging / "fusion-frames.jsonl"
|
||||
world_path = staging / "world-state.jsonl"
|
||||
arrays_path = staging / "transient-perception.npz"
|
||||
gpu_path = staging / "gpu-telemetry.jsonl"
|
||||
report_path = staging / "run-report.json"
|
||||
_write_jsonl(semantic_path, semantic_rows)
|
||||
_write_jsonl(fusion_path, fusion_rows)
|
||||
_write_jsonl(world_path, world_rows)
|
||||
np.savez_compressed(
|
||||
arrays_path,
|
||||
frame_times_ns=frame_times.astype(np.int64, copy=False),
|
||||
semantic_frame_indices=reference_indices.astype(np.int64, copy=False),
|
||||
semantic_masks=stabilized_masks.astype(np.uint8, copy=False),
|
||||
support_offsets=np.zeros(frame_count + 1, dtype=np.int64),
|
||||
support_points=np.empty((0, 3), dtype=np.float32),
|
||||
support_colors=np.empty((0, 3), dtype=np.uint8),
|
||||
box_offsets=np.asarray(box_offsets, dtype=np.int64),
|
||||
box_centers=np.asarray(centers, dtype=np.float32).reshape((-1, 3)),
|
||||
box_half_sizes=np.asarray(half_sizes, dtype=np.float32).reshape((-1, 3)),
|
||||
box_quaternions=np.asarray(quaternions, dtype=np.float32).reshape((-1, 4)),
|
||||
box_colors=np.asarray(colors, dtype=np.uint8).reshape((-1, 4)),
|
||||
)
|
||||
shutil.copyfile(worker_root / "gpu-telemetry.jsonl", gpu_path)
|
||||
report = {
|
||||
"schema_version": "missioncore.e10-integrated-perception-report/v1",
|
||||
"result_id": result_id,
|
||||
"created_at_utc": worker_report["created_at_utc"],
|
||||
"state": "accepted",
|
||||
"ground_truth": False,
|
||||
"identity": identity,
|
||||
"acceptance": {
|
||||
"accepted": True,
|
||||
"navigation_or_safety_accepted": False,
|
||||
"checks": quality_checks,
|
||||
},
|
||||
"metrics": {
|
||||
**worker_report["metrics"],
|
||||
"quality": quality,
|
||||
"visual_projection": {
|
||||
"frames": frame_count,
|
||||
"semantic_masks": len(semantic_rows),
|
||||
"detector_replacement_frames": dropped_indices,
|
||||
"accepted_cuboids": len(centers),
|
||||
},
|
||||
},
|
||||
"runtime": worker_report.get("runtime", {}),
|
||||
"limitations": [
|
||||
"This is the accepted E23 recorded 1x inline worker gate, not a physical K1 run.",
|
||||
"Latest-wins detector replacements are explicit empty visual frames.",
|
||||
"Semantic pixels are reconstructed only after exact inline SHA-256 matches.",
|
||||
"LiDAR support points remain in the immutable source scene and are not duplicated.",
|
||||
"Navigation and safety authority remain disabled.",
|
||||
],
|
||||
}
|
||||
write_json_atomic(report_path, report)
|
||||
artifacts = [
|
||||
_artifact_descriptor(
|
||||
"e10-semantic-frames",
|
||||
semantic_path,
|
||||
"missioncore.e10-semantic-frame/v1",
|
||||
),
|
||||
_artifact_descriptor(
|
||||
"e10-fusion-frames",
|
||||
fusion_path,
|
||||
"missioncore.e10-fusion-frame/v1",
|
||||
),
|
||||
_artifact_descriptor(
|
||||
"e10-world-state",
|
||||
world_path,
|
||||
"missioncore.live-perception-world-state/v1",
|
||||
),
|
||||
_artifact_descriptor("e10-transient-perception", arrays_path, None),
|
||||
_artifact_descriptor("worker-gpu-telemetry", gpu_path, None),
|
||||
_artifact_descriptor(
|
||||
"e10-run-report",
|
||||
report_path,
|
||||
"missioncore.e10-integrated-perception-report/v1",
|
||||
),
|
||||
]
|
||||
write_json_atomic(
|
||||
staging / "result.json",
|
||||
{
|
||||
"schema_version": "missioncore.e10-integrated-perception-result/v1",
|
||||
"result_id": result_id,
|
||||
"identity_sha256": identity_sha256,
|
||||
"identity": identity,
|
||||
"created_at_utc": worker_report["created_at_utc"],
|
||||
"ground_truth": False,
|
||||
"publication_scope": "recorded-integrated-realtime-qualification-only",
|
||||
"acceptance_state": "accepted",
|
||||
"frames_processed": frame_count,
|
||||
"artifacts": artifacts,
|
||||
},
|
||||
)
|
||||
_publish_directory(staging, destination)
|
||||
finally:
|
||||
_remove_staging(staging)
|
||||
return destination, quality
|
||||
|
||||
|
||||
def _dropped_world_row(frame_index: int, session_seconds: float) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": "missioncore.live-perception-world-state/v1",
|
||||
@@ -799,6 +1316,13 @@ def _cuboid_color(item: dict[str, Any]) -> list[int]:
|
||||
return [64 + digest[0] % 176, 64 + digest[1] % 176, 64 + digest[2] % 176, 88]
|
||||
|
||||
|
||||
def _fraction_reduction(baseline: int | float, stabilized: int | float) -> float:
|
||||
baseline_value = float(baseline)
|
||||
if baseline_value <= 0:
|
||||
return 0.0
|
||||
return (baseline_value - float(stabilized)) / baseline_value
|
||||
|
||||
|
||||
def _read_jsonl(path: Path) -> list[dict[str, Any]]:
|
||||
rows: list[dict[str, Any]] = []
|
||||
with path.open(encoding="utf-8") as stream:
|
||||
|
||||
@@ -21,6 +21,7 @@ from k1link.compute import (
|
||||
prepare_camera_compute_job,
|
||||
publish_e21_lab_instance,
|
||||
publish_e22_lab_instance,
|
||||
publish_e23_lab_instance,
|
||||
publish_integrated_lab_instance,
|
||||
)
|
||||
from k1link.device_plugins.xgrids_k1.analyze import (
|
||||
@@ -497,6 +498,89 @@ def publish_e22_lab(
|
||||
)
|
||||
|
||||
|
||||
@lab_app.command("publish-e23")
|
||||
def publish_e23_lab(
|
||||
reference: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Accepted zero-based E10 result containing exact source masks.",
|
||||
),
|
||||
],
|
||||
worker_result: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
"--worker-result",
|
||||
exists=True,
|
||||
file_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Accepted E23 inline-temporal worker result.",
|
||||
),
|
||||
],
|
||||
source_report: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
"--source-report",
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Exact E23 1x replay source report.",
|
||||
),
|
||||
],
|
||||
profile: Annotated[
|
||||
Path,
|
||||
typer.Option(
|
||||
exists=True,
|
||||
dir_okay=False,
|
||||
readable=True,
|
||||
resolve_path=True,
|
||||
help="Bounded E23 inline-temporal profile.",
|
||||
),
|
||||
],
|
||||
session_id: Annotated[
|
||||
str,
|
||||
typer.Option("--session-id", help="New immutable LAB session id."),
|
||||
],
|
||||
lab_id: Annotated[
|
||||
str,
|
||||
typer.Option("--lab-id", help="LAB marker, for example 'LAB E23.2'."),
|
||||
],
|
||||
display_name: Annotated[
|
||||
str,
|
||||
typer.Option("--display-name", help="Operator-facing saved-session title."),
|
||||
],
|
||||
) -> None:
|
||||
"""Publish an accepted inline-temporal 1x run as an immutable LAB replay."""
|
||||
|
||||
repository_root = Path(__file__).resolve().parents[4]
|
||||
try:
|
||||
published = publish_e23_lab_instance(
|
||||
repository_root=repository_root,
|
||||
reference_result_root=reference,
|
||||
worker_result_root=worker_result,
|
||||
source_report_path=source_report,
|
||||
profile_path=profile,
|
||||
lab_session_id=session_id,
|
||||
lab_id=lab_id,
|
||||
display_name=display_name,
|
||||
)
|
||||
except (OSError, SessionIntegrityError, RuntimeError, ValueError) as exc:
|
||||
console.print(f"[red]E23 LAB publication failed:[/red] {exc}")
|
||||
raise typer.Exit(code=2) from exc
|
||||
console.print(
|
||||
"[green]E23 LAB instance published.[/green] "
|
||||
f"session={published.binding.session_id}; "
|
||||
f"source={published.binding.source_session_id}; "
|
||||
f"result={published.binding.result_id}; "
|
||||
"speed=1.0; source_payloads_mutated=false"
|
||||
)
|
||||
|
||||
|
||||
@app.command("serve")
|
||||
def serve_console(
|
||||
port: Annotated[
|
||||
|
||||
Reference in New Issue
Block a user