fix(perception): stabilize replay body frame
This commit is contained in:
@@ -8,10 +8,7 @@ from k1link.perception.threat_replay import read_threat_replay_result
|
||||
from k1link.web.m4_threat_replay_api import build_m4_threat_replay_router
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
RESULT_ID = (
|
||||
"m4-threat-replay-"
|
||||
"7e1613a3ea35638b5ea7a3f7c1c78fe9eba1a3adae540b652dec167f815d45b2"
|
||||
)
|
||||
RESULT_ID = "m4-threat-replay-78a06d96c4db5263dc63fc4e6e067c07fc81370d3f5085ff43361af89cec1e9e"
|
||||
RESULTS_ROOT = REPOSITORY_ROOT / ".runtime/compute-experiments/m4/replay-threat"
|
||||
|
||||
|
||||
@@ -35,9 +32,9 @@ def test_full_source_threat_result_closes_m4_6_contract() -> None:
|
||||
"stale-or-held": 37995,
|
||||
}
|
||||
assert result.metrics["decisions"] == {
|
||||
"not-threat": 6610,
|
||||
"threat": 8010,
|
||||
"unknown": 60832,
|
||||
"not-threat": 10700,
|
||||
"threat": 2716,
|
||||
"unknown": 62036,
|
||||
}
|
||||
assert result.metrics["fixtures"] == {
|
||||
"critical": 4,
|
||||
@@ -53,10 +50,10 @@ def test_threat_result_is_content_bound_and_visual_evidence_is_complete() -> Non
|
||||
assert isinstance(identity, dict)
|
||||
|
||||
assert identity["frames_sha256"] == (
|
||||
"bf690358efb45c323db7172251074b33c3ef7ede6ae99bd8d3da53cfba86b142"
|
||||
"d55e7651f0b16a62c6b61c5cb2358dd8dff87dbfa57a59e9ec350bc38b156bc1"
|
||||
)
|
||||
assert identity["visuals_sha256"] == (
|
||||
"fb022c6efd84f27c0916a6c87887443c9b43993ac4b1f9910332433152533dea"
|
||||
"957c35d46ae30143beb6b2f26f8f722853ef2a1e91a41d5dc1a03fbf723a54e0"
|
||||
)
|
||||
visual = result.metrics["visual_evidence"]
|
||||
assert isinstance(visual, dict)
|
||||
@@ -69,24 +66,27 @@ def test_threat_result_is_content_bound_and_visual_evidence_is_complete() -> Non
|
||||
"point_cloud_available",
|
||||
"metric_distance_available",
|
||||
"virtual_corridor_available",
|
||||
"qualified_base_footprint_available",
|
||||
)
|
||||
)
|
||||
assert visual["geometry_regression_sequences"] == [138, 274]
|
||||
body_frame = result.metrics["body_frame"]
|
||||
assert body_frame["qualified"] == 3861
|
||||
assert body_frame["rejected"] == 67
|
||||
assert body_frame["camera_forward_alignment_deg"]["p95"] < 9.0
|
||||
|
||||
|
||||
def test_m4_6_lab_api_projects_report_and_exact_visual_frame() -> None:
|
||||
list_results = _endpoint("/api/v1/laboratory/m4-threat/results")
|
||||
list_visuals = _endpoint(
|
||||
"/api/v1/laboratory/m4-threat/results/{result_id}/visuals"
|
||||
)
|
||||
get_visual = _endpoint(
|
||||
"/api/v1/laboratory/m4-threat/results/{result_id}/visuals/{ordinal}"
|
||||
)
|
||||
list_visuals = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/visuals")
|
||||
get_visual = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/visuals/{ordinal}")
|
||||
|
||||
catalog = list_results(limit=1)
|
||||
assert catalog["items"][0]["result_id"] == RESULT_ID
|
||||
assert catalog["items"][0]["authority"] == "replay-simulated"
|
||||
visuals = list_visuals(RESULT_ID)
|
||||
assert len(visuals["items"]) == 32
|
||||
assert [item["sequence"] for item in visuals["items"][:3]] == [62, 138, 274]
|
||||
frame = get_visual(RESULT_ID, 1)
|
||||
assert frame["schema_version"] == "missioncore.perception-threat-visual-frame/v1"
|
||||
assert frame["point_cloud_sample_count"] > 0
|
||||
@@ -98,16 +98,12 @@ def test_m4_6_lab_api_projects_report_and_exact_visual_frame() -> None:
|
||||
|
||||
|
||||
def test_m4_6_video_overlay_covers_the_exact_recorded_camera_timeline() -> None:
|
||||
get_overlay = _endpoint(
|
||||
"/api/v1/laboratory/m4-threat/results/{result_id}/video-overlay"
|
||||
)
|
||||
get_overlay = _endpoint("/api/v1/laboratory/m4-threat/results/{result_id}/video-overlay")
|
||||
|
||||
overlay = get_overlay(RESULT_ID)
|
||||
|
||||
assert overlay["frame_count"] == 4489
|
||||
assert overlay["recorded_source"]["session_id"] == (
|
||||
"20260720T065719Z_viewer_live"
|
||||
)
|
||||
assert overlay["recorded_source"]["session_id"] == ("20260720T065719Z_viewer_live")
|
||||
assert overlay["frames"][0]["frame_index"] == 0
|
||||
assert overlay["frames"][-1]["frame_index"] == 4488
|
||||
assert overlay["authority"] == "replay-simulated"
|
||||
|
||||
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from k1link.perception.contracts import (
|
||||
BoundingRegion2D,
|
||||
CorridorIntersection,
|
||||
@@ -15,23 +17,29 @@ from k1link.perception.contracts import (
|
||||
TemporalState,
|
||||
ThreatDecision,
|
||||
)
|
||||
from k1link.perception.geometry import RecordedGeometryStore
|
||||
from k1link.perception.graph_validation import validate_threats
|
||||
from k1link.perception.threat import (
|
||||
DualEvidenceReplayThreatProvider,
|
||||
ReplayPose,
|
||||
RecordedReplayBodyFrameResolver,
|
||||
ReplayBodyFrame,
|
||||
load_replay_threat_profile,
|
||||
)
|
||||
|
||||
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
|
||||
PROFILE_PATH = REPOSITORY_ROOT / "config/perception/m4-replay-threat-v1.json"
|
||||
PROFILE_PATH = REPOSITORY_ROOT / "config/perception/m4-replay-threat-v2.json"
|
||||
|
||||
|
||||
class _Poses:
|
||||
def pose_for_frame(self, frame_id: str) -> ReplayPose:
|
||||
return ReplayPose(
|
||||
class _BodyFrames:
|
||||
def body_frame_for_frame(self, frame_id: str) -> ReplayBodyFrame:
|
||||
return ReplayBodyFrame(
|
||||
frame_id=frame_id,
|
||||
position_map_xyz_m=(0.0, 0.0, 0.0),
|
||||
orientation_map_from_lidar_xyzw=(0.0, 0.0, 0.0, 1.0),
|
||||
origin_map_xyz_m=(0.0, 0.0, 0.0),
|
||||
basis_map_from_body=((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)),
|
||||
sensor_height_m=1.25,
|
||||
surface_slope_deg=0.0,
|
||||
forward_source="fixture",
|
||||
camera_forward_alignment_deg=0.0,
|
||||
)
|
||||
|
||||
|
||||
@@ -63,9 +71,7 @@ def _obstacle(
|
||||
last_centroid_xyz_m=None if state is TemporalState.EXPIRED else current.centroid_xyz_m,
|
||||
motion=MotionState.UNKNOWN if state is not TemporalState.CURRENT else motion,
|
||||
motion_confidence=(
|
||||
0.0
|
||||
if state is not TemporalState.CURRENT or motion is MotionState.UNKNOWN
|
||||
else 1.0
|
||||
0.0 if state is not TemporalState.CURRENT or motion is MotionState.UNKNOWN else 1.0
|
||||
),
|
||||
motion_reason=(
|
||||
"stale-support"
|
||||
@@ -127,9 +133,35 @@ def test_replay_threat_profile_freezes_virtual_authority_and_dual_evidence_polic
|
||||
)
|
||||
|
||||
|
||||
def test_recorded_body_frame_is_grounded_and_does_not_inherit_handheld_roll_pitch() -> None:
|
||||
profile = load_replay_threat_profile(PROFILE_PATH)
|
||||
resolver = RecordedReplayBodyFrameResolver(
|
||||
RecordedGeometryStore.from_repository(REPOSITORY_ROOT),
|
||||
profile=profile.body_frame,
|
||||
)
|
||||
|
||||
start = resolver.body_frame_for_frame("frame-000000")
|
||||
middle = resolver.body_frame_for_frame("frame-000138")
|
||||
later = resolver.body_frame_for_frame("frame-000274")
|
||||
|
||||
assert start is None # the opening surface height is not qualified evidence
|
||||
assert middle is not None and later is not None
|
||||
assert tuple(row[2] for row in middle.basis_map_from_body) == (0.0, 0.0, 1.0)
|
||||
assert tuple(row[2] for row in later.basis_map_from_body) == (0.0, 0.0, 1.0)
|
||||
assert middle.sensor_height_m == pytest.approx(1.2509065924)
|
||||
assert later.sensor_height_m == pytest.approx(1.2838213430)
|
||||
assert middle.camera_forward_alignment_deg < 7.0
|
||||
assert later.camera_forward_alignment_deg < 2.0
|
||||
|
||||
summary = resolver.qualification_summary()
|
||||
assert summary["available"] == 3928
|
||||
assert summary["qualified"] == 3861
|
||||
assert summary["rejected"] == 67
|
||||
|
||||
|
||||
def test_static_crossing_approaching_and_geometry_only_critical_cases_are_never_safe() -> None:
|
||||
provider = DualEvidenceReplayThreatProvider(
|
||||
pose_resolver=_Poses(),
|
||||
body_frame_resolver=_BodyFrames(),
|
||||
profile=load_replay_threat_profile(PROFILE_PATH),
|
||||
)
|
||||
current_frame = "frame-000002"
|
||||
@@ -179,17 +211,17 @@ def test_static_crossing_approaching_and_geometry_only_critical_cases_are_never_
|
||||
assert {item.decision for item in result} == {ThreatDecision.THREAT}
|
||||
assert all(item.corridor_intersection is CorridorIntersection.INTERSECTS for item in result)
|
||||
assert (
|
||||
next(item for item in result if item.component_id == "approaching").ttc_seconds
|
||||
is not None
|
||||
next(item for item in result if item.component_id == "approaching").ttc_seconds is not None
|
||||
)
|
||||
assert (
|
||||
"geometry-only-evidence"
|
||||
in next(item for item in result if item.component_id == "geometry-only").reason_codes
|
||||
)
|
||||
assert "geometry-only-evidence" in next(
|
||||
item for item in result if item.component_id == "geometry-only"
|
||||
).reason_codes
|
||||
|
||||
|
||||
def test_receding_and_static_outside_are_clear_but_incomplete_evidence_is_unknown() -> None:
|
||||
provider = DualEvidenceReplayThreatProvider(
|
||||
pose_resolver=_Poses(),
|
||||
body_frame_resolver=_BodyFrames(),
|
||||
profile=load_replay_threat_profile(PROFILE_PATH),
|
||||
)
|
||||
current_frame = "frame-000002"
|
||||
@@ -246,7 +278,7 @@ def test_receding_and_static_outside_are_clear_but_incomplete_evidence_is_unknow
|
||||
|
||||
def test_semantic_hint_and_ephemeral_component_name_do_not_change_threat_geometry() -> None:
|
||||
provider = DualEvidenceReplayThreatProvider(
|
||||
pose_resolver=_Poses(),
|
||||
body_frame_resolver=_BodyFrames(),
|
||||
profile=load_replay_threat_profile(PROFILE_PATH),
|
||||
)
|
||||
history = (
|
||||
|
||||
Reference in New Issue
Block a user