feat(perception): freeze blind detector gates

This commit is contained in:
DCCONSTRUCTIONS
2026-07-29 14:20:31 +03:00
parent 8b1f109b09
commit 82a44478fb
14 changed files with 3087 additions and 0 deletions
+116
View File
@@ -0,0 +1,116 @@
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))
+67
View File
@@ -0,0 +1,67 @@
from __future__ import annotations
import pytest
from k1link.compute.e46_detector_truth_island import (
E46DetectorTruthIslandError,
E46DetectorTruthIslandProfile,
select_truth_island_frames,
)
def _frames() -> list[dict[str, object]]:
rows = []
image_id = 1
for frame_index in range(48):
rows.append(
{
"image_id": image_id,
"frame_index": frame_index * 10,
"role": "anchor",
"group_id": f"anchor-{image_id:03d}",
}
)
image_id += 1
for group_index, start in enumerate((500, 600, 700, 800), start=1):
for offset in range(4):
rows.append(
{
"image_id": image_id,
"frame_index": start + offset,
"role": "temporal",
"group_id": f"clip-{group_index}",
}
)
image_id += 1
rows.sort(key=lambda row: int(row["frame_index"]))
return rows
def test_e46_selects_two_anchors_per_bin_and_all_temporal_groups() -> None:
frames = _frames()
selected = select_truth_island_frames(frames)
assert len(selected) == 32
assert sum(row["role"] == "anchor" for row in selected) == 16
assert sum(row["role"] == "temporal" for row in selected) == 16
assert {
row["group_id"]
for row in selected
if row["role"] == "temporal"
} == {"clip-1", "clip-2", "clip-3", "clip-4"}
assert selected == select_truth_island_frames(frames)
def test_e46_rejects_partial_or_nonconsecutive_temporal_group() -> None:
frames = _frames()
frames[-1]["frame_index"] = 900
frames.sort(key=lambda row: int(row["frame_index"]))
with pytest.raises(E46DetectorTruthIslandError, match="consecutive"):
select_truth_island_frames(frames)
def test_e46_profile_requires_two_reviewers_and_no_prelabel_mode() -> None:
with pytest.raises(E46DetectorTruthIslandError):
E46DetectorTruthIslandProfile(independent_reviewers_required=1)
@@ -0,0 +1,75 @@
from __future__ import annotations
import pytest
from k1link.compute.e47_detector_candidate_freeze import (
E47DetectorCandidateFreezeError,
normalize_candidate_predictions,
)
def test_e47_normalizes_raw_predictions_and_drops_outside_ontology() -> None:
result = normalize_candidate_predictions(
instances=[
{
"label": "car",
"score": 0.8,
"box_xyxy": [10.0, 20.0, 30.0, 40.0],
},
{
"label": "laptop",
"score": 0.9,
"box_xyxy": [20.0, 30.0, 40.0, 50.0],
},
],
label_mapping={"car": 4},
target_categories={4: "car"},
source_kind="raw",
)
assert result == (
{
"category_id": 4,
"category": "car",
"source_category": "car",
"score": 0.8,
"box_xyxy": [10.0, 20.0, 30.0, 40.0],
},
)
def test_e47_normalizes_fill_predictions_without_truth_fields() -> None:
result = normalize_candidate_predictions(
instances=[
{
"draft_category_id": 1,
"source_model_category": "person",
"score": 0.95,
"box_xyxy": [1.0, 2.0, 3.0, 4.0],
"review_state": "unreviewed-model-draft",
}
],
label_mapping={},
target_categories={1: "person"},
source_kind="fill",
)
assert result[0]["category"] == "person"
assert "review_state" not in result[0]
assert "truth" not in result[0]
def test_e47_rejects_invalid_box() -> None:
with pytest.raises(E47DetectorCandidateFreezeError, match="box"):
normalize_candidate_predictions(
instances=[
{
"label": "car",
"score": 0.8,
"box_xyxy": [30.0, 20.0, 10.0, 40.0],
}
],
label_mapping={"car": 4},
target_categories={4: "car"},
source_kind="raw",
)