feat(perception): add E34 temporal occupied layer

This commit is contained in:
DCCONSTRUCTIONS
2026-07-27 16:55:40 +03:00
parent 95c6540691
commit 621084fcd6
22 changed files with 4732 additions and 104 deletions
+135 -3
View File
@@ -1,10 +1,13 @@
from __future__ import annotations
from pathlib import Path
from types import SimpleNamespace
from fastapi import APIRouter
from fastapi.routing import APIRoute
from pytest import MonkeyPatch
import k1link.web.advanced_laboratory_api as advanced_api
from k1link.web.advanced_laboratory_api import build_advanced_laboratory_router
@@ -23,7 +26,7 @@ def _endpoint(router: APIRouter, path: str) -> object:
def test_advanced_catalogs_are_empty_when_not_configured() -> None:
router = build_advanced_laboratory_router()
for name in ("e31", "e32", "e33"):
for name in ("e31", "e32", "e33", "e34"):
route = _endpoint(router, f"/api/v1/laboratory/{name}/results")
catalog = route(limit=1) # type: ignore[operator]
assert catalog == {
@@ -42,18 +45,21 @@ def test_advanced_catalogs_fail_closed_on_incomplete_results(
e31 = tmp_path / "e31"
e32 = tmp_path / "e32"
e33 = tmp_path / "e33"
for root in (e31, e32, e33):
e34 = tmp_path / "e34"
for root in (e31, e32, e33, e34):
root.mkdir()
(e31 / f"e31-source-qualification-{'1' * 64}").mkdir()
(e32 / f"e32-track-geometry-{'2' * 64}").mkdir()
(e33 / f"e33-worker-shadow-{'3' * 64}").mkdir()
(e34 / f"e34-temporal-occupied-{'4' * 64}").mkdir()
router = build_advanced_laboratory_router(
e31_root_provider=lambda: e31,
e32_root_provider=lambda: e32,
e33_root_provider=lambda: e33,
e34_root_provider=lambda: e34,
)
for name in ("e31", "e32", "e33"):
for name in ("e31", "e32", "e33", "e34"):
route = _endpoint(router, f"/api/v1/laboratory/{name}/results")
catalog = route(limit=1) # type: ignore[operator]
assert catalog["configured"] is True
@@ -77,3 +83,129 @@ def test_advanced_catalog_does_not_follow_a_configured_symlink(
catalog = route(limit=1) # type: ignore[operator]
assert catalog["configured"] is False
def test_e34_catalog_projects_only_accepted_read_only_evidence(
tmp_path: Path,
monkeypatch: MonkeyPatch,
) -> None:
result_id = f"e34-temporal-occupied-{'4' * 64}"
root = tmp_path / "e34"
candidate = root / result_id
candidate.mkdir(parents=True)
(candidate / "manifest.json").write_text("{}", encoding="utf-8")
authority = {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
}
result = SimpleNamespace(
result_id=result_id,
accepted=True,
manifest={
"created_at_utc": "2026-07-27T13:28:30Z",
"acceptance_state": (
"accepted-bounded-occupied-unknown-temporal-layer"
),
"identity": {
"source_session_id": "source-session",
"e32_result_id": f"e32-track-geometry-{'2' * 64}",
"e33_result_id": f"e33-worker-shadow-{'3' * 64}",
"pipeline": "lidar-local-map/short-ttl-hit-only/v1",
"coordinate_frame": "map",
"profile": {
"profile_id": "e34-profile/v1",
"layer": {
"voxel_size_m": 0.45,
"occupied_ttl_seconds": 0.75,
"maximum_active_components": 256,
"maximum_cells_per_component": 4096,
},
"association": {
"geometry_maximum_gap_seconds": 0.35,
"geometry_maximum_centroid_distance_m": 0.9,
},
},
},
},
report={
"metrics": {
"frames": {
"source": 4489,
"processed": 4489,
"with_current_layer": 3928,
"held_only": 540,
"unknown_empty": 21,
},
"components": {
"created": 4912,
"camera_created": 1027,
"geometry_created": 3885,
"geometry_reassociated_components": 2366,
"exact_camera_associations": 5168,
"geometry_spatial_reassociations": 17433,
"held_publications": 39664,
"expired": 4895,
"peak_active": 37,
"maximum_observed_span_seconds": 13.394,
"continuity_fraction": 0.821466,
"geometry_only_reassociation_fraction": 0.81776,
},
"occupancy": {
"e32_current_point_rows": 2119302,
"e34_consumed_current_point_rows": 2119302,
"current_cell_rows": 834478,
"held_cell_rows": 531581,
"stored_cell_rows": 1366059,
"free_cell_rows": 0,
"peak_cells_per_component": 324,
},
"aging": {
"maximum_held_age_seconds": 0.75,
"maximum_expiry_delay_seconds": 0.0,
"maximum_replay_materialization_delay_seconds": 0.283,
},
"map_frame": {"jump_candidates": 0},
"runtime": {
"build_elapsed_ms": 4317.0,
"frame_processing_ms": {"p95": 1.483},
},
},
"acceptance": {"accepted": True, "requirements": {}},
"decision": {"next_gate": "A8/E35"},
"authority": authority,
},
review={
"schema_version": (
"missioncore.e34-temporal-review-timeline/v1"
),
"result_id": result_id,
"frames": [],
},
)
def fake_read(
root_text: str,
signature: tuple[int, ...],
) -> SimpleNamespace:
assert root_text == str(candidate.resolve())
assert signature
return result
monkeypatch.setattr(advanced_api, "_read_e34_cached", fake_read)
router = build_advanced_laboratory_router(
e34_root_provider=lambda: root,
)
route = _endpoint(router, "/api/v1/laboratory/e34/results")
catalog = route(limit=1) # type: ignore[operator]
assert catalog["candidate_total"] == 1
assert catalog["invalid_total"] == 0
item = catalog["items"][0]
assert item["status"] == (
"accepted-bounded-occupied-unknown-temporal-layer"
)
assert item["metrics"]["processed_frames"] == 4489
assert item["metrics"]["free_cell_rows"] == 0
assert item["authority"] == authority
assert item["access"] == "read-only"
+356
View File
@@ -0,0 +1,356 @@
from __future__ import annotations
import numpy as np
import pytest
from k1link.compute.temporal_occupied_layer import (
TemporalLayerConfig,
TemporalOccupiedLayer,
TemporalOccupiedLayerError,
)
from k1link.compute.track_geometry import (
PointSlab,
TrackGeometry,
TrackGeometryCurrentness,
TrackGeometryEvidenceState,
TrackGeometryFrame,
TrackGeometryMetricBasis,
TrackGeometryOwnerKind,
TrackGeometrySourceBinding,
)
def _config(**changes: object) -> TemporalLayerConfig:
values: dict[str, object] = {
"voxel_size_m": 0.45,
"occupied_ttl_seconds": 0.75,
"maximum_active_components": 256,
"maximum_cells_per_component": 4096,
"geometry_maximum_gap_seconds": 0.35,
"geometry_maximum_centroid_distance_m": 0.9,
"geometry_minimum_voxel_overlap_fraction": 0.05,
"geometry_neighbor_radius_cells": 1,
"jump_maximum_adjacent_gap_seconds": 0.25,
"jump_minimum_matched_components": 4,
"jump_minimum_median_displacement_m": 1.5,
"jump_minimum_p25_displacement_m": 0.9,
}
values.update(changes)
return TemporalLayerConfig(**values) # type: ignore[arg-type]
def _binding() -> TrackGeometrySourceBinding:
return TrackGeometrySourceBinding(
source_pack_id="e10-lidar-pack-" + "a" * 64,
source_session_id="source-session",
representation_profile_id="representation-profile/v1",
e31_qualification_id="e31-source-qualification-" + "b" * 64,
calibration_sha256="c" * 64,
coordinate_frame="map",
time_basis="source-time",
selected_offset_ms=0,
)
def _frame(
*,
frame_index: int,
seconds: float,
owners: list[
tuple[
str,
TrackGeometryOwnerKind,
list[list[float]],
int | None,
]
],
source_available: bool = True,
unknown_camera_owner: str | None = None,
) -> TrackGeometryFrame:
owner_keys = tuple(item[0] for item in owners)
source_indices: list[int] = []
points: list[list[float]] = []
owner_indices: list[int] = []
geometries: list[TrackGeometry] = []
for owner_index, (owner_key, owner_kind, owner_points, track_id) in enumerate(
owners
):
row_start = len(points)
points.extend(owner_points)
source_indices.extend(range(row_start, row_start + len(owner_points)))
owner_indices.extend([owner_index] * len(owner_points))
geometries.append(
TrackGeometry(
owner_key=owner_key,
owner_kind=owner_kind,
evidence_state=(
TrackGeometryEvidenceState.AGREE
if owner_kind is TrackGeometryOwnerKind.CAMERA_TRACK
else TrackGeometryEvidenceState.GEOMETRY_ONLY
),
currentness=TrackGeometryCurrentness.CURRENT,
metric_basis=TrackGeometryMetricBasis.CURRENT_POINTS,
reason_codes=("accepted-hit-backed-support",),
semantic_track_id=track_id,
semantic_label=(
"object"
if owner_kind is TrackGeometryOwnerKind.CAMERA_TRACK
else None
),
bbox_xyxy=(
(1.0, 1.0, 2.0, 2.0)
if owner_kind is TrackGeometryOwnerKind.CAMERA_TRACK
else None
),
range_m=1.0,
)
)
if unknown_camera_owner is not None:
track_id = int(unknown_camera_owner.split(":")[1])
geometries.append(
TrackGeometry(
owner_key=unknown_camera_owner,
owner_kind=TrackGeometryOwnerKind.CAMERA_TRACK,
evidence_state=TrackGeometryEvidenceState.CAMERA_ONLY,
currentness=TrackGeometryCurrentness.CURRENT,
metric_basis=TrackGeometryMetricBasis.UNAVAILABLE,
reason_codes=("camera-without-current-points",),
semantic_track_id=track_id,
semantic_label="object",
bbox_xyxy=(1.0, 1.0, 2.0, 2.0),
)
)
point_array = (
np.asarray(points, dtype="<f4")
if points
else np.empty((0, 3), dtype="<f4")
)
return TrackGeometryFrame(
binding=_binding(),
frame_index=frame_index,
source_frame_index=frame_index,
session_seconds=seconds,
source_available=source_available,
point_slab=PointSlab(
frame_index=frame_index,
source_frame_index=frame_index,
source_point_count=len(points),
coordinate_frame="map",
owner_keys=owner_keys,
source_indices=np.asarray(source_indices, dtype="<i8"),
points_xyz_m=point_array,
owner_indices=np.asarray(owner_indices, dtype="<u4"),
),
geometries=tuple(geometries),
)
def test_temporal_layer_holds_unknown_then_expires_without_publishing_cells() -> None:
layer = TemporalOccupiedLayer(_config())
current = layer.update(
_frame(
frame_index=0,
seconds=0.0,
owners=[
(
"track:7",
TrackGeometryOwnerKind.CAMERA_TRACK,
[[1.0, 2.0, 0.5], [1.1, 2.0, 0.5]],
7,
)
],
)
)
held = layer.update(
_frame(
frame_index=1,
seconds=0.5,
owners=[],
source_available=False,
)
)
expired = layer.update(
_frame(
frame_index=2,
seconds=0.9,
owners=[],
source_available=False,
)
)
assert current.document["current"][0]["occupancy_state"] == "occupied"
assert held.document["held"][0]["occupancy_state"] == "unknown"
assert held.document["held"][0]["cell_row_count"] > 0
assert expired.document["expired"][0]["occupancy_state"] == "unknown"
assert expired.document["expired"][0]["cell_row_count"] == 0
assert expired.document["expired"][0]["expiration_event"] == {
"deadline_seconds": 0.75,
"emitted_at_seconds": 0.75,
"emission_delay_seconds": 0.0,
"replay_materialized_at_seconds": 0.9,
"replay_materialization_delay_seconds": pytest.approx(0.15),
"clock": "independent-layer-deadline",
}
assert expired.cell_rows.shape == (0, 3)
assert layer.active_component_count == 0
def test_geometry_only_components_reassociate_without_inventing_semantics() -> None:
layer = TemporalOccupiedLayer(_config())
first = layer.update(
_frame(
frame_index=0,
seconds=0.0,
owners=[
(
"geometry:0",
TrackGeometryOwnerKind.GEOMETRY_CLUSTER,
[[1.0, 1.0, 0.5], [1.2, 1.0, 0.5]],
None,
)
],
)
)
second = layer.update(
_frame(
frame_index=1,
seconds=0.1,
owners=[
(
"geometry:9",
TrackGeometryOwnerKind.GEOMETRY_CLUSTER,
[[1.1, 1.0, 0.5], [1.3, 1.0, 0.5]],
None,
)
],
)
)
assert first.document["current"][0]["temporal_id"] == second.document[
"current"
][0]["temporal_id"]
assert second.document["current"][0]["association_reason"] == (
"geometry-spatial-reassociation"
)
assert second.document["current"][0]["semantic_provenance"] == {
"owner": None,
"track_ids": [],
"labels": [],
"class_inferred_by_e34": False,
}
def test_unknown_without_current_points_cannot_create_occupied_cells() -> None:
layer = TemporalOccupiedLayer(_config())
projection = layer.update(
_frame(
frame_index=0,
seconds=0.0,
owners=[],
unknown_camera_owner="track:11",
)
)
assert projection.document["layer_state"] == "unknown"
assert projection.document["current"] == []
assert projection.document["input"]["unknown_without_current_points"] == 1
assert projection.cell_rows.shape == (0, 3)
assert layer.active_component_count == 0
def test_temporal_layer_detects_supported_global_map_frame_jump() -> None:
layer = TemporalOccupiedLayer(_config())
first = [
(
f"track:{track_id}",
TrackGeometryOwnerKind.CAMERA_TRACK,
[[float(track_id), 0.0, 0.5]],
track_id,
)
for track_id in range(1, 5)
]
shifted = [
(
f"track:{track_id}",
TrackGeometryOwnerKind.CAMERA_TRACK,
[[float(track_id) + 2.0, 0.0, 0.5]],
track_id,
)
for track_id in range(1, 5)
]
layer.update(_frame(frame_index=0, seconds=0.0, owners=first))
projection = layer.update(
_frame(frame_index=1, seconds=0.1, owners=shifted)
)
assert projection.document["map_frame_jump"]["candidate"] is True
assert projection.document["map_frame_jump"][
"matched_component_count"
] == 4
def test_temporal_layer_rejects_incoherent_nearest_neighbor_jump() -> None:
layer = TemporalOccupiedLayer(_config())
first = [
(
f"geometry:{index}",
TrackGeometryOwnerKind.GEOMETRY_CLUSTER,
[[float(index) * 5.0, 0.0, 0.5]],
None,
)
for index in range(4)
]
incoherent = [
(
f"geometry:{index}",
TrackGeometryOwnerKind.GEOMETRY_CLUSTER,
[[x, y, 0.5]],
None,
)
for index, (x, y) in enumerate(
((2.0, 0.0), (8.0, 2.0), (13.0, -3.0), (25.0, 4.0))
)
]
layer.update(_frame(frame_index=0, seconds=0.0, owners=first))
projection = layer.update(
_frame(frame_index=1, seconds=0.1, owners=incoherent)
)
assert projection.document["map_frame_jump"]["candidate"] is False
assert projection.document["map_frame_jump"]["reason"] == (
"no-coherent-global-translation"
)
def test_temporal_layer_fails_closed_instead_of_truncating_cells() -> None:
layer = TemporalOccupiedLayer(_config(maximum_cells_per_component=1))
with pytest.raises(
TemporalOccupiedLayerError,
match="cell bound exceeded",
):
layer.update(
_frame(
frame_index=0,
seconds=0.0,
owners=[
(
"geometry:0",
TrackGeometryOwnerKind.GEOMETRY_CLUSTER,
[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]],
None,
)
],
)
)
def test_temporal_layer_rejects_non_monotonic_replay() -> None:
layer = TemporalOccupiedLayer(_config())
layer.update(_frame(frame_index=0, seconds=0.0, owners=[]))
with pytest.raises(
TemporalOccupiedLayerError,
match="frame order changed",
):
layer.update(_frame(frame_index=2, seconds=0.1, owners=[]))