GoWelcome/tests/test_road_logic.py

94 lines
3.6 KiB
Python

"""Pure-geometry tests for :class:`gowelcome.types.RoadInfo` and
:class:`gowelcome.types.Detection` derived properties. No cv2 / SDK / hardware.
"""
from __future__ import annotations
import pytest
from gowelcome.types import Detection, RoadInfo
# --------------------------------------------------------------------------- #
# RoadInfo.clearer_side
# --------------------------------------------------------------------------- #
def test_clearer_side_prefers_right_when_right_has_less_road():
"""Less road on the right (right <= left) -> clearer_side +1 (right is the clear side).
Note: clearer_side is a *side index* (+1 = right), not a yaw sign; the state
machine negates it to obtain the CW/right turn that steers away from the road.
"""
info = RoadInfo(coverage=0.5, left=0.8, center=0.5, right=0.1)
assert info.clearer_side == 1
def test_clearer_side_prefers_left_when_left_has_less_road():
"""Less road on the left -> -1."""
info = RoadInfo(coverage=0.5, left=0.1, center=0.5, right=0.8)
assert info.clearer_side == -1
def test_clearer_side_ties_to_right():
"""Equal coverage is a tie; the contract resolves ties to +1 (right)."""
info = RoadInfo(coverage=0.4, left=0.4, center=0.4, right=0.4)
assert info.clearer_side == 1
def test_clearer_side_is_pure_without_mask():
"""clearer_side works with mask=None (no cv2 involvement)."""
info = RoadInfo(coverage=0.0, left=0.2, center=0.0, right=0.05, mask=None)
assert info.mask is None
assert info.clearer_side == 1
# --------------------------------------------------------------------------- #
# Detection geometry
# --------------------------------------------------------------------------- #
def test_detection_centroid_and_dimensions():
"""cx, cy, w, h, area are computed from the corners."""
det = Detection(label="person", conf=0.9, x1=100, y1=200, x2=300, y2=500)
assert det.cx == pytest.approx(200.0) # (100 + 300) / 2
assert det.cy == pytest.approx(350.0) # (200 + 500) / 2
assert det.w == pytest.approx(200.0)
assert det.h == pytest.approx(300.0)
assert det.area == pytest.approx(200.0 * 300.0)
def test_detection_height_ratio():
"""height_ratio is box height over frame height."""
det = Detection(label="person", conf=0.9, x1=0, y1=120, x2=50, y2=360)
# h = 240, frame_h = 480 -> 0.5
assert det.height_ratio(480) == pytest.approx(0.5)
def test_detection_height_ratio_guards_zero_frame():
"""A zero/garbage frame height does not divide-by-zero."""
det = Detection(label="person", conf=0.9, x1=0, y1=0, x2=10, y2=100)
# max(1, frame_h) guard -> finite result.
val = det.height_ratio(0)
assert val == pytest.approx(100.0)
def test_detection_horizontal_offset_centre_is_zero():
"""A box centred in the frame has ~zero horizontal offset."""
frame_w = 640
det = Detection(label="person", conf=0.9, x1=300, y1=0, x2=340, y2=10) # cx=320
assert det.horizontal_offset(frame_w) == pytest.approx(0.0)
def test_detection_horizontal_offset_sign_right_positive():
"""A box right of centre has a positive (+) offset, left has negative (-)."""
frame_w = 640
right = Detection(label="person", conf=0.9, x1=600, y1=0, x2=640, y2=10)
left = Detection(label="person", conf=0.9, x1=0, y1=0, x2=40, y2=10)
assert right.horizontal_offset(frame_w) > 0.0
assert left.horizontal_offset(frame_w) < 0.0
def test_detection_horizontal_offset_full_right_near_one():
"""A box hard against the right edge approaches +1."""
frame_w = 640
det = Detection(label="person", conf=0.9, x1=636, y1=0, x2=640, y2=10) # cx=638
off = det.horizontal_offset(frame_w)
assert 0.9 < off <= 1.0