feat(perception): add diagnostic semantic SLAM replay

This commit is contained in:
DCCONSTRUCTIONS
2026-08-06 11:26:37 +03:00
parent b7a51e26e6
commit 8eaa3ab497
15 changed files with 4554 additions and 6 deletions
@@ -0,0 +1,10 @@
{
"schema_version": "missioncore.laboratory-evidence-definition/v1",
"work_id": "e47-semantic-slam-shadow",
"evidence": {
"runtime_relative_root": "e47/semantic-slam-results",
"result_id_prefix": "e47-semantic-slam",
"document_name": "manifest.json",
"schema_version": "missioncore.e47-semantic-slam-result/v1"
}
}
+19
View File
@@ -67,6 +67,25 @@
"run": "missioncore.laboratory-run/v1",
"evidence": "missioncore.e46j-raw-fisheye-realtime-result/v1"
}
},
{
"work_id": "e47-semantic-slam-shadow",
"lifecycle": "experimental",
"isolation": "bounded-adapter",
"adapter_id": "experimental.e47-semantic-slam-shadow/v1",
"input_roles": [
"repository_root",
"semantic_result_root",
"threat_result_root",
"geometry_result_root"
],
"contracts": {
"source": "missioncore.e47-semantic-slam-source-set/v1",
"provider": "missioncore.semantic-slam-diagnostic-provider/v1",
"graph": "missioncore.e47-semantic-slam-shadow-graph/v1",
"run": "missioncore.laboratory-run/v1",
"evidence": "missioncore.e47-semantic-slam-result/v1"
}
}
],
"legacy_work_ids": [
@@ -0,0 +1,84 @@
{
"schema_version": "missioncore.e47-semantic-slam-profile/v1",
"profile_id": "ravnoves00-eomt-kb4-slam-shadow/v1",
"source": {
"source_id": "RAVNOVES00",
"session_id": "20260720T065719Z_viewer_live",
"frame_count": 4489,
"image_width": 800,
"image_height": 600,
"source_pack_id": "e10-lidar-pack-576c994a6c814e2592dd6240ace3902a5db94843312c759a73ba0c9166157d2b",
"source_pack_sha256": "0685d24219d8236caf8b7f1685e93f6d6b59e7fd015a768d88a92bbe8b154944",
"calibration_content_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"semantic_provider": {
"provider_id": "eomt-cityscapes-semantic-control/v1",
"model_id": "tue-mps/cityscapes_semantic_eomt_large_1024",
"model_revision": "8d6b6d1a3f7b50d441afd7d247c2ed10db186e8f",
"model_weights_sha256": "c265da9a74f58f5c3f4826d23ca4ca78beac0b106cca5842beca61580de5b782",
"preprocess_id": "raw-kb4-valid-fov-semantic/v1",
"mask_metadata_schema_version": "missioncore.panoptic-frame/v1",
"mask_payload": {
"media_type": "image/png",
"encoding": "uint8-class-id",
"width": 800,
"height": 600,
"sequence_binding": "sequence-0-to-frame-000001"
},
"role": "fixed-control-not-selected-production-provider"
},
"taxonomy": [
{"class_id": 0, "label": "outside_valid_fov", "disposition": "ambiguous", "color_rgb": [0, 0, 0]},
{"class_id": 1, "label": "person", "disposition": "labeled", "color_rgb": [220, 20, 60]},
{"class_id": 2, "label": "bicycle", "disposition": "labeled", "color_rgb": [119, 11, 32]},
{"class_id": 3, "label": "motorcycle", "disposition": "labeled", "color_rgb": [0, 0, 230]},
{"class_id": 4, "label": "car", "disposition": "labeled", "color_rgb": [0, 0, 142]},
{"class_id": 5, "label": "heavy_vehicle", "disposition": "labeled", "color_rgb": [0, 0, 70]},
{"class_id": 6, "label": "building_structure", "disposition": "labeled", "color_rgb": [70, 70, 70]},
{"class_id": 7, "label": "paved_road", "disposition": "labeled", "color_rgb": [128, 64, 128]},
{"class_id": 8, "label": "sidewalk_curb", "disposition": "labeled", "color_rgb": [244, 35, 232]},
{"class_id": 9, "label": "ground_dirt", "disposition": "labeled", "color_rgb": [81, 0, 81]},
{"class_id": 10, "label": "grass_low_vegetation", "disposition": "labeled", "color_rgb": [152, 251, 152]},
{"class_id": 11, "label": "tree_woody_vegetation", "disposition": "labeled", "color_rgb": [107, 142, 35]},
{"class_id": 12, "label": "sky", "disposition": "labeled", "color_rgb": [70, 130, 180]},
{"class_id": 13, "label": "static_obstacle", "disposition": "labeled", "color_rgb": [220, 220, 0]},
{"class_id": 14, "label": "animal", "disposition": "labeled", "color_rgb": [255, 127, 80]},
{"class_id": 15, "label": "other_background", "disposition": "labeled", "color_rgb": [153, 153, 153]}
],
"fusion": {
"projection": "factory-kb4-current-increment/v1",
"point_index_space": "frame-local-source-point-id/v1",
"observation_aggregation": "dominant-labeled-majority-diagnostic/v1",
"unprojected_status": "unprojected",
"semantic_absence_means_free": false,
"semantic_can_create_obstacle": false,
"semantic_can_change_identity": false,
"semantic_can_change_metric_geometry": false,
"semantic_can_change_occupancy": false,
"semantic_can_change_motion": false,
"semantic_can_change_threat": false
},
"temporal_binding": {
"semantic_to_camera": "exact-sequence-and-session-time",
"camera_to_lidar": "accepted-e6-nearest-host-arrival-best-effort",
"clock_basis": "recorded-host-monotonic-arrival",
"maximum_lidar_camera_delta_ms": 100.0,
"maximum_pose_point_delta_ms": 100.0,
"physical_synchronization_proven": false
},
"acceptance": {
"full_frame_accounting_required": true,
"point_accounting_required": true,
"observation_binding_required": true,
"exact_mask_archive_required": true,
"independent_semantic_truth_required_for_provider_promotion": true
},
"authority": {
"ground_truth": false,
"physical_live": false,
"commands_enabled": false,
"actuation_allowed": false,
"navigation_or_safety_accepted": false,
"semantic_authority": "diagnostic-only"
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ WHEEL_NAME = "nodedc_mission_core-0.1.0-py3-none-any.whl"
RUNNER_NAME = RUNNER.name
PATCH_ID = re.compile(r"^[A-Za-z0-9._-]{1,96}$")
EXPECTED_BASELINE_SHA256 = "ea10359339e6cce31b5780a2710299771cab7cc0c1c2a2b56a1621f786b31fa8"
EXPECTED_WHEEL_SHA256 = "2fc53bf3c2cd82a33e62b158a455d813bca64b844707e758792b4bac263b2543"
EXPECTED_WHEEL_SHA256 = "c396a202d5cddc2d22dcc3e8b936519205399d20b0f060c763c02e51ba16c62a"
PAYLOAD_FILES = (
RUNNER_NAME,
WHEEL_NAME,
+17
View File
@@ -315,6 +315,7 @@ def canonical_laboratory_adapters() -> dict[str, LaboratoryAdapter]:
"canonical.e33-worker-shadow/v1": _run_e33,
"canonical.e35-degradation-recovery/v1": _run_e35,
"canonical.e46j-raw-fisheye-realtime/v1": _run_e46j,
"experimental.e47-semantic-slam-shadow/v1": _run_e47,
}
@@ -375,6 +376,22 @@ def _run_e46j(request: LaboratoryRunRequest) -> LaboratoryAdapterResult:
)
def _run_e47(request: LaboratoryRunRequest) -> LaboratoryAdapterResult:
from k1link.perception.semantic_slam_replay import build_semantic_slam_replay
result = build_semantic_slam_replay(
repository_root=request.inputs["repository_root"],
semantic_result_root=request.inputs["semantic_result_root"],
threat_result_root=request.inputs["threat_result_root"],
geometry_result_root=request.inputs["geometry_result_root"],
output_root=request.output_root,
)
return LaboratoryAdapterResult(
result_root=result.result_root,
result_id=result.result_id,
)
def _validate_request(
request: LaboratoryRunRequest,
definition: LaboratoryExecutionDefinition,
+100 -3
View File
@@ -83,6 +83,22 @@ class GeometryFrame:
return int(self.points_map.shape[0])
@dataclass(frozen=True, slots=True)
class RecordedFrameTemporalBinding:
"""Digest-bound recorded timing evidence for one camera-indexed increment.
The shared session time binds the camera ordinal to the E10 pack entry. The
LiDAR and pose deltas retain their admitted E6 meaning: nearest host-arrival
best effort, not hardware synchronization.
"""
frame_index: int
source_time_ns: int
source_available: bool
lidar_camera_delta_ms: float | None
pose_point_delta_ms: float | None
@dataclass(frozen=True, slots=True)
class ReplayBodyFrameInputs:
"""Verified inputs required to derive one replay-only virtual body frame."""
@@ -222,13 +238,29 @@ class RecordedGeometryStore:
or pose_reference.frame_index != envelope.sequence
):
raise GeometryProviderError("packet geometry references are not source-bound")
frame_index = envelope.sequence
frame = self.frame_for_index(envelope.sequence)
if frame is None:
raise GeometryProviderError("packet claims unavailable source geometry as current")
return frame
def frame_for_index(self, frame_index: int) -> GeometryFrame | None:
"""Expose one verified source increment with its pose and KB4 calibration.
This read-only seam is intentionally narrower than the source archive. It
exists for deterministic replay diagnostics which must project the exact
frame-local point index space without manufacturing a ``SourcePacket``.
An unavailable recorded increment remains ``None``; surface validity is
retained on the returned frame rather than silently filtering its points.
"""
if not isinstance(frame_index, int) or isinstance(frame_index, bool):
raise GeometryProviderError("replay geometry frame index is invalid")
if not 0 <= frame_index < self.profile.frame_count:
raise GeometryProviderError("packet geometry frame index is outside the profile")
raise GeometryProviderError("replay geometry frame is outside the profile")
if int(self._source["frame_indices"][frame_index]) != frame_index:
raise GeometryProviderError("source pack frame sequence changed")
if not bool(self._source["sample_available"][frame_index]):
raise GeometryProviderError("packet claims unavailable source geometry as current")
return None
offsets = self._source["cloud_offsets"]
start, end = int(offsets[frame_index]), int(offsets[frame_index + 1])
return GeometryFrame(
@@ -247,6 +279,42 @@ class RecordedGeometryStore:
surface_valid=bool(self._surface["frame_valid"][frame_index]),
)
def temporal_binding_for_index(self, frame_index: int) -> RecordedFrameTemporalBinding:
"""Return the sealed ordinal/session binding and admitted best-effort deltas."""
if not isinstance(frame_index, int) or isinstance(frame_index, bool):
raise GeometryProviderError("replay temporal frame index is invalid")
if not 0 <= frame_index < self.profile.frame_count:
raise GeometryProviderError("replay temporal frame is outside the profile")
if (
int(self._source["frame_indices"][frame_index]) != frame_index
or int(self._source["source_frame_indices"][frame_index]) != frame_index
):
raise GeometryProviderError("source pack temporal sequence changed")
session_seconds = float(self._source["session_seconds"][frame_index])
if not math.isfinite(session_seconds) or session_seconds < 0.0:
raise GeometryProviderError("source pack session time is invalid")
source_available = bool(self._source["sample_available"][frame_index])
lidar_delta = float(self._source["lidar_camera_delta_ms"][frame_index])
pose_delta = float(self._source["pose_point_delta_ms"][frame_index])
if source_available:
if not math.isfinite(lidar_delta) or not math.isfinite(pose_delta):
raise GeometryProviderError("available source temporal deltas are invalid")
lidar_value: float | None = lidar_delta
pose_value: float | None = pose_delta
else:
if not math.isnan(lidar_delta) or not math.isnan(pose_delta):
raise GeometryProviderError("unavailable source carries temporal deltas")
lidar_value = None
pose_value = None
return RecordedFrameTemporalBinding(
frame_index=frame_index,
source_time_ns=round(session_seconds * 1_000_000_000),
source_available=source_available,
lidar_camera_delta_ms=lidar_value,
pose_point_delta_ms=pose_value,
)
def current_points(self, packet: SourcePacket) -> FloatArray | None:
"""Expose the verified frame-local point index space to temporal occupancy."""
@@ -393,6 +461,8 @@ class RecordedGeometryStore:
def _validate(self) -> None:
source_required = {
"frame_indices",
"source_frame_indices",
"session_seconds",
"sample_available",
"cloud_offsets",
"cloud_points_map",
@@ -401,6 +471,8 @@ class RecordedGeometryStore:
"intrinsic_fx_fy_cx_cy",
"distortion_kb4",
"t_camera_from_lidar",
"lidar_camera_delta_ms",
"pose_point_delta_ms",
}
surface_required = {"frame_valid", "point_class"}
if not source_required.issubset(self._source):
@@ -411,6 +483,8 @@ class RecordedGeometryStore:
points = self.profile.point_count
shapes = {
"frame_indices": (frames,),
"source_frame_indices": (frames,),
"session_seconds": (frames,),
"sample_available": (frames,),
"cloud_offsets": (frames + 1,),
"cloud_points_map": (points, 3),
@@ -419,6 +493,8 @@ class RecordedGeometryStore:
"intrinsic_fx_fy_cx_cy": (4,),
"distortion_kb4": (4,),
"t_camera_from_lidar": (4, 4),
"lidar_camera_delta_ms": (frames,),
"pose_point_delta_ms": (frames,),
}
if any(self._source[name].shape != shape for name, shape in shapes.items()):
raise GeometryProviderError("source pack array shapes changed")
@@ -428,6 +504,26 @@ class RecordedGeometryStore:
raise GeometryProviderError("local surface point shape changed")
if int(self._source["cloud_offsets"][-1]) != points:
raise GeometryProviderError("source point offsets do not close")
expected_indices = np.arange(frames, dtype=np.int64)
session_seconds = np.asarray(self._source["session_seconds"], dtype=np.float64)
if (
not np.array_equal(self._source["frame_indices"], expected_indices)
or not np.array_equal(self._source["source_frame_indices"], expected_indices)
or not np.isfinite(session_seconds).all()
or np.any(session_seconds < 0.0)
or np.any(np.diff(session_seconds) <= 0.0)
):
raise GeometryProviderError("source temporal index changed")
available = np.asarray(self._source["sample_available"], dtype=np.bool_)
lidar_deltas = np.asarray(self._source["lidar_camera_delta_ms"], dtype=np.float64)
pose_deltas = np.asarray(self._source["pose_point_delta_ms"], dtype=np.float64)
if (
not np.isfinite(lidar_deltas[available]).all()
or not np.isfinite(pose_deltas[available]).all()
or not np.isnan(lidar_deltas[~available]).all()
or not np.isnan(pose_deltas[~available]).all()
):
raise GeometryProviderError("source temporal delta availability changed")
if (
int(np.count_nonzero(self._source["sample_available"]))
!= self.profile.valid_frame_count
@@ -1030,6 +1126,7 @@ __all__ = [
"GeometryProviderError",
"GeometryProviderSnapshot",
"Ravnoves00GeometryAssociationProvider",
"RecordedFrameTemporalBinding",
"RecordedGeometryStore",
"load_geometry_profile",
]
+643
View File
@@ -0,0 +1,643 @@
"""Model-neutral semantic diagnostics for admitted geometry observations.
The seam projects a provider-owned ``uint8`` semantic mask onto the existing
frame-local point index space and aggregates those labels for already-created
``ObstacleObservation`` values. It deliberately returns separate diagnostic
evidence: semantic output cannot create or replace obstacle identity, metric
occupancy, motion, threat, or safety authority.
"""
from __future__ import annotations
import math
import re
from collections import Counter
from dataclasses import dataclass
from enum import IntEnum, StrEnum
import numpy as np
import numpy.typing as npt
from .contracts import ObstacleObservation
from .geometry_math import ProjectedPointCloud
Int16Array = npt.NDArray[np.int16]
UInt8Array = npt.NDArray[np.uint8]
NO_SEMANTIC_CLASS_ID = -1
_IDENTIFIER = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/-]{0,159}$")
class SemanticFusionError(ValueError):
"""Semantic input or its geometry binding violates the diagnostic contract."""
class SemanticClassDisposition(StrEnum):
"""Whether one provider class is usable as a label or explicitly uncertain."""
LABELED = "labeled"
AMBIGUOUS = "ambiguous"
class SemanticEvidenceStatus(IntEnum):
"""Compact source-point and observation semantic state."""
ABSENT = 0
UNPROJECTED = 1
AMBIGUOUS = 2
LABELED = 3
class SemanticEvidenceAuthority(StrEnum):
"""Semantic output is never promoted into planner or safety authority."""
DIAGNOSTIC_ONLY = "diagnostic-only"
@dataclass(frozen=True, slots=True)
class SemanticClassDefinition:
"""Provider-neutral meaning assigned to one raw ``uint8`` mask value."""
class_id: int
label: str
disposition: SemanticClassDisposition = SemanticClassDisposition.LABELED
def __post_init__(self) -> None:
if (
not isinstance(self.class_id, int)
or isinstance(self.class_id, bool)
or not 0 <= self.class_id <= 255
):
raise SemanticFusionError("semantic class id must fit uint8")
_label(self.label, "semantic class label")
if not isinstance(self.disposition, SemanticClassDisposition):
raise SemanticFusionError("semantic class disposition is invalid")
@dataclass(frozen=True, slots=True)
class SemanticMask:
"""One source-bound hard semantic mask plus its complete class vocabulary."""
source_id: str
frame_id: str
provider_id: str
model_id: str
preprocess_id: str
labels: UInt8Array
classes: tuple[SemanticClassDefinition, ...]
def __post_init__(self) -> None:
for value, label in (
(self.source_id, "semantic source id"),
(self.frame_id, "semantic frame id"),
(self.provider_id, "semantic provider id"),
(self.model_id, "semantic model id"),
(self.preprocess_id, "semantic preprocess id"),
):
_identifier(value, label)
if not isinstance(self.labels, np.ndarray):
raise SemanticFusionError("semantic mask must be a numpy array")
if self.labels.dtype != np.uint8 or self.labels.ndim != 2:
raise SemanticFusionError("semantic mask must have uint8 HxW shape")
if self.labels.shape[0] < 1 or self.labels.shape[1] < 1:
raise SemanticFusionError("semantic mask dimensions must be positive")
if not isinstance(self.classes, tuple) or not self.classes:
raise SemanticFusionError("semantic class vocabulary must be a nonempty tuple")
if any(not isinstance(item, SemanticClassDefinition) for item in self.classes):
raise SemanticFusionError("semantic class vocabulary is invalid")
class_ids = tuple(item.class_id for item in self.classes)
if len(set(class_ids)) != len(class_ids):
raise SemanticFusionError("semantic class ids must be unique")
undeclared = set(int(value) for value in np.unique(self.labels)) - set(class_ids)
if undeclared:
raise SemanticFusionError("semantic mask contains undeclared class ids")
frozen = np.array(self.labels, dtype=np.uint8, order="C", copy=True)
frozen.setflags(write=False)
object.__setattr__(self, "labels", frozen)
@property
def height(self) -> int:
return int(self.labels.shape[0])
@property
def width(self) -> int:
return int(self.labels.shape[1])
def class_definition(self, class_id: int) -> SemanticClassDefinition:
for definition in self.classes:
if definition.class_id == class_id:
return definition
raise SemanticFusionError("semantic class id is not declared")
@dataclass(frozen=True, slots=True)
class PointSemanticLabels:
"""Semantic labels aligned to the complete source-point index space.
``class_ids`` uses ``-1`` only when the corresponding status is ``ABSENT``
or ``UNPROJECTED``. Callers must never interpret that sentinel as a model
class. Ambiguous provider classes retain their raw class id for review.
"""
class_ids: Int16Array
status_codes: UInt8Array
classes: tuple[SemanticClassDefinition, ...]
def __post_init__(self) -> None:
if not isinstance(self.class_ids, np.ndarray) or self.class_ids.dtype != np.int16:
raise SemanticFusionError("point semantic class ids must be int16")
if self.class_ids.ndim != 1:
raise SemanticFusionError("point semantic class ids must be one-dimensional")
if not isinstance(self.status_codes, np.ndarray) or self.status_codes.dtype != np.uint8:
raise SemanticFusionError("point semantic status codes must be uint8")
if self.status_codes.shape != self.class_ids.shape:
raise SemanticFusionError("point semantic arrays must have equal shape")
if not isinstance(self.classes, tuple) or any(
not isinstance(item, SemanticClassDefinition) for item in self.classes
):
raise SemanticFusionError("point semantic vocabulary is invalid")
if len({item.class_id for item in self.classes}) != len(self.classes):
raise SemanticFusionError("point semantic class ids must be unique")
valid_statuses = {int(status) for status in SemanticEvidenceStatus}
if set(int(value) for value in np.unique(self.status_codes)) - valid_statuses:
raise SemanticFusionError("point semantic status code is invalid")
unavailable = np.isin(
self.status_codes,
(SemanticEvidenceStatus.ABSENT, SemanticEvidenceStatus.UNPROJECTED),
)
if np.any(self.class_ids[unavailable] != NO_SEMANTIC_CLASS_ID):
raise SemanticFusionError("unavailable point semantics cannot carry a class id")
available = ~unavailable
if np.any((self.class_ids[available] < 0) | (self.class_ids[available] > 255)):
raise SemanticFusionError("available point semantic class id is invalid")
definitions = {item.class_id: item for item in self.classes}
for class_id, status_code in zip(
self.class_ids[available].tolist(),
self.status_codes[available].tolist(),
strict=True,
):
definition = definitions.get(int(class_id))
if definition is None:
raise SemanticFusionError("point semantic class id is not declared")
expected = (
SemanticEvidenceStatus.AMBIGUOUS
if definition.disposition is SemanticClassDisposition.AMBIGUOUS
else SemanticEvidenceStatus.LABELED
)
if int(status_code) != int(expected):
raise SemanticFusionError("point semantic status disagrees with its class")
class_ids = np.array(self.class_ids, dtype=np.int16, order="C", copy=True)
status_codes = np.array(self.status_codes, dtype=np.uint8, order="C", copy=True)
class_ids.setflags(write=False)
status_codes.setflags(write=False)
object.__setattr__(self, "class_ids", class_ids)
object.__setattr__(self, "status_codes", status_codes)
@property
def source_point_count(self) -> int:
return int(self.class_ids.size)
def status_for(self, source_point_id: int) -> SemanticEvidenceStatus:
_point_id(source_point_id, self.source_point_count)
return SemanticEvidenceStatus(int(self.status_codes[source_point_id]))
def class_id_for(self, source_point_id: int) -> int | None:
_point_id(source_point_id, self.source_point_count)
value = int(self.class_ids[source_point_id])
return None if value == NO_SEMANTIC_CLASS_ID else value
def label_for(self, source_point_id: int) -> str | None:
class_id = self.class_id_for(source_point_id)
if class_id is None:
return None
for definition in self.classes:
if definition.class_id == class_id:
return definition.label
raise AssertionError("validated point class disappeared from its vocabulary")
@dataclass(frozen=True, slots=True)
class SemanticClassEvidence:
"""Point support for one semantic class inside an existing observation."""
class_id: int
label: str
disposition: SemanticClassDisposition
point_count: int
def __post_init__(self) -> None:
if (
not isinstance(self.class_id, int)
or isinstance(self.class_id, bool)
or not 0 <= self.class_id <= 255
):
raise SemanticFusionError("semantic evidence class id must fit uint8")
_label(self.label, "semantic evidence label")
if not isinstance(self.disposition, SemanticClassDisposition):
raise SemanticFusionError("semantic evidence disposition is invalid")
if (
not isinstance(self.point_count, int)
or isinstance(self.point_count, bool)
or self.point_count < 1
):
raise SemanticFusionError("semantic evidence point count must be positive")
@dataclass(frozen=True, slots=True)
class ObservationSemanticEvidence:
"""Aggregated, non-authoritative semantics for one immutable observation."""
observation_id: str
occupancy_key: str
status: SemanticEvidenceStatus
source_point_count: int
labeled_point_count: int
ambiguous_point_count: int
unprojected_point_count: int
absent_point_count: int
class_evidence: tuple[SemanticClassEvidence, ...]
dominant_class_id: int | None
dominant_label: str | None
dominant_fraction_of_labeled: float | None
reason_code: str
authority: SemanticEvidenceAuthority = SemanticEvidenceAuthority.DIAGNOSTIC_ONLY
def __post_init__(self) -> None:
_identifier(self.observation_id, "semantic observation id")
_identifier(self.occupancy_key, "semantic occupancy binding")
_identifier(self.reason_code, "semantic evidence reason")
if not isinstance(self.status, SemanticEvidenceStatus):
raise SemanticFusionError("observation semantic status is invalid")
if self.authority is not SemanticEvidenceAuthority.DIAGNOSTIC_ONLY:
raise SemanticFusionError("semantic evidence cannot acquire product authority")
counts = (
self.source_point_count,
self.labeled_point_count,
self.ambiguous_point_count,
self.unprojected_point_count,
self.absent_point_count,
)
if any(
not isinstance(value, int) or isinstance(value, bool) or value < 0
for value in counts
):
raise SemanticFusionError("semantic evidence counts must be nonnegative integers")
if sum(counts[1:]) != self.source_point_count:
raise SemanticFusionError("semantic evidence accounting is incomplete")
if not isinstance(self.class_evidence, tuple) or any(
not isinstance(item, SemanticClassEvidence) for item in self.class_evidence
):
raise SemanticFusionError("semantic class evidence is invalid")
if sum(item.point_count for item in self.class_evidence) != (
self.labeled_point_count + self.ambiguous_point_count
):
raise SemanticFusionError("semantic class evidence accounting is incomplete")
if len({item.class_id for item in self.class_evidence}) != len(self.class_evidence):
raise SemanticFusionError("semantic class evidence ids must be unique")
if self.status is SemanticEvidenceStatus.ABSENT:
if self.absent_point_count != self.source_point_count:
raise SemanticFusionError("absent semantic evidence accounting is invalid")
elif self.status is SemanticEvidenceStatus.UNPROJECTED:
if self.source_point_count and self.unprojected_point_count != self.source_point_count:
raise SemanticFusionError("unprojected semantic evidence accounting is invalid")
elif self.status is SemanticEvidenceStatus.AMBIGUOUS:
if not self.labeled_point_count and not self.ambiguous_point_count:
raise SemanticFusionError("ambiguous semantic evidence needs projected labels")
elif not self.labeled_point_count:
raise SemanticFusionError("labeled semantic evidence needs labeled points")
dominant_values = (
self.dominant_class_id,
self.dominant_label,
self.dominant_fraction_of_labeled,
)
if self.status is SemanticEvidenceStatus.LABELED:
if any(value is None for value in dominant_values):
raise SemanticFusionError("labeled semantic evidence needs a dominant class")
if (
not isinstance(self.dominant_fraction_of_labeled, float)
or not math.isfinite(self.dominant_fraction_of_labeled)
or not 0.5 < self.dominant_fraction_of_labeled <= 1.0
):
raise SemanticFusionError("dominant semantic fraction must be a majority")
elif any(value is not None for value in dominant_values):
raise SemanticFusionError("non-labeled semantic evidence cannot claim a dominant class")
@property
def semantic_coverage_fraction(self) -> float:
if not self.source_point_count:
return 0.0
return (self.labeled_point_count + self.ambiguous_point_count) / self.source_point_count
@dataclass(frozen=True, slots=True)
class SemanticFusionResult:
"""One frame's detached semantic diagnostics."""
mask_available: bool
point_labels: PointSemanticLabels
observation_evidence: tuple[ObservationSemanticEvidence, ...]
authority: SemanticEvidenceAuthority = SemanticEvidenceAuthority.DIAGNOSTIC_ONLY
def __post_init__(self) -> None:
if not isinstance(self.mask_available, bool):
raise SemanticFusionError("semantic mask availability must be boolean")
if not isinstance(self.point_labels, PointSemanticLabels):
raise SemanticFusionError("semantic point labels are invalid")
if not isinstance(self.observation_evidence, tuple) or any(
not isinstance(item, ObservationSemanticEvidence)
for item in self.observation_evidence
):
raise SemanticFusionError("observation semantic evidence is invalid")
if len({item.observation_id for item in self.observation_evidence}) != len(
self.observation_evidence
):
raise SemanticFusionError("observation semantic evidence ids must be unique")
if self.authority is not SemanticEvidenceAuthority.DIAGNOSTIC_ONLY:
raise SemanticFusionError("semantic fusion cannot acquire product authority")
if self.mask_available is not bool(self.point_labels.classes):
raise SemanticFusionError("semantic mask availability and vocabulary disagree")
def fuse_semantic_diagnostics(
*,
semantic_mask: SemanticMask | None,
projected: ProjectedPointCloud,
observations: tuple[ObstacleObservation, ...],
) -> SemanticFusionResult:
"""Attach mask diagnostics to points and observations without mutating authority."""
if semantic_mask is not None and not isinstance(semantic_mask, SemanticMask):
raise SemanticFusionError("semantic mask contract is invalid")
if not isinstance(observations, tuple) or any(
not isinstance(item, ObstacleObservation) for item in observations
):
raise SemanticFusionError("geometry observations must be a tuple")
_validate_projection(projected)
_validate_observation_bindings(
observations,
semantic_mask=semantic_mask,
source_point_count=projected.source_point_count,
)
point_labels = _project_point_labels(semantic_mask, projected)
evidence = tuple(
_aggregate_observation(observation, point_labels) for observation in observations
)
return SemanticFusionResult(
mask_available=semantic_mask is not None,
point_labels=point_labels,
observation_evidence=evidence,
)
def _project_point_labels(
semantic_mask: SemanticMask | None,
projected: ProjectedPointCloud,
) -> PointSemanticLabels:
point_count = projected.source_point_count
class_ids = np.full(point_count, NO_SEMANTIC_CLASS_ID, dtype=np.int16)
if semantic_mask is None:
return PointSemanticLabels(
class_ids=class_ids,
status_codes=np.full(
point_count,
int(SemanticEvidenceStatus.ABSENT),
dtype=np.uint8,
),
classes=(),
)
statuses = np.full(
point_count,
int(SemanticEvidenceStatus.UNPROJECTED),
dtype=np.uint8,
)
if projected.projected_point_count:
pixel_indices = np.floor(projected.pixels_xy).astype(np.int64)
inside = (
(pixel_indices[:, 0] >= 0)
& (pixel_indices[:, 0] < semantic_mask.width)
& (pixel_indices[:, 1] >= 0)
& (pixel_indices[:, 1] < semantic_mask.height)
)
rows = np.flatnonzero(inside)
if rows.size:
source_ids = projected.source_indices[rows]
pixels = pixel_indices[rows]
raw_classes = semantic_mask.labels[pixels[:, 1], pixels[:, 0]]
class_ids[source_ids] = raw_classes.astype(np.int16, copy=False)
ambiguous_ids = np.asarray(
[
item.class_id
for item in semantic_mask.classes
if item.disposition is SemanticClassDisposition.AMBIGUOUS
],
dtype=np.uint8,
)
ambiguous = np.isin(raw_classes, ambiguous_ids)
statuses[source_ids] = np.where(
ambiguous,
int(SemanticEvidenceStatus.AMBIGUOUS),
int(SemanticEvidenceStatus.LABELED),
).astype(np.uint8, copy=False)
return PointSemanticLabels(
class_ids=class_ids,
status_codes=statuses,
classes=semantic_mask.classes,
)
def _aggregate_observation(
observation: ObstacleObservation,
point_labels: PointSemanticLabels,
) -> ObservationSemanticEvidence:
point_ids = np.asarray(observation.source_point_ids, dtype=np.int64)
statuses = point_labels.status_codes[point_ids]
class_ids = point_labels.class_ids[point_ids]
counts = Counter(int(value) for value in statuses.tolist())
labeled_count = counts[int(SemanticEvidenceStatus.LABELED)]
ambiguous_count = counts[int(SemanticEvidenceStatus.AMBIGUOUS)]
unprojected_count = counts[int(SemanticEvidenceStatus.UNPROJECTED)]
absent_count = counts[int(SemanticEvidenceStatus.ABSENT)]
definitions = {item.class_id: item for item in point_labels.classes}
semantic_class_ids = class_ids[
np.isin(
statuses,
(SemanticEvidenceStatus.LABELED, SemanticEvidenceStatus.AMBIGUOUS),
)
]
class_counts = Counter(int(value) for value in semantic_class_ids.tolist())
class_evidence = tuple(
SemanticClassEvidence(
class_id=class_id,
label=definitions[class_id].label,
disposition=definitions[class_id].disposition,
point_count=point_count,
)
for class_id, point_count in sorted(class_counts.items())
)
status: SemanticEvidenceStatus
dominant_class_id: int | None = None
dominant_label: str | None = None
dominant_fraction: float | None = None
if not point_labels.classes:
status = SemanticEvidenceStatus.ABSENT
reason_code = "semantic-mask-unavailable"
elif not observation.source_point_ids or unprojected_count == len(
observation.source_point_ids
):
status = SemanticEvidenceStatus.UNPROJECTED
reason_code = (
"observation-has-no-source-points"
if not observation.source_point_ids
else "observation-points-unprojected"
)
elif not labeled_count:
status = SemanticEvidenceStatus.AMBIGUOUS
reason_code = "semantic-classes-ambiguous"
else:
labeled_ids = class_ids[statuses == int(SemanticEvidenceStatus.LABELED)]
labeled_counts = Counter(int(value) for value in labeled_ids.tolist())
maximum = max(labeled_counts.values())
candidates = [
class_id for class_id, count in labeled_counts.items() if count == maximum
]
semantic_point_count = labeled_count + ambiguous_count
if len(candidates) != 1 or maximum * 2 <= semantic_point_count:
status = SemanticEvidenceStatus.AMBIGUOUS
reason_code = "semantic-label-majority-ambiguous"
else:
status = SemanticEvidenceStatus.LABELED
dominant_class_id = candidates[0]
dominant_label = definitions[dominant_class_id].label
dominant_fraction = float(maximum / labeled_count)
reason_code = "semantic-label-majority"
return ObservationSemanticEvidence(
observation_id=observation.observation_id,
occupancy_key=observation.occupancy_key,
status=status,
source_point_count=len(observation.source_point_ids),
labeled_point_count=labeled_count,
ambiguous_point_count=ambiguous_count,
unprojected_point_count=unprojected_count,
absent_point_count=absent_count,
class_evidence=class_evidence,
dominant_class_id=dominant_class_id,
dominant_label=dominant_label,
dominant_fraction_of_labeled=dominant_fraction,
reason_code=reason_code,
)
def _validate_projection(projected: ProjectedPointCloud) -> None:
if not isinstance(projected, ProjectedPointCloud):
raise SemanticFusionError("projected point cloud contract is invalid")
if (
not isinstance(projected.pixels_xy, np.ndarray)
or projected.pixels_xy.dtype != np.float64
or projected.pixels_xy.ndim != 2
or projected.pixels_xy.shape[1:] != (2,)
):
raise SemanticFusionError("projected pixels must have float64 Nx2 shape")
count = projected.projected_point_count
if (
not isinstance(projected.depths_m, np.ndarray)
or projected.depths_m.dtype != np.float64
or projected.depths_m.shape != (count,)
):
raise SemanticFusionError("projected depths must have float64 N shape")
if (
not isinstance(projected.source_indices, np.ndarray)
or projected.source_indices.dtype != np.int64
or projected.source_indices.shape != (count,)
):
raise SemanticFusionError("projected source indices must have int64 N shape")
for value, label in (
(projected.source_point_count, "source point count"),
(projected.camera_front_point_count, "camera-front point count"),
):
if not isinstance(value, int) or isinstance(value, bool) or value < 0:
raise SemanticFusionError(f"{label} must be a nonnegative integer")
if not count <= projected.camera_front_point_count <= projected.source_point_count:
raise SemanticFusionError("projected point accounting is invalid")
if (
not np.isfinite(projected.pixels_xy).all()
or not np.isfinite(projected.depths_m).all()
or np.any(projected.depths_m <= 0.0)
):
raise SemanticFusionError("projected point values must be finite and in front")
if np.any(projected.source_indices < 0) or np.any(
projected.source_indices >= projected.source_point_count
):
raise SemanticFusionError("projected source point id is outside the source frame")
if np.unique(projected.source_indices).size != count:
raise SemanticFusionError("projected source point ids must be unique")
def _validate_observation_bindings(
observations: tuple[ObstacleObservation, ...],
*,
semantic_mask: SemanticMask | None,
source_point_count: int,
) -> None:
observation_ids: set[str] = set()
point_owners: dict[int, str] = {}
source_frames = {(item.source_id, item.frame_id) for item in observations}
if len(source_frames) > 1:
raise SemanticFusionError("geometry observations escaped their source frame")
for observation in observations:
if observation.observation_id in observation_ids:
raise SemanticFusionError("geometry observation ids must be unique")
observation_ids.add(observation.observation_id)
if semantic_mask is not None and (
observation.source_id != semantic_mask.source_id
or observation.frame_id != semantic_mask.frame_id
):
raise SemanticFusionError("semantic mask escaped its observation source frame")
for point_id in observation.source_point_ids:
if point_id >= source_point_count:
raise SemanticFusionError("observation source point id is outside the source frame")
previous = point_owners.setdefault(point_id, observation.observation_id)
if previous != observation.observation_id:
raise SemanticFusionError("source point has duplicate observation ownership")
def _identifier(value: str, label: str) -> None:
if not isinstance(value, str) or _IDENTIFIER.fullmatch(value) is None:
raise SemanticFusionError(f"{label} is invalid")
def _label(value: str, label: str) -> None:
if (
not isinstance(value, str)
or not value
or value != value.strip()
or len(value) > 120
or any(ord(character) < 32 for character in value)
):
raise SemanticFusionError(f"{label} is invalid")
def _point_id(value: int, source_point_count: int) -> None:
if (
not isinstance(value, int)
or isinstance(value, bool)
or not 0 <= value < source_point_count
):
raise SemanticFusionError("source point id is outside the point-label frame")
__all__ = [
"NO_SEMANTIC_CLASS_ID",
"ObservationSemanticEvidence",
"PointSemanticLabels",
"SemanticClassDefinition",
"SemanticClassDisposition",
"SemanticClassEvidence",
"SemanticEvidenceAuthority",
"SemanticEvidenceStatus",
"SemanticFusionError",
"SemanticFusionResult",
"SemanticMask",
"fuse_semantic_diagnostics",
]
File diff suppressed because it is too large Load Diff
+12
View File
@@ -74,6 +74,7 @@ from k1link.web.e46i_grounding_dino_full_replay_api import (
from k1link.web.e46j_raw_fisheye_realtime_api import (
build_e46j_raw_fisheye_realtime_router,
)
from k1link.web.e47_semantic_slam_api import build_e47_semantic_slam_router
from k1link.web.environment_api import build_environment_router
from k1link.web.l3_pointpillars_visual_api import (
build_l3_pointpillars_visual_router,
@@ -777,6 +778,17 @@ app.include_router(
),
)
)
app.include_router(
build_e47_semantic_slam_router(
root_provider=lambda: (
REPOSITORY_ROOT
/ ".runtime"
/ "compute-experiments"
/ "e47"
/ "semantic-slam-results"
),
)
)
app.include_router(
build_e46e_ready_stack_router(
root_provider=lambda: (
+556
View File
@@ -0,0 +1,556 @@
"""Read-only LAB projection of the immutable E47 semantic/SLAM shadow result."""
from __future__ import annotations
import copy
import hashlib
import json
import re
import zipfile
from collections.abc import Callable
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Final
import numpy as np
import numpy.typing as npt
from fastapi import APIRouter, HTTPException, Query, Response
from k1link.perception.semantic_fusion import SemanticEvidenceStatus
from k1link.perception.semantic_slam_replay import (
PUBLICATION_STATUS,
SEMANTIC_SLAM_MANIFEST_NAME,
SEMANTIC_SLAM_MASKS_NAME,
SEMANTIC_SLAM_OBSERVATIONS_NAME,
SEMANTIC_SLAM_POINTS_NAME,
SEMANTIC_SLAM_REPORT_NAME,
SEMANTIC_SLAM_RESULT_PREFIX,
SEMANTIC_SLAM_TAXONOMY_NAME,
SEMANTIC_SLAM_TAXONOMY_SCHEMA,
SemanticSlamReplayError,
SemanticSlamReplayResult,
read_semantic_slam_replay_result,
)
E47_SEMANTIC_SLAM_CATALOG_SCHEMA: Final = "missioncore.e47-semantic-slam-catalog/v1"
E47_SEMANTIC_SLAM_VIEW_SCHEMA: Final = "missioncore.e47-semantic-slam-view/v1"
E47_SEMANTIC_SLAM_CHUNK_SCHEMA: Final = "missioncore.e47-semantic-slam-chunk/v1"
E47_SEMANTIC_SLAM_FRAME_SCHEMA: Final = "missioncore.e47-semantic-slam-frame/v1"
E47_SEMANTIC_SLAM_VIEW_STATUS: Final = "diagnostic-semantic-slam-shadow"
E47_SEMANTIC_SLAM_MAX_CHUNK_FRAMES: Final = 24
_RESULT_ID = re.compile(rf"^{SEMANTIC_SLAM_RESULT_PREFIX}[a-f0-9]{{64}}$")
_EXPECTED_ARTIFACTS: Final = (
SEMANTIC_SLAM_MANIFEST_NAME,
SEMANTIC_SLAM_REPORT_NAME,
SEMANTIC_SLAM_POINTS_NAME,
SEMANTIC_SLAM_OBSERVATIONS_NAME,
SEMANTIC_SLAM_MASKS_NAME,
SEMANTIC_SLAM_TAXONOMY_NAME,
)
_MAX_TAXONOMY_BYTES: Final = 1024 * 1024
_MAX_MASK_BYTES: Final = 16 * 1024 * 1024
RootProvider = Callable[[], Path | None]
Int64Array = npt.NDArray[np.int64]
Int32Array = npt.NDArray[np.int32]
UInt8Array = npt.NDArray[np.uint8]
@dataclass(frozen=True, slots=True)
class _SemanticPointLedger:
frame_offsets: Int64Array
point_labels: UInt8Array
point_status_codes: UInt8Array
frame_source_point_counts: Int32Array
frame_labeled_point_counts: Int32Array
frame_ambiguous_point_counts: Int32Array
frame_unprojected_point_counts: Int32Array
frame_absent_point_counts: Int32Array
@property
def frame_count(self) -> int:
return int(self.frame_offsets.size - 1)
def build_e47_semantic_slam_router(
*,
root_provider: RootProvider = lambda: None,
) -> APIRouter:
"""Expose immutable E47 evidence without granting it safety authority."""
router = APIRouter(
prefix="/api/v1/laboratory/e47-semantic-slam",
tags=["laboratory"],
)
def result(result_id: str) -> SemanticSlamReplayResult:
if _RESULT_ID.fullmatch(result_id) is None:
raise HTTPException(status_code=404, detail="E47 result не найден")
root = _configured_root(root_provider)
if root is None:
raise HTTPException(status_code=404, detail="E47 result не найден")
candidate = root / result_id
if candidate.is_symlink():
raise HTTPException(status_code=404, detail="E47 result не найден")
try:
path = candidate.resolve(strict=True)
except OSError:
raise HTTPException(status_code=404, detail="E47 result не найден") from None
if path.parent != root or not path.is_dir():
raise HTTPException(status_code=404, detail="E47 result не найден")
try:
return _read_semantic_result_cached(str(path), _result_signature(path))
except (SemanticSlamReplayError, OSError, ValueError, zipfile.BadZipFile):
raise HTTPException(status_code=404, detail="E47 result не найден") from None
def point_ledger(result_id: str) -> tuple[SemanticSlamReplayResult, _SemanticPointLedger]:
frozen = result(result_id)
try:
signature = _result_signature(frozen.result_root)
return frozen, _read_point_ledger_cached(str(frozen.result_root), signature)
except (SemanticSlamReplayError, OSError, ValueError, zipfile.BadZipFile):
raise HTTPException(
status_code=503,
detail="E47 semantic timeline не прошёл проверку",
) from None
@router.get("/results")
def list_results(limit: int = Query(default=1, ge=1, le=10)) -> dict[str, object]:
candidates = _candidates(root_provider)
items: list[dict[str, object]] = []
invalid_total = 0
for candidate in candidates:
if len(items) >= limit:
break
try:
items.append(_project_result(result(candidate.name)))
except (HTTPException, OSError, ValueError, json.JSONDecodeError):
invalid_total += 1
return {
"schema_version": E47_SEMANTIC_SLAM_CATALOG_SCHEMA,
"configured": _configured_root(root_provider) is not None,
"items": items,
"candidate_total": len(candidates),
"invalid_total": invalid_total,
"access": "read-only-diagnostic-shadow",
}
@router.get("/results/{result_id}/timeline/chunk")
def get_timeline_chunk(
result_id: str,
start: int = Query(default=0, ge=0),
count: int = Query(
default=12,
ge=1,
le=E47_SEMANTIC_SLAM_MAX_CHUNK_FRAMES,
),
) -> dict[str, object]:
frozen, ledger = point_ledger(result_id)
if (
not isinstance(start, int)
or isinstance(start, bool)
or not isinstance(count, int)
or isinstance(count, bool)
or start < 0
or not 1 <= count <= E47_SEMANTIC_SLAM_MAX_CHUNK_FRAMES
):
raise HTTPException(status_code=422, detail="Некорректный E47 timeline chunk")
if start >= ledger.frame_count:
raise HTTPException(status_code=404, detail="E47 timeline chunk не найден")
stop = min(start + count, ledger.frame_count)
try:
frames = [_project_frame(ledger, sequence) for sequence in range(start, stop)]
except ValueError:
raise HTTPException(
status_code=503,
detail="E47 semantic timeline не прошёл проверку",
) from None
return {
"schema_version": E47_SEMANTIC_SLAM_CHUNK_SCHEMA,
"result_id": frozen.result_id,
"start_sequence": start,
"frame_count": len(frames),
"next_sequence": stop if stop < ledger.frame_count else None,
"frames": frames,
"access": "read-only-diagnostic-shadow",
}
@router.get("/results/{result_id}/masks/{sequence}")
def get_mask(result_id: str, sequence: int) -> Response:
frozen = result(result_id)
frame_total = _frame_total(frozen)
if (
not isinstance(sequence, int)
or isinstance(sequence, bool)
or not 0 <= sequence < frame_total
):
raise HTTPException(status_code=404, detail="E47 semantic mask не найдена")
try:
signature = _result_signature(frozen.result_root)
frozen = _read_semantic_result_cached(str(frozen.result_root), signature)
payload = _read_mask(frozen, sequence)
if _result_signature(frozen.result_root) != signature:
raise ValueError("E47 result changed during mask read")
except (
SemanticSlamReplayError,
OSError,
KeyError,
ValueError,
RuntimeError,
zipfile.BadZipFile,
):
raise HTTPException(
status_code=503,
detail="E47 semantic mask не прошла проверку",
) from None
digest = hashlib.sha256(payload).hexdigest()
return Response(
content=payload,
media_type="image/png",
headers={
"Cache-Control": "private, max-age=31536000, immutable",
"ETag": f'"{digest}"',
"X-Content-Type-Options": "nosniff",
},
)
return router
@lru_cache(maxsize=4)
def _read_semantic_result_cached(
root_value: str,
signature: tuple[int, ...],
) -> SemanticSlamReplayResult:
del signature
return read_semantic_slam_replay_result(Path(root_value))
@lru_cache(maxsize=2)
def _read_point_ledger_cached(
root_value: str,
signature: tuple[int, ...],
) -> _SemanticPointLedger:
frozen = _read_semantic_result_cached(root_value, signature)
path = frozen.result_root / SEMANTIC_SLAM_POINTS_NAME
required = {
"frame_offsets",
"point_labels",
"point_status_codes",
"frame_source_point_counts",
"frame_labeled_point_counts",
"frame_ambiguous_point_counts",
"frame_unprojected_point_counts",
"frame_absent_point_counts",
}
with np.load(path, allow_pickle=False) as archive:
if not required.issubset(archive.files):
raise ValueError("E47 semantic point arrays are incomplete")
ledger = _SemanticPointLedger(
frame_offsets=_frozen_int64(archive["frame_offsets"]),
point_labels=_frozen_uint8(archive["point_labels"]),
point_status_codes=_frozen_uint8(archive["point_status_codes"]),
frame_source_point_counts=_frozen_int32(archive["frame_source_point_counts"]),
frame_labeled_point_counts=_frozen_int32(archive["frame_labeled_point_counts"]),
frame_ambiguous_point_counts=_frozen_int32(archive["frame_ambiguous_point_counts"]),
frame_unprojected_point_counts=_frozen_int32(archive["frame_unprojected_point_counts"]),
frame_absent_point_counts=_frozen_int32(archive["frame_absent_point_counts"]),
)
_validate_point_ledger(ledger, _frame_total(frozen))
_validate_class_status_bindings(ledger, _read_taxonomy(frozen))
return ledger
def _project_result(result: SemanticSlamReplayResult) -> dict[str, object]:
if result.status != PUBLICATION_STATUS:
raise ValueError("E47 publication status changed")
identity = _object(result.manifest.get("identity"), "E47 identity")
provider = _object(identity.get("semantic_provider"), "E47 provider")
temporal_binding = _object(
identity.get("temporal_binding"),
"E47 temporal binding",
)
authority = _object(identity.get("authority"), "E47 authority")
if (
authority.get("ground_truth") is not False
or authority.get("semantic_authority") != "diagnostic-only"
or authority.get("navigation_or_safety_accepted") is not False
or authority.get("actuation_allowed") is not False
):
raise ValueError("E47 authority changed")
taxonomy = _read_taxonomy(result)
return {
"schema_version": E47_SEMANTIC_SLAM_VIEW_SCHEMA,
"result_id": result.result_id,
"created_at_utc": result.manifest["created_at_utc"],
"status": E47_SEMANTIC_SLAM_VIEW_STATUS,
"profile_id": identity["profile_id"],
"base_m4_result_id": identity["base_m4_result_id"],
"semantic_result_id": identity["semantic_result_id"],
"geometry_result_id": identity["geometry_result_id"],
"source_pack_id": identity["source_pack_id"],
"calibration_content_sha256": identity["calibration_content_sha256"],
"provider": {
"provider_id": provider["provider_id"],
"model_id": provider["model_id"],
"model_revision": provider["model_revision"],
"model_weights_sha256": provider["model_weights_sha256"],
"preprocess_id": provider["preprocess_id"],
},
"temporal_binding": copy.deepcopy(temporal_binding),
"taxonomy": taxonomy,
"metrics": copy.deepcopy(result.metrics),
"acceptance": {
"artifact_contract_passed": True,
"frame_accounting_passed": True,
"point_accounting_passed": True,
"observation_binding_passed": True,
"temporal_binding_passed": True,
"independent_semantic_truth_passed": False,
"provider_promoted": False,
},
"limitations": copy.deepcopy(result.report["limitations"]),
"ground_truth": False,
"semantic_authority": "diagnostic-only",
"navigation_or_safety_accepted": False,
"actuation_allowed": False,
"access": "read-only-diagnostic-shadow",
}
def _project_frame(ledger: _SemanticPointLedger, sequence: int) -> dict[str, object]:
offset = int(ledger.frame_offsets[sequence])
stop = int(ledger.frame_offsets[sequence + 1])
labels = ledger.point_labels[offset:stop].astype(np.int16)
statuses = ledger.point_status_codes[offset:stop]
unavailable = np.isin(
statuses,
(
int(SemanticEvidenceStatus.ABSENT),
int(SemanticEvidenceStatus.UNPROJECTED),
),
)
labels[unavailable] = -1
counts = {
"labeled": int(ledger.frame_labeled_point_counts[sequence]),
"ambiguous": int(ledger.frame_ambiguous_point_counts[sequence]),
"unprojected": int(ledger.frame_unprojected_point_counts[sequence]),
"absent": int(ledger.frame_absent_point_counts[sequence]),
}
actual_counts = {
"labeled": int(np.count_nonzero(statuses == int(SemanticEvidenceStatus.LABELED))),
"ambiguous": int(np.count_nonzero(statuses == int(SemanticEvidenceStatus.AMBIGUOUS))),
"unprojected": int(np.count_nonzero(statuses == int(SemanticEvidenceStatus.UNPROJECTED))),
"absent": int(np.count_nonzero(statuses == int(SemanticEvidenceStatus.ABSENT))),
}
if counts != actual_counts or sum(counts.values()) != stop - offset:
raise ValueError("E47 frame point accounting changed")
return {
"schema_version": E47_SEMANTIC_SLAM_FRAME_SCHEMA,
"sequence": sequence,
"source_point_count": int(ledger.frame_source_point_counts[sequence]),
"class_ids": labels.tolist(),
"status_codes": statuses.tolist(),
"counts": counts,
}
def _read_taxonomy(result: SemanticSlamReplayResult) -> list[dict[str, object]]:
path = result.result_root / SEMANTIC_SLAM_TAXONOMY_NAME
if not path.is_file() or path.is_symlink() or path.stat().st_size > _MAX_TAXONOMY_BYTES:
raise ValueError("E47 taxonomy is invalid")
payload = path.read_bytes()
identity = _object(result.manifest.get("identity"), "E47 identity")
if hashlib.sha256(payload).hexdigest() != identity.get("taxonomy_sha256"):
raise ValueError("E47 taxonomy identity changed")
document = json.loads(payload)
if not isinstance(document, dict) or set(document) != {"schema_version", "classes"}:
raise ValueError("E47 taxonomy contract changed")
if document.get("schema_version") != SEMANTIC_SLAM_TAXONOMY_SCHEMA:
raise ValueError("E47 taxonomy schema changed")
classes = document.get("classes")
if not isinstance(classes, list) or not classes:
raise ValueError("E47 taxonomy classes are invalid")
for item in classes:
if not isinstance(item, dict) or set(item) != {
"class_id",
"label",
"disposition",
"color_rgb",
}:
raise ValueError("E47 taxonomy class changed")
return copy.deepcopy(classes)
def _read_mask(result: SemanticSlamReplayResult, sequence: int) -> bytes:
archive_path = result.result_root / SEMANTIC_SLAM_MASKS_NAME
if not archive_path.is_file() or archive_path.is_symlink():
raise ValueError("E47 semantic mask archive is invalid")
member_name = f"semantic-masks/frame-{sequence + 1:06d}.png"
with zipfile.ZipFile(archive_path, mode="r") as archive:
info = archive.getinfo(member_name)
if info.is_dir() or not 0 < info.file_size <= _MAX_MASK_BYTES:
raise ValueError("E47 semantic mask member is invalid")
payload = archive.read(info)
if len(payload) != info.file_size or not payload.startswith(b"\x89PNG\r\n\x1a\n"):
raise ValueError("E47 semantic mask payload is invalid")
return payload
def _validate_point_ledger(ledger: _SemanticPointLedger, frame_total: int) -> None:
arrays = (
ledger.frame_source_point_counts,
ledger.frame_labeled_point_counts,
ledger.frame_ambiguous_point_counts,
ledger.frame_unprojected_point_counts,
ledger.frame_absent_point_counts,
)
if (
ledger.frame_offsets.ndim != 1
or ledger.frame_offsets.shape != (frame_total + 1,)
or int(ledger.frame_offsets[0]) != 0
or np.any(np.diff(ledger.frame_offsets) < 0)
or ledger.point_labels.ndim != 1
or ledger.point_status_codes.shape != ledger.point_labels.shape
or int(ledger.frame_offsets[-1]) != ledger.point_labels.size
or any(value.ndim != 1 or value.shape != (frame_total,) for value in arrays)
or np.any(np.asarray(arrays) < 0)
or not np.array_equal(
np.diff(ledger.frame_offsets),
ledger.frame_source_point_counts,
)
):
raise ValueError("E47 semantic point ledger changed")
valid_statuses = {int(status) for status in SemanticEvidenceStatus}
if set(int(value) for value in np.unique(ledger.point_status_codes)) - valid_statuses:
raise ValueError("E47 semantic status changed")
expected_total = (
ledger.frame_labeled_point_counts
+ ledger.frame_ambiguous_point_counts
+ ledger.frame_unprojected_point_counts
+ ledger.frame_absent_point_counts
)
if not np.array_equal(expected_total, ledger.frame_source_point_counts):
raise ValueError("E47 semantic frame accounting changed")
def _validate_class_status_bindings(
ledger: _SemanticPointLedger,
taxonomy: list[dict[str, object]],
) -> None:
dispositions: dict[int, str] = {}
for item in taxonomy:
class_id = item.get("class_id")
disposition = item.get("disposition")
if (
not isinstance(class_id, int)
or isinstance(class_id, bool)
or not 0 <= class_id <= 255
or disposition not in {"labeled", "ambiguous"}
or class_id in dispositions
):
raise ValueError("E47 semantic taxonomy binding changed")
dispositions[class_id] = str(disposition)
unavailable = np.isin(
ledger.point_status_codes,
(
int(SemanticEvidenceStatus.ABSENT),
int(SemanticEvidenceStatus.UNPROJECTED),
),
)
if np.any(ledger.point_labels[unavailable] != 0):
raise ValueError("E47 unavailable semantic point carried a class")
for status, disposition in (
(SemanticEvidenceStatus.AMBIGUOUS, "ambiguous"),
(SemanticEvidenceStatus.LABELED, "labeled"),
):
class_ids = np.unique(ledger.point_labels[ledger.point_status_codes == int(status)])
if any(dispositions.get(int(class_id)) != disposition for class_id in class_ids):
raise ValueError("E47 semantic point status disagrees with taxonomy")
def _frame_total(result: SemanticSlamReplayResult) -> int:
frames = _object(result.metrics.get("frames"), "E47 frame metrics")
value = frames.get("total")
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
raise ValueError("E47 frame count changed")
return value
def _frozen_int64(value: npt.ArrayLike) -> Int64Array:
array = np.array(value, dtype=np.int64, order="C", copy=True)
array.setflags(write=False)
return array
def _frozen_int32(value: npt.ArrayLike) -> Int32Array:
array = np.array(value, dtype=np.int32, order="C", copy=True)
array.setflags(write=False)
return array
def _frozen_uint8(value: npt.ArrayLike) -> UInt8Array:
array = np.array(value, dtype=np.uint8, order="C", copy=True)
array.setflags(write=False)
return array
def _configured_root(provider: RootProvider) -> Path | None:
value = provider()
if value is None:
return None
candidate = value.expanduser().absolute()
if candidate.is_symlink():
return None
try:
root = candidate.resolve(strict=True)
except OSError:
return None
return root if root.is_dir() else None
def _result_signature(root: Path) -> tuple[int, ...]:
signature: list[int] = []
for name in _EXPECTED_ARTIFACTS:
path = root / name
if not path.is_file() or path.is_symlink():
raise ValueError("E47 result artifact is invalid")
stat = path.stat()
signature.extend((stat.st_ino, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns))
return tuple(signature)
def _candidates(provider: RootProvider) -> list[Path]:
root = _configured_root(provider)
if root is None:
return []
try:
return sorted(
(
item
for item in root.iterdir()
if item.is_dir() and not item.is_symlink() and _RESULT_ID.fullmatch(item.name)
),
key=lambda item: item.stat().st_mtime_ns,
reverse=True,
)
except OSError:
return []
def _object(value: object, label: str) -> dict[str, object]:
if not isinstance(value, dict):
raise ValueError(f"{label} is invalid")
return value
__all__ = [
"E47_SEMANTIC_SLAM_CATALOG_SCHEMA",
"E47_SEMANTIC_SLAM_CHUNK_SCHEMA",
"E47_SEMANTIC_SLAM_FRAME_SCHEMA",
"E47_SEMANTIC_SLAM_MAX_CHUNK_FRAMES",
"E47_SEMANTIC_SLAM_VIEW_SCHEMA",
"build_e47_semantic_slam_router",
]
+272
View File
@@ -0,0 +1,272 @@
from __future__ import annotations
import hashlib
import json
import zipfile
from pathlib import Path
import numpy as np
import pytest
from fastapi import HTTPException
from fastapi.routing import APIRoute
from k1link.perception.semantic_slam_replay import (
PUBLICATION_STATUS,
SemanticSlamReplayResult,
)
from k1link.web import e47_semantic_slam_api as api
RESULT_ID = f"e47-semantic-slam-{'a' * 64}"
M4_RESULT_ID = f"m4-threat-replay-{'b' * 64}"
PNG_0 = b"\x89PNG\r\n\x1a\nsealed-mask-zero"
PNG_1 = b"\x89PNG\r\n\x1a\nsealed-mask-one"
def _endpoint(path: str, root: Path):
router = api.build_e47_semantic_slam_router(root_provider=lambda: root)
return next(
route.endpoint
for route in router.routes
if isinstance(route, APIRoute) and route.path == path
)
@pytest.fixture(autouse=True)
def _clear_api_caches() -> None:
api._read_semantic_result_cached.cache_clear()
api._read_point_ledger_cached.cache_clear()
yield
api._read_semantic_result_cached.cache_clear()
api._read_point_ledger_cached.cache_clear()
@pytest.fixture
def publication(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> tuple[Path, Path]:
root = tmp_path / "semantic-slam-results"
result_root = root / RESULT_ID
result_root.mkdir(parents=True)
taxonomy = {
"schema_version": "missioncore.e47-semantic-taxonomy/v1",
"classes": [
{
"class_id": 0,
"label": "ambiguous",
"disposition": "ambiguous",
"color_rgb": [0, 0, 0],
},
{
"class_id": 1,
"label": "road",
"disposition": "labeled",
"color_rgb": [128, 64, 128],
},
],
}
taxonomy_payload = json.dumps(taxonomy, separators=(",", ":")).encode()
(result_root / "taxonomy.json").write_bytes(taxonomy_payload)
np.savez(
result_root / "semantic-points.npz",
frame_offsets=np.asarray([0, 4, 7], dtype=np.int64),
point_labels=np.asarray([1, 0, 0, 0, 0, 1, 1], dtype=np.uint8),
point_status_codes=np.asarray([3, 2, 1, 0, 2, 3, 3], dtype=np.uint8),
frame_source_point_counts=np.asarray([4, 3], dtype=np.int32),
frame_labeled_point_counts=np.asarray([1, 2], dtype=np.int32),
frame_ambiguous_point_counts=np.asarray([1, 1], dtype=np.int32),
frame_unprojected_point_counts=np.asarray([1, 0], dtype=np.int32),
frame_absent_point_counts=np.asarray([1, 0], dtype=np.int32),
)
with zipfile.ZipFile(result_root / "semantic-masks.zip", mode="w") as archive:
archive.writestr("semantic-masks/frame-000001.png", PNG_0)
archive.writestr("semantic-masks/frame-000002.png", PNG_1)
for name, payload in (
("manifest.json", b"fixture-manifest"),
("report.json", b"fixture-report"),
("semantic-observations.jsonl", b"fixture-observations\n"),
):
(result_root / name).write_bytes(payload)
metrics = {
"frames": {"total": 2, "mask_available": 2, "source_available": 2},
"points": {
"total": 7,
"projected": 5,
"labeled": 3,
"ambiguous": 2,
"unprojected": 1,
"absent": 1,
},
"observations": {
"total": 3,
"labeled": 1,
"ambiguous": 1,
"unprojected": 1,
"absent": 0,
},
"runtime": {"elapsed_ms": 10.0, "frames_per_second": 200.0},
}
identity = {
"profile_id": "ravnoves00-eomt-kb4-slam-shadow/v1",
"base_m4_result_id": M4_RESULT_ID,
"semantic_result_id": f"result-{'c' * 64}",
"geometry_result_id": f"m4-geometry-replay-{'d' * 64}",
"source_pack_id": "ravnoves00-source-pack/v1",
"calibration_content_sha256": "e" * 64,
"taxonomy_sha256": hashlib.sha256(taxonomy_payload).hexdigest(),
"semantic_provider": {
"provider_id": "eomt-cityscapes-semantic-control/v1",
"model_id": "tue-mps/eomt",
"model_revision": "f" * 40,
"model_weights_sha256": "1" * 64,
"preprocess_id": "raw-kb4-valid-fov-semantic/v1",
"role": "fixed-control-not-selected-production-provider",
},
"temporal_binding": {
"semantic_to_camera": "exact-sequence-and-session-time",
"camera_to_lidar": "accepted-e6-nearest-host-arrival-best-effort",
"clock_basis": "recorded-host-monotonic-arrival",
"maximum_lidar_camera_delta_ms": 100.0,
"maximum_pose_point_delta_ms": 100.0,
"physical_synchronization_proven": False,
},
"authority": {
"ground_truth": False,
"semantic_authority": "diagnostic-only",
"navigation_or_safety_accepted": False,
"actuation_allowed": False,
},
}
frozen = SemanticSlamReplayResult(
result_id=RESULT_ID,
result_root=result_root,
status=PUBLICATION_STATUS,
metrics=metrics,
report={
"status": PUBLICATION_STATUS,
"metrics": metrics,
"limitations": ["No independent semantic truth."],
},
manifest={
"result_id": RESULT_ID,
"created_at_utc": "2026-08-06T06:30:00.000Z",
"identity": identity,
},
)
monkeypatch.setattr(api, "_read_semantic_result_cached", lambda *_: frozen)
return root, result_root
def test_catalog_projects_exact_diagnostic_only_view(
publication: tuple[Path, Path],
) -> None:
root, _ = publication
list_results = _endpoint("/api/v1/laboratory/e47-semantic-slam/results", root)
catalog = list_results(limit=1)
assert catalog["schema_version"] == "missioncore.e47-semantic-slam-catalog/v1"
assert catalog["candidate_total"] == 1
assert catalog["invalid_total"] == 0
item = catalog["items"][0]
assert item["schema_version"] == "missioncore.e47-semantic-slam-view/v1"
assert item["result_id"] == RESULT_ID
assert item["status"] == "diagnostic-semantic-slam-shadow"
assert item["base_m4_result_id"] == M4_RESULT_ID
assert item["provider"]["provider_id"] == "eomt-cityscapes-semantic-control/v1"
assert item["temporal_binding"]["semantic_to_camera"] == (
"exact-sequence-and-session-time"
)
assert item["temporal_binding"]["physical_synchronization_proven"] is False
assert [entry["label"] for entry in item["taxonomy"]] == ["ambiguous", "road"]
assert item["acceptance"] == {
"artifact_contract_passed": True,
"frame_accounting_passed": True,
"point_accounting_passed": True,
"observation_binding_passed": True,
"temporal_binding_passed": True,
"independent_semantic_truth_passed": False,
"provider_promoted": False,
}
assert item["semantic_authority"] == "diagnostic-only"
assert item["navigation_or_safety_accepted"] is False
assert item["actuation_allowed"] is False
def test_timeline_chunk_preserves_point_index_space_and_unavailable_sentinel(
publication: tuple[Path, Path],
) -> None:
root, _ = publication
get_chunk = _endpoint(
"/api/v1/laboratory/e47-semantic-slam/results/{result_id}/timeline/chunk",
root,
)
chunk = get_chunk(RESULT_ID, start=0, count=2)
assert chunk["schema_version"] == "missioncore.e47-semantic-slam-chunk/v1"
assert chunk["frame_count"] == 2
assert chunk["next_sequence"] is None
first = chunk["frames"][0]
assert first == {
"schema_version": "missioncore.e47-semantic-slam-frame/v1",
"sequence": 0,
"source_point_count": 4,
"class_ids": [1, 0, -1, -1],
"status_codes": [3, 2, 1, 0],
"counts": {"labeled": 1, "ambiguous": 1, "unprojected": 1, "absent": 1},
}
assert chunk["frames"][1]["class_ids"] == [0, 1, 1]
def test_timeline_chunk_rejects_out_of_range_and_oversized_requests(
publication: tuple[Path, Path],
) -> None:
root, _ = publication
get_chunk = _endpoint(
"/api/v1/laboratory/e47-semantic-slam/results/{result_id}/timeline/chunk",
root,
)
with pytest.raises(HTTPException) as out_of_range:
get_chunk(RESULT_ID, start=2, count=1)
assert out_of_range.value.status_code == 404
with pytest.raises(HTTPException) as oversized:
get_chunk(RESULT_ID, start=0, count=25)
assert oversized.value.status_code == 422
def test_mask_endpoint_streams_exact_png_with_immutable_identity(
publication: tuple[Path, Path],
) -> None:
root, _ = publication
get_mask = _endpoint(
"/api/v1/laboratory/e47-semantic-slam/results/{result_id}/masks/{sequence}",
root,
)
response = get_mask(RESULT_ID, 1)
assert response.body == PNG_1
assert response.media_type == "image/png"
assert response.headers["cache-control"] == "private, max-age=31536000, immutable"
assert response.headers["etag"] == f'"{hashlib.sha256(PNG_1).hexdigest()}"'
assert response.headers["x-content-type-options"] == "nosniff"
def test_result_resolution_rejects_invalid_id_and_symlink(tmp_path: Path) -> None:
root = tmp_path / "results"
root.mkdir()
outside = tmp_path / RESULT_ID
outside.mkdir()
(root / RESULT_ID).symlink_to(outside, target_is_directory=True)
get_chunk = _endpoint(
"/api/v1/laboratory/e47-semantic-slam/results/{result_id}/timeline/chunk",
root,
)
with pytest.raises(HTTPException) as invalid:
get_chunk("../escape", start=0, count=1)
assert invalid.value.status_code == 404
with pytest.raises(HTTPException) as linked:
get_chunk(RESULT_ID, start=0, count=1)
assert linked.value.status_code == 404
+2 -1
View File
@@ -127,10 +127,11 @@ def test_product_registry_declares_every_advanced_evidence_source() -> None:
repository_root / "config" / "laboratories"
)
assert len(registry.definitions) == 32
assert len(registry.definitions) == 33
assert {item.work_id for item in registry.definitions} >= {
"e31-source-binding",
"e46j-raw-fisheye-realtime",
"e47-semantic-slam-shadow",
"l3-pointpillars-visual-audit",
"l31-pointpillars-ravnoves",
"l32-pointpillars-camera-review",
+9 -1
View File
@@ -94,8 +94,16 @@ def test_repository_registry_classifies_every_evidence_definition() -> None:
"e33-worker-shadow",
"e35-degradation-recovery",
"e46j-raw-fisheye-realtime",
"e47-semantic-slam-shadow",
}
assert all(row.lifecycle == "canonical" for row in execution.definitions)
by_work_id = {row.work_id: row for row in execution.definitions}
assert by_work_id["e47-semantic-slam-shadow"].lifecycle == "experimental"
assert by_work_id["e47-semantic-slam-shadow"].isolation == "bounded-adapter"
assert all(
row.lifecycle == "canonical"
for row in execution.definitions
if row.work_id != "e47-semantic-slam-shadow"
)
assert len(execution.definitions) + len(execution.legacy_work_ids) == len(
evidence.definitions
)
+306
View File
@@ -0,0 +1,306 @@
from __future__ import annotations
from dataclasses import replace
import numpy as np
import pytest
from k1link.perception.contracts import (
EvidenceBasis,
EvidenceCurrentness,
MetricGeometry,
ObstacleObservation,
)
from k1link.perception.geometry_math import ProjectedPointCloud
from k1link.perception.semantic_fusion import (
NO_SEMANTIC_CLASS_ID,
SemanticClassDefinition,
SemanticClassDisposition,
SemanticEvidenceAuthority,
SemanticEvidenceStatus,
SemanticFusionError,
SemanticMask,
fuse_semantic_diagnostics,
)
def _classes() -> tuple[SemanticClassDefinition, ...]:
return (
SemanticClassDefinition(1, "road"),
SemanticClassDefinition(2, "car"),
SemanticClassDefinition(
255,
"void / uncertain",
SemanticClassDisposition.AMBIGUOUS,
),
)
def _mask(*, source_id: str = "RAVNOVES00", frame_id: str = "frame-000014") -> SemanticMask:
return SemanticMask(
source_id=source_id,
frame_id=frame_id,
provider_id="semantic-provider/v1",
model_id="semantic-model/v1",
preprocess_id="raw-kb4-semantic/v1",
labels=np.asarray(
[
[1, 2, 255, 1],
[1, 1, 1, 1],
[1, 1, 1, 1],
],
dtype=np.uint8,
),
classes=_classes(),
)
def _projection() -> ProjectedPointCloud:
return ProjectedPointCloud(
pixels_xy=np.asarray(
[
[0.1, 0.1],
[1.2, 0.2],
[1.8, 0.8],
[2.1, 0.2],
[9.0, 9.0],
],
dtype=np.float64,
),
depths_m=np.asarray([2.0, 2.1, 2.2, 2.3, 2.4], dtype=np.float64),
source_indices=np.asarray([0, 1, 2, 3, 4], dtype=np.int64),
source_point_count=5,
camera_front_point_count=5,
)
def _geometry_observation(
*point_ids: int,
observation_id: str = "geometry-observation-1",
) -> ObstacleObservation:
return ObstacleObservation(
observation_id=observation_id,
occupancy_key=f"occupancy-{observation_id}",
source_id="RAVNOVES00",
frame_id="frame-000014",
evidence_time_ns=14_000_000_000,
basis=EvidenceBasis.LIDAR,
currentness=EvidenceCurrentness.CURRENT,
occupied_support=True,
source_point_ids=point_ids,
metric_geometry=MetricGeometry(
coordinate_frame="map",
centroid_xyz_m=(2.0, 0.0, 0.5),
range_m=2.0,
covariance_diagonal_m2=(0.1, 0.1, 0.1),
),
proposal_ids=(),
semantic_hint=None,
reason_codes=("qualified-lidar-points",),
)
def _camera_only_observation() -> ObstacleObservation:
return ObstacleObservation(
observation_id="camera-observation-1",
occupancy_key="occupancy-camera-observation-1",
source_id="RAVNOVES00",
frame_id="frame-000014",
evidence_time_ns=14_000_000_000,
basis=EvidenceBasis.CAMERA,
currentness=EvidenceCurrentness.CURRENT,
occupied_support=False,
source_point_ids=(),
metric_geometry=None,
proposal_ids=("proposal-1",),
semantic_hint=None,
reason_codes=("camera-only",),
)
def test_semantic_mask_is_strict_source_bound_uint8_and_immutable() -> None:
labels = np.asarray([[1, 2]], dtype=np.uint8)
semantic = SemanticMask(
source_id="RAVNOVES00",
frame_id="frame-000014",
provider_id="semantic-provider/v1",
model_id="semantic-model/v1",
preprocess_id="raw-kb4-semantic/v1",
labels=labels,
classes=_classes(),
)
labels[0, 0] = 2
assert semantic.labels.tolist() == [[1, 2]]
assert semantic.labels.flags.writeable is False
with pytest.raises(ValueError):
semantic.labels[0, 0] = 2
with pytest.raises(SemanticFusionError, match="uint8 HxW"):
replace(semantic, labels=np.asarray([[1, 2]], dtype=np.int64))
with pytest.raises(SemanticFusionError, match="undeclared"):
replace(semantic, labels=np.asarray([[1, 7]], dtype=np.uint8))
with pytest.raises(SemanticFusionError, match="unique"):
replace(
semantic,
classes=(SemanticClassDefinition(1, "road"), SemanticClassDefinition(1, "other")),
)
def test_mask_projection_keeps_absence_ambiguity_and_unprojected_separate() -> None:
result = fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(),
)
labels = result.point_labels
assert [labels.status_for(index) for index in range(5)] == [
SemanticEvidenceStatus.LABELED,
SemanticEvidenceStatus.LABELED,
SemanticEvidenceStatus.LABELED,
SemanticEvidenceStatus.AMBIGUOUS,
SemanticEvidenceStatus.UNPROJECTED,
]
assert [labels.class_id_for(index) for index in range(5)] == [1, 2, 2, 255, None]
assert [labels.label_for(index) for index in range(5)] == [
"road",
"car",
"car",
"void / uncertain",
None,
]
assert labels.class_ids.tolist() == [1, 2, 2, 255, NO_SEMANTIC_CLASS_ID]
assert labels.class_ids.flags.writeable is False
assert labels.status_codes.flags.writeable is False
assert result.authority is SemanticEvidenceAuthority.DIAGNOSTIC_ONLY
def test_observation_aggregation_is_detached_from_geometry_and_safety_authority() -> None:
observation = _geometry_observation(0, 1, 2, 4)
before = observation.to_dict()
result = fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(observation,),
)
evidence = result.observation_evidence[0]
assert observation.to_dict() == before
assert evidence.observation_id == observation.observation_id
assert evidence.occupancy_key == observation.occupancy_identity
assert evidence.status is SemanticEvidenceStatus.LABELED
assert evidence.dominant_class_id == 2
assert evidence.dominant_label == "car"
assert evidence.dominant_fraction_of_labeled == pytest.approx(2 / 3)
assert evidence.labeled_point_count == 3
assert evidence.unprojected_point_count == 1
assert evidence.semantic_coverage_fraction == pytest.approx(0.75)
assert evidence.authority is SemanticEvidenceAuthority.DIAGNOSTIC_ONLY
assert not hasattr(evidence, "occupied_support")
assert not hasattr(evidence, "motion")
assert not hasattr(evidence, "threat")
assert not hasattr(evidence, "actuation_allowed")
def test_tied_or_provider_ambiguous_labels_remain_ambiguous() -> None:
tied = _geometry_observation(0, 1, observation_id="geometry-tied")
provider_ambiguous = _geometry_observation(3, observation_id="geometry-void")
result = fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(tied, provider_ambiguous),
)
tie_evidence, void_evidence = result.observation_evidence
assert tie_evidence.status is SemanticEvidenceStatus.AMBIGUOUS
assert tie_evidence.reason_code == "semantic-label-majority-ambiguous"
assert tie_evidence.dominant_class_id is None
assert {item.label: item.point_count for item in tie_evidence.class_evidence} == {
"road": 1,
"car": 1,
}
assert void_evidence.status is SemanticEvidenceStatus.AMBIGUOUS
assert void_evidence.reason_code == "semantic-classes-ambiguous"
assert void_evidence.ambiguous_point_count == 1
assert void_evidence.class_evidence[0].disposition is SemanticClassDisposition.AMBIGUOUS
mixed = fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(_geometry_observation(1, 3, observation_id="geometry-mixed"),),
).observation_evidence[0]
assert mixed.status is SemanticEvidenceStatus.AMBIGUOUS
assert mixed.reason_code == "semantic-label-majority-ambiguous"
assert mixed.dominant_class_id is None
def test_missing_mask_and_pointless_geometry_have_distinct_outcomes() -> None:
observation = _geometry_observation(0, 1)
absent = fuse_semantic_diagnostics(
semantic_mask=None,
projected=_projection(),
observations=(observation,),
)
assert absent.mask_available is False
assert [absent.point_labels.status_for(index) for index in range(5)] == [
SemanticEvidenceStatus.ABSENT
] * 5
assert absent.observation_evidence[0].status is SemanticEvidenceStatus.ABSENT
assert absent.observation_evidence[0].absent_point_count == 2
unprojected = fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(_camera_only_observation(),),
)
evidence = unprojected.observation_evidence[0]
assert evidence.status is SemanticEvidenceStatus.UNPROJECTED
assert evidence.reason_code == "observation-has-no-source-points"
assert evidence.source_point_count == 0
def test_fusion_rejects_frame_escape_invalid_point_ids_and_duplicate_ownership() -> None:
observation = _geometry_observation(0)
with pytest.raises(SemanticFusionError, match="source frame"):
fuse_semantic_diagnostics(
semantic_mask=_mask(frame_id="frame-000015"),
projected=_projection(),
observations=(observation,),
)
with pytest.raises(SemanticFusionError, match="outside the source frame"):
fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(_geometry_observation(5),),
)
with pytest.raises(SemanticFusionError, match="duplicate observation ownership"):
fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=_projection(),
observations=(
observation,
_geometry_observation(0, observation_id="geometry-observation-2"),
),
)
with pytest.raises(SemanticFusionError, match="escaped their source frame"):
fuse_semantic_diagnostics(
semantic_mask=None,
projected=_projection(),
observations=(
observation,
replace(
_geometry_observation(1, observation_id="geometry-observation-2"),
frame_id="frame-000015",
),
),
)
def test_projection_validator_rejects_malformed_existing_contract_values() -> None:
malformed = replace(
_projection(),
source_indices=np.asarray([0, 1, 2, 3, 5], dtype=np.int64),
)
with pytest.raises(SemanticFusionError, match="outside the source frame"):
fuse_semantic_diagnostics(
semantic_mask=_mask(),
projected=malformed,
observations=(),
)
+506
View File
@@ -0,0 +1,506 @@
from __future__ import annotations
import hashlib
import io
import json
import tarfile
from dataclasses import dataclass, replace
from pathlib import Path
import numpy as np
import pytest
from PIL import Image
import k1link.perception.semantic_slam_replay as replay
from k1link.perception.contracts import (
EvidenceBasis,
EvidenceCurrentness,
MetricGeometry,
ObstacleObservation,
)
from k1link.perception.geometry import GeometryFrame, RecordedFrameTemporalBinding
from k1link.perception.geometry_math import Kb4ProjectionProfile
from k1link.perception.geometry_replay import GeometryReplayResult
from k1link.perception.semantic_fusion import (
SemanticClassDisposition,
SemanticEvidenceStatus,
)
from k1link.perception.threat_replay import ThreatReplayResult
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:
return hashlib.sha256(path.read_bytes()).hexdigest()
def _write_jsonl(path: Path, rows: list[dict[str, object]]) -> None:
path.write_bytes(b"".join(_canonical_json(row) + b"\n" for row in rows))
def _png(labels: np.ndarray) -> bytes:
buffer = io.BytesIO()
Image.fromarray(labels, mode="L").save(buffer, format="PNG")
return buffer.getvalue()
@dataclass(frozen=True)
class _Store:
frame: GeometryFrame
lidar_delta_ms: float = 4.0
pose_delta_ms: float = 2.0
def frame_for_index(self, frame_index: int) -> GeometryFrame | None:
return self.frame if frame_index == 0 else None
def temporal_binding_for_index(self, frame_index: int) -> RecordedFrameTemporalBinding:
return RecordedFrameTemporalBinding(
frame_index=frame_index,
source_time_ns=(frame_index + 1) * 1_000_000_000,
source_available=frame_index == 0,
lidar_camera_delta_ms=self.lidar_delta_ms if frame_index == 0 else None,
pose_point_delta_ms=self.pose_delta_ms if frame_index == 0 else None,
)
def _observation() -> ObstacleObservation:
return ObstacleObservation(
observation_id="frame-000000:obstacle-0",
occupancy_key="frame-000000:obstacle-0",
source_id="RAVNOVES00",
frame_id="frame-000000",
evidence_time_ns=1,
basis=EvidenceBasis.FUSED,
currentness=EvidenceCurrentness.CURRENT,
occupied_support=True,
source_point_ids=(0, 1),
metric_geometry=MetricGeometry(
coordinate_frame="map",
centroid_xyz_m=(0.0, 0.0, 1.0),
range_m=1.0,
covariance_diagonal_m2=(0.0, 0.0, 0.0),
),
proposal_ids=("proposal-0",),
semantic_hint="car",
reason_codes=("current-test-support",),
)
def _fixture(tmp_path: Path) -> replay._AdmittedInputs:
source = tmp_path / "source"
source.mkdir()
profile_path = source / "profile.json"
profile_path.write_text('{"fixture":true}\n', encoding="utf-8")
authority = {
"ground_truth": False,
"physical_live": False,
"commands_enabled": False,
"actuation_allowed": False,
"navigation_or_safety_accepted": False,
"semantic_authority": "diagnostic-only",
}
fusion = {
"projection": "factory-kb4-current-increment/v1",
"point_index_space": "frame-local-source-point-id/v1",
"observation_aggregation": "dominant-labeled-majority-diagnostic/v1",
"unprojected_status": "unprojected",
"semantic_absence_means_free": False,
"semantic_can_create_obstacle": False,
"semantic_can_change_identity": False,
"semantic_can_change_metric_geometry": False,
"semantic_can_change_occupancy": False,
"semantic_can_change_motion": False,
"semantic_can_change_threat": False,
}
profile = replay._SemanticSlamProfile(
path=profile_path,
sha256=_sha256(profile_path),
profile_id="fixture-semantic-slam/v1",
source_id="RAVNOVES00",
session_id="fixture-session",
frame_count=2,
image_width=4,
image_height=4,
source_pack_id="fixture-source-pack",
source_pack_sha256="1" * 64,
calibration_sha256="2" * 64,
provider_id="fixture-semantic-provider/v1",
model_id="fixture-model",
model_revision="fixture-revision",
model_weights_sha256="3" * 64,
preprocess_id="fixture-preprocess/v1",
mask_metadata_schema_version="missioncore.panoptic-frame/v1",
mask_payload={
"media_type": "image/png",
"encoding": "uint8-class-id",
"width": 4,
"height": 4,
"sequence_binding": "sequence-0-to-frame-000001",
},
provider_role="fixed-control-not-selected-production-provider",
classes=(
replay._TaxonomyClass(
class_id=0,
label="outside_valid_fov",
disposition=SemanticClassDisposition.AMBIGUOUS,
color_rgb=(0, 0, 0),
),
replay._TaxonomyClass(
class_id=4,
label="car",
disposition=SemanticClassDisposition.LABELED,
color_rgb=(0, 0, 142),
),
),
fusion=fusion,
temporal_binding={
"semantic_to_camera": "exact-sequence-and-session-time",
"camera_to_lidar": "accepted-e6-nearest-host-arrival-best-effort",
"clock_basis": "recorded-host-monotonic-arrival",
"maximum_lidar_camera_delta_ms": 100.0,
"maximum_pose_point_delta_ms": 100.0,
"physical_synchronization_proven": False,
},
acceptance={
"full_frame_accounting_required": True,
"point_accounting_required": True,
"observation_binding_required": True,
"exact_mask_archive_required": True,
"independent_semantic_truth_required_for_provider_promotion": True,
},
authority=authority,
)
semantic_root = source / "semantic"
semantic_root.mkdir()
result_json = semantic_root / "result.json"
result_json.write_text('{"sealed":"fixture"}\n', encoding="utf-8")
masks = []
first = np.zeros((4, 4), dtype=np.uint8)
first[2, 2] = 4
masks.append(_png(first))
masks.append(_png(np.zeros((4, 4), dtype=np.uint8)))
mask_archive = semantic_root / "masks.tar.gz"
with tarfile.open(mask_archive, mode="w:gz") as archive:
directory = tarfile.TarInfo("semantic-masks")
directory.type = tarfile.DIRTYPE
archive.addfile(directory)
for sequence, payload in enumerate(masks, start=1):
member = tarfile.TarInfo(f"semantic-masks/frame-{sequence:06d}.png")
member.size = len(payload)
archive.addfile(member, io.BytesIO(payload))
semantic_frames = semantic_root / "frames.jsonl"
_write_jsonl(
semantic_frames,
[
{
"schema_version": "missioncore.panoptic-frame/v1",
"frame_index": 0,
"sequence": 1,
"session_seconds": 1.0,
"instances": [],
"semantic_classes": [
{
"id": 4,
"label": "car",
"pixels": 1,
"fraction_of_valid_fov": 0.0625,
}
],
},
{
"schema_version": "missioncore.panoptic-frame/v1",
"frame_index": 1,
"sequence": 2,
"session_seconds": 2.0,
"instances": [],
"semantic_classes": [],
},
],
)
semantic = replay._SemanticUpstream(
result_id="result-" + "4" * 64,
result_root=semantic_root,
result_manifest_sha256=_sha256(result_json),
frames_path=semantic_frames,
frames_sha256=_sha256(semantic_frames),
masks_path=mask_archive,
masks_sha256=_sha256(mask_archive),
created_at_utc="2026-08-06T00:00:00.000Z",
job_id="fixture-job",
input_sha256="5" * 64,
source_id="sensor.camera.right",
session_id="fixture-session",
calibration_sha256="2" * 64,
configuration_profile_sha256="6" * 64,
model_id="fixture-model",
model_revision="fixture-revision",
model_weights_sha256="3" * 64,
)
observation = _observation()
geometry_root = source / "geometry"
geometry_root.mkdir()
geometry_frames = geometry_root / "frames.jsonl"
_write_jsonl(
geometry_frames,
[
{
"schema_version": "missioncore.perception-geometry-replay-frame/v1",
"sequence": 0,
"frame_id": "frame-000000",
"source_available": True,
"observations": [observation.to_dict()],
},
{
"schema_version": "missioncore.perception-geometry-replay-frame/v1",
"sequence": 1,
"frame_id": "frame-000001",
"source_available": False,
"observations": [],
},
],
)
geometry_sha256 = _sha256(geometry_frames)
geometry = GeometryReplayResult(
result_id="m4-geometry-replay-" + "7" * 64,
result_root=geometry_root,
accepted=True,
metrics={"frames": {"total": 2}},
report={},
manifest={"identity": {"frames_sha256": geometry_sha256}},
)
threat_root = source / "threat"
threat_root.mkdir()
threat_frames = threat_root / "frames.jsonl"
_write_jsonl(threat_frames, [{"decision": "unchanged"}])
threat_sha256 = _sha256(threat_frames)
threat = ThreatReplayResult(
result_id="m4-threat-replay-" + "8" * 64,
result_root=threat_root,
accepted=True,
metrics={"frames": {"total": 2}},
report={},
manifest={"identity": {"frames_sha256": threat_sha256}},
)
transform = np.eye(4, dtype=np.float64)
frame = GeometryFrame(
frame_index=0,
points_map=np.asarray(((0.0, 0.0, 1.0), (100.0, 0.0, 1.0)), dtype=np.float64),
point_class=np.zeros(2, dtype=np.uint8),
sensor_position_map=np.zeros(3, dtype=np.float64),
sensor_orientation_xyzw=np.asarray((0.0, 0.0, 0.0, 1.0), dtype=np.float64),
projection=Kb4ProjectionProfile(
width=4,
height=4,
intrinsic_fx_fy_cx_cy=(2.0, 2.0, 2.0, 2.0),
distortion_kb4=(0.0, 0.0, 0.0, 0.0),
t_camera_from_lidar=transform,
),
surface_valid=True,
)
return replay._AdmittedInputs(
profile=profile,
semantic=semantic,
geometry=geometry,
threat=threat,
store=_Store(frame), # type: ignore[arg-type]
geometry_frames_path=geometry_frames,
geometry_frames_sha256=geometry_sha256,
threat_frames_path=threat_frames,
threat_frames_sha256=threat_sha256,
)
def _build(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> tuple[replay.SemanticSlamReplayResult, replay._AdmittedInputs]:
admitted = _fixture(tmp_path)
monkeypatch.setattr(replay, "_admit_inputs", lambda **_kwargs: admitted)
result = replay.build_semantic_slam_replay(
repository_root=tmp_path,
semantic_result_root=tmp_path,
threat_result_root=tmp_path,
geometry_result_root=tmp_path,
output_root=tmp_path / "output",
)
return result, admitted
def test_builder_is_idempotent_and_preserves_geometry_and_threat_ledgers(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
admitted = _fixture(tmp_path)
geometry_before = admitted.geometry_frames_path.read_bytes()
threat_before = admitted.threat_frames_path.read_bytes()
monkeypatch.setattr(replay, "_admit_inputs", lambda **_kwargs: admitted)
arguments = {
"repository_root": tmp_path,
"semantic_result_root": tmp_path,
"threat_result_root": tmp_path,
"geometry_result_root": tmp_path,
"output_root": tmp_path / "output",
}
first = replay.build_semantic_slam_replay(**arguments)
manifest_before = (first.result_root / replay.SEMANTIC_SLAM_MANIFEST_NAME).read_bytes()
second = replay.build_semantic_slam_replay(**arguments)
assert second.result_id == first.result_id
assert (second.result_root / replay.SEMANTIC_SLAM_MANIFEST_NAME).read_bytes() == manifest_before
assert admitted.geometry_frames_path.read_bytes() == geometry_before
assert admitted.threat_frames_path.read_bytes() == threat_before
identity = first.manifest["identity"]
assert identity["geometry_frames_sha256"] == hashlib.sha256(geometry_before).hexdigest()
assert identity["base_m4_frames_sha256"] == hashlib.sha256(threat_before).hexdigest()
def test_unprojected_source_point_is_uint8_zero_with_explicit_status(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
result, _ = _build(tmp_path, monkeypatch)
with np.load(
result.result_root / replay.SEMANTIC_SLAM_POINTS_NAME,
allow_pickle=False,
) as archive:
assert archive["point_labels"].dtype == np.uint8
assert archive["point_labels"].tolist() == [4, 0]
assert archive["point_status_codes"].tolist() == [
int(SemanticEvidenceStatus.LABELED),
int(SemanticEvidenceStatus.UNPROJECTED),
]
assert archive["point_projected"].tolist() == [1, 0]
rows = [
json.loads(line)
for line in (result.result_root / replay.SEMANTIC_SLAM_OBSERVATIONS_NAME)
.read_text("utf-8")
.splitlines()
]
assert rows[0]["observations"][0]["observation_id"] == _observation().observation_id
assert rows[0]["observations"][0]["status"] == "labeled"
assert rows[0]["observations"][0]["unprojected_point_count"] == 1
assert "threat" not in rows[0]["observations"][0]
assert rows[0]["source_time_ns"] == 1_000_000_000
assert rows[0]["temporal_binding"] == {
"semantic_to_camera": "exact-sequence-and-session-time",
"camera_to_lidar": "accepted-e6-nearest-host-arrival-best-effort",
"lidar_camera_delta_ms": 4.0,
"pose_point_delta_ms": 2.0,
"physical_synchronization_proven": False,
}
assert rows[1]["temporal_binding"]["lidar_camera_delta_ms"] is None
def test_reader_rejects_tampered_point_artifact(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
result, _ = _build(tmp_path, monkeypatch)
points = result.result_root / replay.SEMANTIC_SLAM_POINTS_NAME
points.write_bytes(points.read_bytes() + b"tamper")
with pytest.raises(replay.SemanticSlamReplayError, match="digest changed"):
replay.read_semantic_slam_replay_result(result.result_root)
def test_builder_rejects_semantic_and_source_pack_session_time_mismatch(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
admitted = _fixture(tmp_path)
rows = [
json.loads(line) for line in admitted.semantic.frames_path.read_text("utf-8").splitlines()
]
rows[1]["session_seconds"] = 2.001
_write_jsonl(admitted.semantic.frames_path, rows)
semantic = replace(
admitted.semantic,
frames_sha256=_sha256(admitted.semantic.frames_path),
)
monkeypatch.setattr(
replay,
"_admit_inputs",
lambda **_kwargs: replace(admitted, semantic=semantic),
)
with pytest.raises(replay.SemanticSlamReplayError, match="session time disagree"):
replay.build_semantic_slam_replay(
repository_root=tmp_path,
semantic_result_root=tmp_path,
threat_result_root=tmp_path,
geometry_result_root=tmp_path,
output_root=tmp_path / "output",
)
def test_builder_rejects_best_effort_delta_outside_admitted_bound(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
admitted = _fixture(tmp_path)
frame = admitted.store.frame_for_index(0)
assert frame is not None
monkeypatch.setattr(
replay,
"_admit_inputs",
lambda **_kwargs: replace(
admitted,
store=_Store(frame, lidar_delta_ms=100.001), # type: ignore[arg-type]
),
)
with pytest.raises(replay.SemanticSlamReplayError, match="delta exceeds"):
replay.build_semantic_slam_replay(
repository_root=tmp_path,
semantic_result_root=tmp_path,
threat_result_root=tmp_path,
geometry_result_root=tmp_path,
output_root=tmp_path / "output",
)
def test_reader_rejects_leaf_result_symlink(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
result, _ = _build(tmp_path, monkeypatch)
alias_parent = tmp_path / "alias"
alias_parent.mkdir()
alias = alias_parent / result.result_id
alias.symlink_to(result.result_root, target_is_directory=True)
with pytest.raises(replay.SemanticSlamReplayError, match="result root is invalid"):
replay.read_semantic_slam_replay_result(alias)
def test_builder_rejects_existing_destination_symlink(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
result, admitted = _build(tmp_path, monkeypatch)
monkeypatch.setattr(replay, "_admit_inputs", lambda **_kwargs: admitted)
second_output = tmp_path / "second-output"
second_output.mkdir()
(second_output / result.result_id).symlink_to(result.result_root, target_is_directory=True)
with pytest.raises(replay.SemanticSlamReplayError, match="destination cannot be a symlink"):
replay.build_semantic_slam_replay(
repository_root=tmp_path,
semantic_result_root=tmp_path,
threat_result_root=tmp_path,
geometry_result_root=tmp_path,
output_root=second_output,
)