1de88a5d0a
- 内存:prepare_segmentation 改为流式解码,只留分屏和配准的抽样帧,绿框逐帧在两半上找; 分割那一半由 SegmentationInput.target_frames() 到用时再解码,segment_video 边读边写 jpg; MedSAM2 init_state 的读帧换成直接在存放设备上建张量(不改 MedSAM2 源码,结果逐位相同); 传播结果写进预分配数组。帧数最多的一段视频常驻内存峰值 11.2 GiB → 2.3 GiB,分辨率最大的 一段原代码被 OOM 杀掉、现为 4.2 GiB;两段的掩码和全部产物与改前逐字节相同,33 段视频的 第 1–2 步结果与改前一致,29 例配准与 review.csv 一致。 - 快照:ceus_quant 开跑时写 snapshot/(git、ceus.yaml、review.csv、uv.lock、segment.json), 配置和核对表从快照副本读;收尾由新函数 runs.finish_snapshot 只新建 finished.json。 build 与 ceus_align_review 同样改用 finish_snapshot 记结束时间。 - build:summary.json 加 video_sha256;装 CEUS 结果时核对快照里的 delivery_dir、逐例核对 视频 SHA-256 和推注时间,不同就整次停下;新字段做类型检查,不进 tic.json。 来源:主会话任务(问题 1–3 及补充的 delivery_dir 比对)。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
109 lines
4.1 KiB
Python
109 lines
4.1 KiB
Python
"""造影画面在哪一半:两半各自选提示帧,恰好一半有才定(任务书 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_frames_are_iterated_once(ceus_config: CeusConfig) -> None:
|
||
"""帧只遍历一遍:传逐帧交出的生成器,结果与传列表相同。"""
|
||
frames = _frames([250])
|
||
_, left, right = split_views(frames, ceus_config.views)
|
||
from_list = locate_contrast_side(
|
||
frames, left, right, ceus_config.prompt_source, ceus_config.keyframes
|
||
)
|
||
from_generator = locate_contrast_side(
|
||
(frame for frame in frames), left, right, ceus_config.prompt_source, ceus_config.keyframes
|
||
)
|
||
assert from_generator.side == from_list.side == "right"
|
||
assert [p.frame_index for p in from_generator.prompt_frames] == [0]
|
||
assert np.array_equal(from_generator.prompt_frames[0].mask, from_list.prompt_frames[0].mask)
|
||
|
||
|
||
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
|