239 lines
7.0 KiB
Python
239 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from k1link.compute.e31_source_qualification import (
|
|
E31SourceQualificationError,
|
|
E31SourceQualificationProfile,
|
|
_E30Chain,
|
|
_offset_sweep,
|
|
_self_mask_report,
|
|
)
|
|
from k1link.compute.lidar_local_surface import POINT_OCCUPIED
|
|
|
|
|
|
class _SweepSource:
|
|
def __init__(self) -> None:
|
|
self.arrays = {
|
|
"session_seconds": np.asarray([0.0, 0.1, 0.2], dtype=np.float64),
|
|
"sample_available": np.ones(3, dtype=np.bool_),
|
|
"cloud_offsets": np.asarray([0, 2, 4, 6], dtype=np.int64),
|
|
"cloud_points_map": np.asarray(
|
|
[
|
|
[1.0, 0.0, 2.0],
|
|
[1.1, 0.0, 2.0],
|
|
[0.0, 0.0, 2.0],
|
|
[0.1, 0.0, 2.0],
|
|
[-1.0, 0.0, 2.0],
|
|
[-1.1, 0.0, 2.0],
|
|
],
|
|
dtype=np.float32,
|
|
),
|
|
"pose_positions_map": np.zeros((3, 3), dtype=np.float64),
|
|
"pose_quaternions_map_from_lidar": np.asarray(
|
|
[[0.0, 0.0, 0.0, 1.0]] * 3,
|
|
dtype=np.float64,
|
|
),
|
|
"intrinsic_fx_fy_cx_cy": np.asarray(
|
|
[100.0, 100.0, 50.0, 50.0],
|
|
dtype=np.float64,
|
|
),
|
|
"distortion_kb4": np.zeros(4, dtype=np.float64),
|
|
"t_camera_from_lidar": np.eye(4, dtype=np.float64),
|
|
}
|
|
self.identity: dict[str, Any] = {
|
|
"source_id": "sensor.camera.right",
|
|
"camera_slot": "camera_1",
|
|
"projection": {"width": 100, "height": 100},
|
|
}
|
|
|
|
|
|
class _SweepSurface:
|
|
def __init__(self) -> None:
|
|
self.arrays = {
|
|
"point_class": np.full(6, POINT_OCCUPIED, dtype=np.uint8),
|
|
}
|
|
|
|
|
|
def _artifact(path: Path) -> dict[str, object]:
|
|
return {
|
|
"path": path.name,
|
|
"byte_length": path.stat().st_size,
|
|
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
}
|
|
|
|
|
|
def _projection_artifact(
|
|
root: Path,
|
|
name: str,
|
|
pixels: list[list[float]],
|
|
) -> dict[str, object]:
|
|
path = root / f"{name}.npz"
|
|
np.savez(
|
|
path,
|
|
projected_pixels_xy=np.asarray(pixels, dtype=np.float32),
|
|
projected_selected_mask=np.ones(len(pixels), dtype=np.uint8),
|
|
)
|
|
return _artifact(path)
|
|
|
|
|
|
def test_profile_rejects_ambiguous_offset_hypotheses() -> None:
|
|
with pytest.raises(E31SourceQualificationError):
|
|
E31SourceQualificationProfile(offset_hypotheses_ms=(0, -50, 50))
|
|
with pytest.raises(E31SourceQualificationError):
|
|
E31SourceQualificationProfile(offset_hypotheses_ms=(-50, 50))
|
|
|
|
|
|
def test_offset_sweep_keeps_evidenced_zero_binding() -> None:
|
|
source = _SweepSource()
|
|
surface = _SweepSurface()
|
|
profile = E31SourceQualificationProfile(
|
|
offset_hypotheses_ms=(-100, 0, 100),
|
|
minimum_correspondence_items=1,
|
|
)
|
|
item = {
|
|
"item_id": "item-1",
|
|
"review_key": "semantic:1:1",
|
|
"evidence_binding": {"frame_index": 1},
|
|
"e29_snapshot": {"bbox_xyxy": [40.0, 40.0, 60.0, 60.0]},
|
|
}
|
|
|
|
sweep, rows = _offset_sweep(
|
|
source=source, # type: ignore[arg-type]
|
|
surface=surface, # type: ignore[arg-type]
|
|
items=(item,),
|
|
profile=profile,
|
|
bbox_inset_fraction=0.03,
|
|
)
|
|
|
|
assert sweep["selected_offset_ms"] == 0
|
|
assert sweep["baseline_supported_fraction"] == 1.0
|
|
assert sweep["best_supported_fraction"] == 1.0
|
|
assert sweep["baseline_support_deficit_fraction"] == 0.0
|
|
assert [row["supported_count"] for row in sweep["hypotheses"]] == [0, 1, 0]
|
|
assert [score["candidate_frame_index"] for score in rows[0]["scores"]] == [
|
|
0,
|
|
1,
|
|
2,
|
|
]
|
|
|
|
|
|
def test_self_mask_admits_only_non_colliding_semantic_rule(
|
|
tmp_path: Path,
|
|
) -> None:
|
|
items: list[dict[str, Any]] = []
|
|
decisions: list[dict[str, Any]] = []
|
|
for index in range(4):
|
|
item = {
|
|
"item_id": f"semantic-self-{index}",
|
|
"e29_snapshot": {
|
|
"label": "person",
|
|
"bbox_xyxy": [
|
|
20.0 + index,
|
|
75.0,
|
|
40.0 + index,
|
|
99.0,
|
|
],
|
|
},
|
|
}
|
|
items.append(item)
|
|
decisions.append(
|
|
{
|
|
"item_id": item["item_id"],
|
|
"cause_code": "self_points",
|
|
"point_ownership": "self",
|
|
}
|
|
)
|
|
|
|
geometry_self = {
|
|
"item_id": "geometry-self",
|
|
"e29_snapshot": {},
|
|
"artifact": _projection_artifact(
|
|
tmp_path,
|
|
"geometry-self",
|
|
[[20.0, 20.0], [30.0, 30.0]],
|
|
),
|
|
}
|
|
accepted_object = {
|
|
"item_id": "accepted-object",
|
|
"e29_snapshot": {},
|
|
"artifact": _projection_artifact(
|
|
tmp_path,
|
|
"accepted-object",
|
|
[[25.0, 25.0], [80.0, 80.0]],
|
|
),
|
|
}
|
|
accepted_person = {
|
|
"item_id": "accepted-person",
|
|
"e29_snapshot": {
|
|
"label": "person",
|
|
"bbox_xyxy": [70.0, 50.0, 90.0, 90.0],
|
|
},
|
|
"artifact": _projection_artifact(
|
|
tmp_path,
|
|
"accepted-person",
|
|
[[80.0, 80.0]],
|
|
),
|
|
}
|
|
items.extend([geometry_self, accepted_object, accepted_person])
|
|
decisions.extend(
|
|
[
|
|
{
|
|
"item_id": geometry_self["item_id"],
|
|
"cause_code": "self_points",
|
|
"point_ownership": "self",
|
|
},
|
|
{
|
|
"item_id": accepted_object["item_id"],
|
|
"cause_code": "none",
|
|
"point_ownership": "object",
|
|
},
|
|
{
|
|
"item_id": accepted_person["item_id"],
|
|
"cause_code": "none",
|
|
"point_ownership": "object",
|
|
},
|
|
]
|
|
)
|
|
chain = _E30Chain(
|
|
materialization_manifest={
|
|
"identity": {
|
|
"projection": {
|
|
"width": 100,
|
|
"height": 100,
|
|
}
|
|
}
|
|
},
|
|
items=tuple(items),
|
|
engineering_manifest={},
|
|
decisions=tuple(decisions),
|
|
exceptions=(),
|
|
human_manifest={},
|
|
human_decisions=(),
|
|
)
|
|
|
|
report = _self_mask_report(
|
|
materialization_root=tmp_path,
|
|
chain=chain,
|
|
profile=E31SourceQualificationProfile(
|
|
minimum_semantic_self_samples=4,
|
|
),
|
|
)
|
|
|
|
assert report["semantic_mask"]["status"] == "admitted"
|
|
assert report["semantic_mask"]["application_rule"] == "bbox-center-inside-rectangle"
|
|
assert report["semantic_mask"]["collateral_item_count"] == 0
|
|
assert report["geometry_point_mask"]["status"] == "rejected"
|
|
assert report["geometry_point_mask"]["collateral"] == [
|
|
{
|
|
"item_id": "accepted-object",
|
|
"masked_selected_point_count": 1,
|
|
}
|
|
]
|
|
assert report["exact_correction_item_ids"] == ["geometry-self"]
|