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