94 lines
3.6 KiB
Python
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
|