Files
AareLC_train/tests/test_target_point.py
2026-04-14 16:07:31 +02:00

140 lines
5.2 KiB
Python

"""Unit tests for src/server/target_point.py"""
import pytest
from src.server.target_point import (
_poly_to_abs,
_shape_center,
_pin_left_midpoint,
_pick_best,
compute_target_point,
)
# ---------------------------------------------------------------------------
# _poly_to_abs
# ---------------------------------------------------------------------------
class TestPolyToAbs:
def test_none_poly_returns_none(self):
assert _poly_to_abs(None, 10, 20) is None
def test_translates_relative_to_absolute(self):
poly = [[1, 2], [3, 4], [5, 6]]
result = _poly_to_abs(poly, 10.0, 20.0)
assert result is not None
assert result[0, 0] == pytest.approx(11.0)
assert result[0, 1] == pytest.approx(22.0)
assert result[2, 0] == pytest.approx(15.0)
def test_rejects_fewer_than_3_points(self):
poly = [[1, 2], [3, 4]]
assert _poly_to_abs(poly, 0, 0) is None
def test_rejects_wrong_shape(self):
poly = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
assert _poly_to_abs(poly, 0, 0) is None
# ---------------------------------------------------------------------------
# _shape_center
# ---------------------------------------------------------------------------
class TestShapeCenter:
def test_falls_back_to_bbox_center_with_no_poly(self):
det = {"x1": 0.0, "y1": 0.0, "x2": 100.0, "y2": 100.0}
cx, cy = _shape_center(det)
assert cx == pytest.approx(50.0)
assert cy == pytest.approx(50.0)
def test_uses_polygon_centroid_when_poly_present(self):
# Unit square at origin: centroid == (0.5, 0.5) + offset (10, 20) = (10.5, 20.5)
poly = [[0, 0], [1, 0], [1, 1], [0, 1]]
det = {"x1": 10.0, "y1": 20.0, "x2": 11.0, "y2": 21.0, "poly": poly}
cx, cy = _shape_center(det)
assert cx == pytest.approx(10.5, abs=0.1)
assert cy == pytest.approx(20.5, abs=0.1)
def test_returns_none_on_missing_coords(self):
assert _shape_center({}) == (0.0, 0.0)
# ---------------------------------------------------------------------------
# _pin_left_midpoint
# ---------------------------------------------------------------------------
class TestPinLeftMidpoint:
def test_returns_x1_and_vertical_midpoint(self):
det = {"x1": 5.0, "y1": 10.0, "x2": 50.0, "y2": 30.0}
x, y = _pin_left_midpoint(det)
assert x == pytest.approx(5.0)
assert y == pytest.approx(20.0)
def test_returns_none_on_bad_coords(self):
assert _pin_left_midpoint({"x1": "bad", "y1": 0, "y2": 10}) is None
# ---------------------------------------------------------------------------
# _pick_best
# ---------------------------------------------------------------------------
class TestPickBest:
def test_returns_none_when_no_matching_label(self):
dets = [{"label": "pin", "conf": 0.9}]
assert _pick_best(dets, "crystal") is None
def test_returns_highest_confidence_detection(self):
dets = [
{"label": "crystal", "conf": 0.5},
{"label": "crystal", "conf": 0.9},
{"label": "crystal", "conf": 0.3},
]
best = _pick_best(dets, "crystal")
assert best["conf"] == pytest.approx(0.9)
def test_returns_none_for_empty_list(self):
assert _pick_best([], "crystal") is None
# ---------------------------------------------------------------------------
# compute_target_point — priority logic
# ---------------------------------------------------------------------------
class TestComputeTargetPoint:
def test_returns_none_for_empty_dets(self):
assert compute_target_point([]) is None
def test_crystal_takes_priority_over_loop_face(self):
dets = [
{"label": "loop_face", "conf": 0.99, "x1": 0, "y1": 0, "x2": 200, "y2": 200},
{"label": "crystal", "conf": 0.5, "x1": 10, "y1": 10, "x2": 50, "y2": 50},
]
result = compute_target_point(dets)
assert result is not None
assert result["source"] == "crystal_center"
def test_loop_face_takes_priority_over_loop_all(self):
dets = [
{"label": "loop_all", "conf": 0.99, "x1": 0, "y1": 0, "x2": 200, "y2": 200},
{"label": "loop_face", "conf": 0.5, "x1": 10, "y1": 10, "x2": 50, "y2": 50},
]
result = compute_target_point(dets)
assert result is not None
assert result["source"] == "loop_face_center"
def test_pin_used_as_last_resort(self):
dets = [{"label": "pin", "conf": 0.8, "x1": 20.0, "y1": 10.0, "x2": 80.0, "y2": 30.0}]
result = compute_target_point(dets)
assert result is not None
assert result["source"] == "pin_left_mid"
assert result["x"] == 20
assert result["y"] == 20
def test_output_contains_integer_coordinates(self):
dets = [{"label": "pin", "conf": 0.8, "x1": 10.7, "y1": 5.3, "x2": 60.0, "y2": 25.1}]
result = compute_target_point(dets)
assert isinstance(result["x"], int)
assert isinstance(result["y"], int)
def test_unknown_labels_return_none(self):
dets = [{"label": "unknown_object", "conf": 0.99, "x1": 0, "y1": 0, "x2": 100, "y2": 100}]
assert compute_target_point(dets) is None