303 lines
8.8 KiB
Python
303 lines
8.8 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
from k1link.compute.occupancy_motion import (
|
|
PersistentSupportTracker,
|
|
SupportMeasurement,
|
|
_apply_occupancy_evidence,
|
|
evaluate_persistent_support_benchmark,
|
|
read_persistent_support_benchmark,
|
|
read_persistent_support_profile,
|
|
)
|
|
|
|
|
|
def _profile() -> dict[str, object]:
|
|
root = Path(__file__).resolve().parents[1]
|
|
profile, digest = read_persistent_support_profile(
|
|
root / "experiments" / "perception" / "e25_persistent_support_profile.json"
|
|
)
|
|
assert len(digest) == 64
|
|
return profile
|
|
|
|
|
|
def _support(x: float, y: float = 2.0) -> np.ndarray:
|
|
return np.asarray(
|
|
[
|
|
[x - 0.2, y - 0.2, 0.4],
|
|
[x + 0.2, y - 0.2, 0.5],
|
|
[x - 0.2, y + 0.2, 0.6],
|
|
[x + 0.2, y + 0.2, 0.7],
|
|
],
|
|
dtype=np.float64,
|
|
)
|
|
|
|
|
|
def test_e25_profile_is_bounded_and_has_no_control_authority() -> None:
|
|
profile = _profile()
|
|
|
|
assert profile["source"]["coordinate_frame"] == "k1-map"
|
|
assert profile["bounds"]["maximum_tracks"] == 192
|
|
assert profile["occupancy"]["maximum_snapshots_per_track"] == 32
|
|
assert profile["authority"] == {
|
|
"commands_enabled": False,
|
|
"navigation_or_safety_accepted": False,
|
|
}
|
|
|
|
|
|
def test_e25_static_support_survives_visible_surface_center_jitter() -> None:
|
|
tracker = PersistentSupportTracker(_profile())
|
|
evidence = {}
|
|
for frame in range(30):
|
|
jitter = 0.18 if frame % 2 else -0.18
|
|
evidence = tracker.observe(
|
|
track_id=250001,
|
|
source_track_id=10,
|
|
group="vehicle",
|
|
session_seconds=frame * 0.1,
|
|
points_map=_support(10.0 + jitter),
|
|
)
|
|
|
|
assert evidence["motion_state"] == "static"
|
|
assert evidence["occupancy_overlap_fraction"] is not None
|
|
assert evidence["occupancy_overlap_fraction"] >= 0.55
|
|
|
|
|
|
def test_e25_coherent_translation_becomes_dynamic() -> None:
|
|
tracker = PersistentSupportTracker(_profile())
|
|
evidence = {}
|
|
for frame in range(20):
|
|
evidence = tracker.observe(
|
|
track_id=250002,
|
|
source_track_id=20,
|
|
group="person",
|
|
session_seconds=frame * 0.1,
|
|
points_map=_support(4.0 + frame * 0.5),
|
|
)
|
|
|
|
assert evidence["motion_state"] == "dynamic"
|
|
assert evidence["speed_mps"] is not None
|
|
assert evidence["speed_mps"] > 1.0
|
|
assert evidence["occupancy_overlap_fraction"] <= 0.25
|
|
|
|
|
|
def test_e25_does_not_claim_motion_without_older_support() -> None:
|
|
tracker = PersistentSupportTracker(_profile())
|
|
|
|
evidence = tracker.observe(
|
|
track_id=250003,
|
|
source_track_id=30,
|
|
group="vehicle",
|
|
session_seconds=0.0,
|
|
points_map=_support(8.0),
|
|
)
|
|
|
|
assert evidence["motion_state"] == "unknown"
|
|
assert evidence["motion_confidence"] == 0.0
|
|
|
|
|
|
def test_e25_current_support_is_not_attached_to_an_e24_held_hypothesis() -> None:
|
|
tracker = PersistentSupportTracker(_profile())
|
|
measurement = SupportMeasurement(
|
|
source_track_id=40,
|
|
label="car",
|
|
group="vehicle",
|
|
score=0.9,
|
|
points_map=_support(10.0),
|
|
source_indices=np.asarray([1, 2, 3, 4], dtype=np.int64),
|
|
)
|
|
_apply_occupancy_evidence(
|
|
[
|
|
{
|
|
"track_id": 250040,
|
|
"source_track_id": 40,
|
|
"temporal_status": "e24-observed",
|
|
}
|
|
],
|
|
{40: measurement},
|
|
tracker,
|
|
0.0,
|
|
)
|
|
result = _apply_occupancy_evidence(
|
|
[
|
|
{
|
|
"track_id": 250041,
|
|
"source_track_id": 40,
|
|
"temporal_status": "e24-observed",
|
|
},
|
|
{
|
|
"track_id": 250040,
|
|
"source_track_id": 40,
|
|
"temporal_status": "e24-held-prediction",
|
|
},
|
|
],
|
|
{40: measurement},
|
|
tracker,
|
|
0.1,
|
|
)
|
|
|
|
assert result[0]["occupancy_evidence_current"] is True
|
|
assert result[1]["occupancy_evidence_current"] is False
|
|
assert tracker.snapshot()["support_observations"] == 2
|
|
|
|
|
|
def test_e25_target_benchmark_cannot_be_satisfied_by_another_object() -> None:
|
|
event = {
|
|
"id": "target-five",
|
|
"kind": "target-motion",
|
|
"window_seconds": [10.0, 12.0],
|
|
"class_group": "vehicle",
|
|
"target_source_track_ids": [5],
|
|
"expected_motion": "dynamic",
|
|
"minimum_hits": 2,
|
|
"minimum_span_seconds": 0.1,
|
|
"minimum_coverage_fraction": 0.5,
|
|
}
|
|
source_rows = [
|
|
{
|
|
"session_seconds": 10.0 + index * 0.2,
|
|
"objects": [
|
|
{
|
|
"track_id": 5,
|
|
"association_group": "vehicle",
|
|
}
|
|
],
|
|
}
|
|
for index in range(3)
|
|
]
|
|
wrong_fusion_rows = [
|
|
{
|
|
"session_seconds": 10.0 + index * 0.2,
|
|
"objects": [
|
|
{
|
|
"track_id": 250099,
|
|
"source_track_id": 99,
|
|
"association_group": "vehicle",
|
|
"motion_state": "dynamic",
|
|
"occupancy_evidence_current": True,
|
|
}
|
|
],
|
|
}
|
|
for index in range(3)
|
|
]
|
|
|
|
result = evaluate_persistent_support_benchmark(
|
|
source_rows,
|
|
wrong_fusion_rows,
|
|
{"events": [event]},
|
|
)
|
|
|
|
assert result["passed"] is False
|
|
assert result["events"][0]["evidence_observations"] == 0
|
|
|
|
|
|
def test_e25_benchmark_deduplicates_world_hypotheses_for_one_source_object() -> None:
|
|
event = {
|
|
"id": "target-five",
|
|
"kind": "target-motion",
|
|
"window_seconds": [10.0, 12.0],
|
|
"class_group": "vehicle",
|
|
"target_source_track_ids": [5],
|
|
"expected_motion": "dynamic",
|
|
"minimum_hits": 2,
|
|
"minimum_span_seconds": 0.1,
|
|
"minimum_coverage_fraction": 0.5,
|
|
}
|
|
source_rows = [
|
|
{
|
|
"frame_index": index,
|
|
"session_seconds": 10.0 + index * 0.2,
|
|
"objects": [{"track_id": 5, "association_group": "vehicle"}],
|
|
}
|
|
for index in range(3)
|
|
]
|
|
fusion_rows = [
|
|
{
|
|
"frame_index": index,
|
|
"session_seconds": 10.0 + index * 0.2,
|
|
"objects": [
|
|
{
|
|
"track_id": world_track,
|
|
"source_track_id": 5,
|
|
"association_group": "vehicle",
|
|
"motion_state": "dynamic",
|
|
"motion_confidence": 0.8,
|
|
"occupancy_cell_count": 4,
|
|
"occupancy_support_observations": 6,
|
|
"occupancy_evidence_current": True,
|
|
}
|
|
for world_track in (250005, 250006)
|
|
],
|
|
}
|
|
for index in range(3)
|
|
]
|
|
|
|
result = evaluate_persistent_support_benchmark(
|
|
source_rows,
|
|
fusion_rows,
|
|
{"events": [event]},
|
|
)
|
|
|
|
assert result["events"][0]["coverage_fraction"] == 1.0
|
|
assert result["events"][0]["evidence_observations"] == 3
|
|
assert result["events"][0]["duplicate_evidence_observations"] == 3
|
|
|
|
|
|
def test_e25_static_control_rejects_excess_false_dynamic_evidence() -> None:
|
|
event = {
|
|
"id": "parked-vehicles",
|
|
"kind": "static-control",
|
|
"window_seconds": [20.0, 22.0],
|
|
"class_group": "vehicle",
|
|
"expected_motion": "static",
|
|
"minimum_source_observations": 3,
|
|
"minimum_coverage_fraction": 0.5,
|
|
"minimum_classified_fraction": 0.5,
|
|
"maximum_false_dynamic_fraction": 0.1,
|
|
}
|
|
source_rows = [
|
|
{
|
|
"session_seconds": 20.0 + index * 0.2,
|
|
"objects": [{"track_id": 7, "association_group": "vehicle"}],
|
|
}
|
|
for index in range(3)
|
|
]
|
|
fusion_rows = [
|
|
{
|
|
"session_seconds": 20.0 + index * 0.2,
|
|
"objects": [
|
|
{
|
|
"track_id": 250007,
|
|
"source_track_id": 7,
|
|
"association_group": "vehicle",
|
|
"motion_state": "dynamic",
|
|
"occupancy_evidence_current": True,
|
|
}
|
|
],
|
|
}
|
|
for index in range(3)
|
|
]
|
|
|
|
result = evaluate_persistent_support_benchmark(
|
|
source_rows,
|
|
fusion_rows,
|
|
{"events": [event]},
|
|
)
|
|
|
|
assert result["passed"] is False
|
|
assert result["events"][0]["false_dynamic_fraction"] == 1.0
|
|
|
|
|
|
def test_e25_benchmark_contract_uses_reviewed_source_track_bindings() -> None:
|
|
root = Path(__file__).resolve().parents[1]
|
|
benchmark, digest = read_persistent_support_benchmark(
|
|
root / "experiments" / "perception" / "e25_motion_benchmark.json"
|
|
)
|
|
target_events = [event for event in benchmark["events"] if event["kind"] == "target-motion"]
|
|
|
|
assert len(digest) == 64
|
|
assert target_events
|
|
assert all(event["target_source_track_ids"] for event in target_events)
|