Files
CADx-4/tests/test_ceus_contrast_side.py
T
iomgaa 00ebbffb28 feat(ceus): 两半坐标换算改为每段视频配准,加目视核对脚本
- 第 1 步分屏后在两半扇形区内用边缘图归一化互相关估 (dx, dy),再用逐帧多块模板匹配的中位数独立核对;
  两者各分量之差超过门槛(15 像素)这一例第 1 步失败。删掉「两个视图左上角相减」的实现与测试。
- 门槛依据:34 例中走完第 1 步的 29 例,差值中位 2、95 分位 6.2、最大 13;参数与门槛在同一批视频上调出。
- scripts/ceus_align_review.py:只做第 1 步,出扇形区棋盘图与边缘叠加图,混入错开 15–30 像素的
  阴性对照、随机编号;计分时任一对照未被判「没对齐」即作废。
- ceus_quant 读 review.csv:不通过的病例第 1 步失败;表里没有这段视频或平移对不上即停。
- 配准与紫圈颜色参数进分割缓存键。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 06:47:27 -04:00

94 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""造影画面在哪一半:两半各自选提示帧,恰好一半有才定(任务书 02 第一阶段第 2 条)。
合成 400×200 的分屏视频,分屏线在 x=200;绿框是 120×100 的实心框(外接框面积 12000,
大于 config/ceus.yaml 的 min_area 10000),画在顶部文字带之下。预期值按画框位置手算。
"""
import cv2
import numpy as np
import pytest
from cadx.ceus import (
CeusCaseError,
CeusConfig,
ContrastSideError,
ViewRect,
locate_contrast_side,
map_source_mask_to_target,
split_views,
)
from cadx.ceus._geometry import Frame
_WIDTH, _HEIGHT, _SPLIT = 400, 200, 200
_GREEN = (0, 255, 0)
def _frames(green_boxes_x1: list[int]) -> list[Frame]:
"""10 帧分屏画面;每个 x1 画一个整帧坐标 [x1, x1+119] × [60, 159] 的绿框。"""
frame = np.zeros((_HEIGHT, _WIDTH, 3), dtype=np.uint8)
frame[:, :_SPLIT] = 120
frame[:, _SPLIT + 1 :] = 90
for x1 in green_boxes_x1:
cv2.rectangle(frame, (x1, 60), (x1 + 119, 159), _GREEN, thickness=-1)
return [frame.copy() for _ in range(10)]
def test_green_box_in_left_half(ceus_config: CeusConfig) -> None:
frames = _frames([40])
_, left, right = split_views(frames, ceus_config.views)
assert (left, right) == (ViewRect(0, 0, 200, 200), ViewRect(200, 0, 400, 200))
side = locate_contrast_side(
frames, left, right, ceus_config.prompt_source, ceus_config.keyframes
)
assert side.side == "left"
assert side.contrast_view == left
assert side.target_view == right
# 10 帧绿框不变:只有第一帧是提示帧。
assert [p.frame_index for p in side.prompt_frames] == [0]
x1, y1, _, _ = side.prompt_frames[0].bbox_xyxy
assert abs(x1 - 40) <= 3 and abs(y1 - 60) <= 3
def test_green_box_in_right_half(ceus_config: CeusConfig) -> None:
frames = _frames([250])
_, left, right = split_views(frames, ceus_config.views)
side = locate_contrast_side(
frames, left, right, ceus_config.prompt_source, ceus_config.keyframes
)
assert side.side == "right"
assert side.contrast_view == right
assert side.target_view == left
# 整帧 x=250 在右半局部是 250 − 200 = 50。
prompt = side.prompt_frames[0]
assert abs(prompt.bbox_xyxy[0] - 50) <= 3
# 挪到左半(灰阶那一半),两半扇形对齐、配准平移为 (0, 0):局部 x 仍是 50。
mapped = map_source_mask_to_target(prompt.mask, (0, 0), side.target_view)
xs = np.where(mapped)[1]
assert abs(int(xs.min()) - 50) <= 3
assert abs(int(xs.max()) - 169) <= 3
def test_green_box_in_both_halves_fails_case(ceus_config: CeusConfig) -> None:
frames = _frames([40, 250])
_, left, right = split_views(frames, ceus_config.views)
with pytest.raises(ContrastSideError, match="两半都找到绿框") as raised:
locate_contrast_side(frames, left, right, ceus_config.prompt_source, ceus_config.keyframes)
assert isinstance(raised.value, CeusCaseError)
assert raised.value.step == 2
def test_no_green_box_fails_case(ceus_config: CeusConfig) -> None:
frames = _frames([])
_, left, right = split_views(frames, ceus_config.views)
with pytest.raises(ContrastSideError, match="两半都没有找到绿框") as raised:
locate_contrast_side(frames, left, right, ceus_config.prompt_source, ceus_config.keyframes)
assert raised.value.step == 2