"""Unit tests for vision primitives (synthetic BGR masks, fast, deterministic).""" from __future__ import annotations import cv2 import numpy as np from atm.vision import find_top_dots BG_RGB = (18, 18, 18) # background in RGB def _make_frame(h: int = 30, w: int = 100) -> np.ndarray: """Blank BGR frame filled with BG_RGB.""" bgr_bg = (BG_RGB[2], BG_RGB[1], BG_RGB[0]) frame = np.zeros((h, w, 3), dtype=np.uint8) frame[:, :] = bgr_bg return frame def _paint_dot(frame: np.ndarray, cx: int, cy: int, radius: int = 5, bgr: tuple[int, int, int] = (0, 255, 0)) -> None: # radius ≥ 5 keeps blob above min_cluster_px after 2× erosion by 3x3 kernel. cv2.circle(frame, (cx, cy), radius, bgr, -1) def test_find_top_dots_happy_three_blobs_sorted_desc(): frame = _make_frame() _paint_dot(frame, 10, 15) _paint_dot(frame, 30, 15) _paint_dot(frame, 50, 15) result = find_top_dots(frame, BG_RGB, n=3) assert len(result) == 3 # Sorted by right edge descending → x=50 first, then 30, then 10. xs = [pt[0] for pt in result] assert xs[0] > xs[1] > xs[2] assert xs[0] >= 48 and xs[2] <= 12 # allow ±2px wobble from centroid def test_find_top_dots_zero_blobs_returns_empty(): frame = _make_frame() assert find_top_dots(frame, BG_RGB, n=3) == [] def test_find_top_dots_one_blob_n3_returns_one(): frame = _make_frame() _paint_dot(frame, 25, 15) result = find_top_dots(frame, BG_RGB, n=3) assert len(result) == 1 cx, _cy = result[0] assert 23 <= cx <= 27 def test_find_top_dots_fused_wide_blob_anchors_to_right_edge(): frame = _make_frame() # Paint a wide stripe (width > 12) — simulates fused anti-aliased dots. cv2.rectangle(frame, (20, 13), (60, 17), (0, 255, 0), -1) result = find_top_dots(frame, BG_RGB, n=1) assert len(result) == 1 cx, _cy = result[0] # Anchor should be near right edge (~58 = 60-2), not centroid (~40). assert cx >= 55 def test_find_top_dots_tie_break_by_y_ascending(): frame = _make_frame(h=40) # Two dots at same right-edge x=50, different y. _paint_dot(frame, 50, 10) # upper — should come first _paint_dot(frame, 50, 30) # lower result = find_top_dots(frame, BG_RGB, n=2) assert len(result) == 2 # Tie-break: smaller y first. assert result[0][1] < result[1][1]