"""测试用合成 CEUS 视频:400×200、10 fps、40 秒的分屏造影视频(test_ceus_pipeline、test_ceus_quant、 test_ceus_align_review 共用)。 两半各有一块位置相同、带纹理、随时间轻微起伏的扇形区(`fake_split_scene`,配准平移 (0, 0))。 造影画面在右半:病灶是整帧 `LESION` 方块,亮度按 Lognormal 团注曲线 `BOLUS` 变化,内部有一块一直 发黑的囊 `CYST`;`green_box` 为真时画医生的绿框(病灶的空心描边)。 """ from pathlib import Path import cv2 import numpy as np from cadx.ceus import LognormalParameters, evaluate_lognormal from fake_split_scene import make_scene, scene_gray, to_bgr WIDTH, HEIGHT, SPLIT = 400, 200, 200 FPS, FRAMES = 10.0, 400 LESION = (250, 60, 369, 159) # 整帧坐标 x1, y1, x2, y2(含端点) CYST = (290, 95, 320, 125) BOLUS = LognormalParameters(area=1300.0, mu=2.4, sigma=0.45, arrival_offset_s=4.0, baseline=40.0) def write_video(path: Path, *, green_box: bool) -> None: rng = np.random.default_rng(3) intensity = evaluate_lognormal(np.arange(FRAMES) / FPS, BOLUS) # 两半是同一幅带纹理的扇形,平移 (0, 0):配准应估出 (0, 0),绿框原位挪到左半。 scene = make_scene( height=HEIGHT, half_width=SPLIT, fan_polygon=[(30, 22), (170, 22), (197, 192), (3, 192)], offset=(0, 0), interior_shift=None, texture_std=20.0, # 造影那一半调暗(扇形内平均约 32,与改动前的 30 相当):mp4 压缩把绿框描边的颜色渗到 # 相邻像素,背景亮时渗出的像素更容易落进绿框的 HSV 范围,绿框会被认大。 right_gain=0.15, right_bias=0.0, seed=7, ) x1, y1, x2, y2 = LESION # 病灶里保留组织纹理(减去它在病灶方块内的均值),病灶平均亮度仍等于团注曲线。 detail = scene.right[y1 : y2 + 1, x1 - SPLIT : x2 - SPLIT + 1] detail = detail - detail.mean() writer = cv2.VideoWriter(str(path), cv2.VideoWriter.fourcc(*"mp4v"), FPS, (WIDTH, HEIGHT)) for index in range(FRAMES): gray = scene_gray(scene, index, rng) gray[:, SPLIT] = 0 # 分屏缝 lesion = intensity[index] + detail + rng.normal(0.0, 6.0, detail.shape) gray[y1 : y2 + 1, x1 : x2 + 1] = lesion cx1, cy1, cx2, cy2 = CYST gray[cy1 : cy2 + 1, cx1 : cx2 + 1] = 10 frame = np.ascontiguousarray(to_bgr(gray)) if green_box: cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), thickness=3) writer.write(frame) writer.release()