117 lines
2.9 KiB
Python
117 lines
2.9 KiB
Python
from __future__ import annotations
|
|
|
|
import math
|
|
|
|
import pytest
|
|
|
|
from k1link.compute.e45_binding_sensitivity import (
|
|
E45_ROW_SCHEMA,
|
|
E45BindingSensitivityError,
|
|
E45BindingSensitivityProfile,
|
|
analyze_binding_sensitivity,
|
|
)
|
|
|
|
|
|
def _row(
|
|
item_id: str,
|
|
*,
|
|
radius: float,
|
|
speed: float,
|
|
angular: float,
|
|
lidar_age: float,
|
|
pose_age: float,
|
|
residual: float,
|
|
points: int,
|
|
) -> dict[str, object]:
|
|
age_seconds = (lidar_age + pose_age) / 1000.0
|
|
return {
|
|
"schema_version": E45_ROW_SCHEMA,
|
|
"item_id": item_id,
|
|
"image_radius_normalized": radius,
|
|
"translation_speed_mps": speed,
|
|
"angular_speed_deg_s": angular,
|
|
"lidar_camera_age_ms": lidar_age,
|
|
"pose_point_age_ms": pose_age,
|
|
"motion_exposure_translation_m": speed * age_seconds,
|
|
"motion_exposure_rotation_deg": angular * age_seconds,
|
|
"centroid_residual_bbox_diagonal": residual,
|
|
"occupied_points_in_bbox": points,
|
|
"supported": points >= 2,
|
|
}
|
|
|
|
|
|
def test_e45_stratifies_existing_diagnostic_residual_without_target_claim() -> None:
|
|
rows = [
|
|
_row(
|
|
"one",
|
|
radius=0.2,
|
|
speed=0.0,
|
|
angular=0.0,
|
|
lidar_age=5.0,
|
|
pose_age=2.0,
|
|
residual=0.1,
|
|
points=5,
|
|
),
|
|
_row(
|
|
"two",
|
|
radius=0.7,
|
|
speed=0.5,
|
|
angular=6.0,
|
|
lidar_age=40.0,
|
|
pose_age=15.0,
|
|
residual=0.2,
|
|
points=8,
|
|
),
|
|
_row(
|
|
"three",
|
|
radius=1.0,
|
|
speed=2.0,
|
|
angular=20.0,
|
|
lidar_age=80.0,
|
|
pose_age=35.0,
|
|
residual=0.4,
|
|
points=12,
|
|
),
|
|
]
|
|
|
|
analysis = analyze_binding_sensitivity(rows)
|
|
|
|
assert analysis["correspondence_count"] == 3
|
|
assert analysis["supported_fraction"] == 1.0
|
|
assert [
|
|
item["count"] for item in analysis["strata"]["image_radius"]
|
|
] == [1, 1, 1]
|
|
assert math.isclose(
|
|
analysis["spearman_residual_correlation"][
|
|
"image_radius_normalized"
|
|
],
|
|
1.0,
|
|
)
|
|
assert (
|
|
analysis["measured_calibration_target_residual_available"] is False
|
|
)
|
|
assert analysis["physical_mount_inferred"] is False
|
|
|
|
|
|
def test_e45_rejects_missing_support_in_accepted_correspondence() -> None:
|
|
rows = [
|
|
_row(
|
|
"one",
|
|
radius=0.2,
|
|
speed=0.0,
|
|
angular=0.0,
|
|
lidar_age=5.0,
|
|
pose_age=2.0,
|
|
residual=0.1,
|
|
points=1,
|
|
)
|
|
]
|
|
|
|
with pytest.raises(E45BindingSensitivityError, match="supported"):
|
|
analyze_binding_sensitivity(rows)
|
|
|
|
|
|
def test_e45_profile_rejects_overlapping_or_reversed_edges() -> None:
|
|
with pytest.raises(E45BindingSensitivityError):
|
|
E45BindingSensitivityProfile(image_radius_edges=(0.8, 0.4))
|