feat(perception): qualify bounded temporal stability

This commit is contained in:
DCCONSTRUCTIONS
2026-07-23 23:30:50 +03:00
parent 5a29a536a2
commit cfc7b062da
8 changed files with 1674 additions and 6 deletions
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
import json
from pathlib import Path
import numpy as np
from k1link.compute.temporal_stability import (
TemporalStabilizer,
read_profile,
stabilize_semantic_masks,
)
def _profile() -> dict[str, object]:
root = Path(__file__).resolve().parents[1]
profile, digest = read_profile(
root / "experiments" / "perception" / "e22_temporal_stability_profile.json"
)
assert len(digest) == 64
return profile
def _object(track_id: int, box: list[float]) -> dict[str, object]:
return {
"association_group": "vehicle",
"bbox_xyxy": box,
"candidate_projected_points": 20,
"clustered_points": 14,
"completion_fraction": 0.9,
"cuboid_center_map": [10.0, 2.0, 0.8],
"cuboid_half_size": [2.25, 0.925, 0.775],
"cuboid_quaternion_xyzw": [0.0, 0.0, 0.0, 1.0],
"cuboid_status": "accepted-class-prior-amodal-v1",
"distance_median_m": 10.2,
"distance_p10_m": 9.8,
"distance_smoothed_m": 10.1,
"geometry": "class-prior-completed-from-visible-lidar-support",
"ground_rejected_points": 0,
"ground_z_map": 0.0,
"label": "car",
"observed_cuboid_center_map": [10.0, 2.0, 0.8],
"observed_cuboid_half_size": [2.25, 0.925, 0.775],
"observed_cuboid_quaternion_xyzw": [0.0, 0.0, 0.0, 1.0],
"orientation_source": "support-pca",
"pre_ground_clustered_points": 14,
"score": 0.9,
"semantic_compatible_points": 18,
"support_coverage_fraction": 0.8,
"support_ground_z_map": None,
"temporal_status": "confirmed",
"track_id": track_id,
}
def test_e22_profile_is_bounded_and_has_no_control_authority() -> None:
profile = _profile()
assert profile["bounds"] == {
"maximum_track_states": 128,
"maximum_track_idle_seconds": 1.25,
"semantic_history_masks": 1,
}
assert profile["authority"] == {
"commands_enabled": False,
"navigation_or_safety_accepted": False,
}
def test_e22_stitches_one_tracklet_and_marks_short_holds() -> None:
stabilizer = TemporalStabilizer(_profile())
first = stabilizer.update(
frame_index=0,
session_seconds=0.0,
objects=[_object(10, [100.0, 100.0, 200.0, 200.0])],
)
second = stabilizer.update(
frame_index=1,
session_seconds=0.1,
objects=[_object(11, [102.0, 100.0, 202.0, 200.0])],
)
held = stabilizer.update(
frame_index=2,
session_seconds=0.2,
objects=[],
)
assert first[0]["track_id"] == 10
assert second[0]["track_id"] == 10
assert second[0]["temporal_source_track_id"] == 11
assert second[0]["temporal_2d_status"] == "stitched-observed"
assert held[0]["temporal_2d_status"] == "held-1-frame"
assert held[0]["cuboid_status"] == "accepted-temporal-hold-e22-v1"
assert stabilizer.stitched_tracks == 1
assert stabilizer.held_2d == 1
assert stabilizer.peak_states <= 128
def test_e22_semantic_hysteresis_removes_island_but_keeps_supported_region() -> None:
masks = np.zeros((2, 600, 800), dtype=np.uint8)
masks[1, 20, 20] = 4
masks[1, 100:110, 100:110] = 7
stabilized, metrics, durations = stabilize_semantic_masks(
masks,
minimum_same_label_neighbors=3,
)
assert stabilized[1, 20, 20] == 0
assert np.all(stabilized[1, 102:108, 102:108] == 7)
assert (
metrics["stabilized_unsupported_change_fraction"]["mean"]
< metrics["baseline_unsupported_change_fraction"]["mean"]
)
assert len(durations) == 1
assert json.dumps(metrics, allow_nan=False)