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
+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",
]