"""B2 split_screen 分屏检测单元测试(旧文件的黑边剔除部分随裁黑边功能删去)。 钉死两件事的关键性质: - ``detect_split_x``:合成左右两半(中间一列黑缝)→ 切分线落在中缝 ±2px; 全黑 / 无缝 / 切分偏出中部合理范围 → ``SplitDetectionError``(不兜底)。 - ``view_rect``:horizontal_split 用检测到的 ``split_x`` 而非 ``W//2``; vertical_split / full / manual_crop 分支与参考一致;非法组合 ``RuntimeError``。 测试用真实数据的二次构造(合成左右分屏帧),边缘场景靠实现 raise 保证。 """ import numpy as np import pytest from cadx.ceus import ( SplitDetectionError, ViewRect, detect_split_x, view_rect, ) from conftest import load_ceus_section # 旧 detect_split_x 的参数默认值;新代码不设默认值,从 config/ceus.yaml 读。 _LO, _HI = load_ceus_section()["views"]["split_search_band"] BAND = (float(_LO), float(_HI)) def _split_frame( height: int, width: int, split_x: int, *, left_gray: int = 180, right_gray: int = 120, ) -> np.ndarray: """合成一帧左右分屏 BGR 图:左右两半各为常量灰度,``split_x`` 处一列纯黑缝。""" frame = np.zeros((height, width, 3), dtype=np.uint8) frame[:, :split_x] = left_gray frame[:, split_x + 1 :] = right_gray frame[:, split_x] = 0 return frame # Phase 1: detect_split_x —— 中缝定位与防御 def test_detect_split_x_finds_centered_seam() -> None: frames = [_split_frame(120, 200, 100) for _ in range(5)] detected = detect_split_x(frames, search_band=BAND) assert abs(detected - 100) <= 2 def test_detect_split_x_finds_offcenter_seam_within_band() -> None: # 缝在 0.42*W 处,仍落在 band=(0.35,0.65) 内、左右宽度比合法。 frames = [_split_frame(120, 200, 84) for _ in range(5)] detected = detect_split_x(frames, search_band=BAND) assert abs(detected - 84) <= 2 def test_detect_split_x_all_black_raises() -> None: frames = [np.zeros((120, 200, 3), dtype=np.uint8) for _ in range(3)] with pytest.raises(SplitDetectionError): detect_split_x(frames, search_band=BAND) def test_detect_split_x_no_seam_raises() -> None: # 整幅均匀灰度、无暗缝:中部 band 内列均值平坦,找不到稳定低谷。 frames = [np.full((120, 200, 3), 150, dtype=np.uint8) for _ in range(3)] with pytest.raises(SplitDetectionError): detect_split_x(frames, search_band=BAND) def test_detect_split_x_empty_frames_raises() -> None: with pytest.raises(SplitDetectionError): detect_split_x([], search_band=BAND) def test_detect_split_x_reversed_band_raises() -> None: # 逆序 band(lo>=hi)非法:要求 0<=lo None: # 越界 band(hi>1)非法。 frames = [_split_frame(120, 200, 100) for _ in range(3)] with pytest.raises(SplitDetectionError): detect_split_x(frames, search_band=(0.35, 1.5)) def test_detect_split_x_width_ratio_violation_raises() -> None: # 把 band 放宽到几乎整宽,缝置于极偏位置(仍在 band 内),使左右宽度比越界。 # split_x=20、W=200 → 左 20 / 右 180,比值 0.11 < 0.5 → SplitDetectionError。 frames = [_split_frame(120, 200, 20) for _ in range(3)] with pytest.raises(SplitDetectionError): detect_split_x(frames, search_band=(0.05, 0.95)) # Phase 2: view_rect —— horizontal_split 用 split_x,而非 W//2 def test_view_rect_horizontal_split_uses_split_x() -> None: width, height, split_x = 200, 120, 84 left = view_rect({"layout": "horizontal_split"}, "left", width, height, split_x=split_x) right = view_rect({"layout": "horizontal_split"}, "right", width, height, split_x=split_x) assert left == ViewRect(0, 0, split_x, height) assert right == ViewRect(split_x, 0, width, height) # 与朴素 W//2 不同,证明用了检测值。 assert split_x != width // 2 def test_view_rect_vertical_split() -> None: width, height = 200, 120 top = view_rect({"layout": "vertical_split"}, "top", width, height, split_x=None) bottom = view_rect({"layout": "vertical_split"}, "bottom", width, height, split_x=None) assert top == ViewRect(0, 0, width, height // 2) assert bottom == ViewRect(0, height // 2, width, height) def test_view_rect_full() -> None: rect = view_rect({"layout": "full"}, "left", 200, 120, split_x=None) assert rect == ViewRect(0, 0, 200, 120) def test_view_rect_manual_crop() -> None: cfg = {"layout": "manual_crop", "crops": {"left": [0.0, 0.0, 0.5, 1.0]}} rect = view_rect(cfg, "left", 200, 120, split_x=None) assert rect == ViewRect(0, 0, 100, 120) def test_view_rect_unsupported_pair_raises() -> None: with pytest.raises(RuntimeError): view_rect({"layout": "horizontal_split"}, "top", 200, 120, split_x=100)