import numpy as np import pytest from atm.config import ROI from atm.layout import _strips_match, detect_strips PALETTE = { "turquoise": ((0, 253, 253), 60.0), "yellow": ((253, 253, 0), 60.0), "dark_green": ((0, 128, 0), 60.0), "dark_red": ((128, 0, 0), 60.0), "light_green": ((0, 255, 0), 60.0), "light_red": ((255, 0, 0), 60.0), } def _blank(h: int = 20, w: int = 200) -> np.ndarray: return np.zeros((h, w, 3), dtype=np.uint8) def _paint(img: np.ndarray, x0: int, x1: int, rgb: tuple[int, int, int]) -> None: """Paint vivid color into BGR image (palette stores RGB).""" bgr = (rgb[2], rgb[1], rgb[0]) img[:, x0:x1] = bgr def test_single_strip(): img = _blank(20, 200) _paint(img, 0, 200, (0, 253, 253)) # turquoise out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert len(out) == 1 assert out[0].x == 0 assert abs(out[0].w - 200) <= 1 assert out[0].h == 20 def test_split_50_50(): img = _blank(20, 230) _paint(img, 0, 100, (0, 253, 253)) _paint(img, 130, 230, (253, 253, 0)) out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert len(out) == 2 assert out[0].x < out[1].x # L->R assert out[0].x == 0 assert abs(out[0].w - 100) <= 1 assert out[1].x == 130 assert abs(out[1].w - 100) <= 1 def test_split_asymmetric(): img = _blank(20, 230) _paint(img, 0, 70, (0, 253, 253)) # 35% width _paint(img, 100, 230, (253, 253, 0)) # 65% width out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert len(out) == 2 assert abs(out[0].w - 70) <= 1 assert abs(out[1].w - 130) <= 1 def test_gray_only_no_strip(): img = _blank(20, 200) img[:, 50:150] = (128, 128, 128) out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert out == [] def test_cooldown_gray_dots_no_detect(): img = _blank(20, 200) # scattered gray dots for x in (20, 50, 80, 110, 140, 170): img[8:12, x:x + 4] = (100, 100, 100) out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert out == [] def test_vivid_palette_match(): img = _blank(20, 200) _paint(img, 50, 80, (0, 255, 0)) # light_green out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert len(out) == 1 def test_blank_frame(): img = _blank(20, 200) out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert out == [] def test_strip_too_narrow_filtered(): img = _blank(20, 200) _paint(img, 50, 53, (0, 253, 253)) # only 3px wide out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=10) assert out == [] def test_small_gap_fuses(): img = _blank(20, 200) _paint(img, 30, 70, (0, 253, 253)) _paint(img, 75, 120, (0, 253, 253)) # 5px gap, < min_gap_px=20 out = detect_strips(img, PALETTE, min_gap_px=20, min_strip_px=5) assert len(out) == 1 assert abs(out[0].x - 30) <= 2 assert abs((out[0].x + out[0].w) - 120) <= 2 def test_split_two_charts_with_interleaved_gray(): # Regresie 2 ferestre TS: fiecare row de buline e mix vivid + gri, separate # de un gap larg de background (dividerul dintre ferestre). Înainte de fix # detect_strips picka doar runs vivid contiguu și rata fereastra stângă. palette = {**PALETTE, "gray": ((128, 128, 128), 60.0)} img = _blank(35, 1796) # Left chart: dots vivid + gray la fiecare 26px, x=0..820 for i, x in enumerate(range(0, 820, 26)): rgb = (128, 128, 128) if i % 3 else (0, 128, 0) _paint(img, x, x + 22, rgb) # Window divider gap: x=820..910 rămâne background # Right chart: same pattern, x=910..1796 for i, x in enumerate(range(910, 1790, 26)): rgb = (128, 128, 128) if i % 3 else (0, 128, 0) _paint(img, x, x + 22, rgb) out = detect_strips(img, palette, min_gap_px=28, min_strip_px=280) assert len(out) == 2, f"expected 2 strips, got {len(out)}: {out}" assert out[0].x == 0 assert out[1].x >= 900 # right chart starts after divider assert out[0].x + out[0].w < out[1].x # disjoint def test_three_strips(): img = _blank(20, 300) _paint(img, 0, 60, (0, 253, 253)) _paint(img, 100, 160, (253, 253, 0)) _paint(img, 220, 300, (0, 255, 0)) out = detect_strips(img, PALETTE, min_gap_px=10, min_strip_px=5) assert len(out) == 3 assert out[0].x < out[1].x < out[2].x assert out[0].x == 0 assert out[1].x == 100 assert out[2].x == 220 def test_strips_match_identical(): a = [ROI(x=0, y=0, w=100, h=20), ROI(x=130, y=0, w=70, h=20)] b = [ROI(x=0, y=0, w=100, h=20), ROI(x=130, y=0, w=70, h=20)] assert _strips_match(a, b) is True def test_strips_match_jitter_5px(): a = [ROI(x=0, y=0, w=100, h=20), ROI(x=130, y=0, w=70, h=20)] b = [ROI(x=5, y=0, w=95, h=20), ROI(x=128, y=0, w=70, h=20)] assert _strips_match(a, b, tol=10) is True def test_strips_match_drift_12px(): a = [ROI(x=0, y=0, w=100, h=20)] b = [ROI(x=12, y=0, w=100, h=20)] assert _strips_match(a, b, tol=10) is False def test_strips_match_count_different(): a = [ROI(x=0, y=0, w=100, h=20)] b = [ROI(x=0, y=0, w=100, h=20), ROI(x=130, y=0, w=70, h=20)] assert _strips_match(a, b) is False