feat(ceus): 任务书 02 第一阶段——底层和分割之前的算法;修复 C14
任务书 01 遗留修复(C14): - library 新增 check_key_image(path):关键图能逐像素保真重编码时返回 None,否则返回原因; _write_clean_png 的判断移过去,自身保留断言性质报错(走到这里说明 build 漏了检查)。 - build 写库前对每例调用,返回原因的整例跳过,记 C14「关键图能原样重编码」,涉及值写原因。 - 原来三条「关键图格式不对整次构建停下」的测试改为整例跳过,另加非 PNG、library 漏检报错两条。 - 结构.md 第八部分、guides/装配两个库.md 相应几行改掉。 第一阶段(src/cadx/ceus/): - 照搬旧 geometry、video_decoder、research_config、split_screen、green_prompt、prompt_frames、 mapping,去掉产品层,注释改中文。 - 配置类去掉全部默认值,strict、extra=forbid、frozen;新建 config/ceus.yaml(paths、ceus 两段), 数值照抄旧 yaml,逐项注明出处。删去 source_view/target_view、effective_region、mapping 锚点; 旧代码写死的分屏抽帧数 25 和搜索区间 (0.35, 0.65) 改成配置,值不变。 - 视频解码改为按路径用 cv2 逐帧读。 - 坐标换算改为两个视图矩形左上角之差,不再用按某种宽度写死的锚点。 - 造影画面在哪一半:两半各自选提示帧,恰好一半有才定,否则抛 ContrastSideError(CeusCaseError 子类,带步号和原因,供第三阶段记 failed.csv)。 - 旧测试照搬(改 import 与配置读取方式),因上述改动不再成立的删去;新增两处修改的手算测试。 - import-linter 加「ceus 不引用其他 cadx 模块」。依赖加 numpy、opencv-python-headless。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+49
-12
@@ -12,11 +12,13 @@ from PIL import Image
|
||||
from cadx.answers import read_aliases, read_diagnoses
|
||||
from cadx.build import make_aliases, run_build
|
||||
from cadx.library import (
|
||||
LibraryCase,
|
||||
list_aliases,
|
||||
read_clinical,
|
||||
read_mr,
|
||||
read_ultrasound,
|
||||
scan_for_leaks,
|
||||
write_library,
|
||||
)
|
||||
from fake_delivery import (
|
||||
CLINICAL_HEADER,
|
||||
@@ -218,24 +220,59 @@ def _build_with_key_image(tmp_path: Path, image: Image.Image) -> None:
|
||||
run_build(write_config(tmp_path, root, 1, None), REPO_ROOT, NOW)
|
||||
|
||||
|
||||
def test_key_image_16_bit_source_stops_build(tmp_path: Path) -> None:
|
||||
image = Image.new("I;16", (4, 4))
|
||||
with pytest.raises(ValueError, match=r"U002_key\.png"):
|
||||
_build_with_key_image(tmp_path, image)
|
||||
assert not (tmp_path / "library" / f"{NOW:%Y%m%d-%H%M%S}").exists()
|
||||
assert [p for p in (tmp_path / "library").iterdir() if not p.name.endswith(".partial")] == []
|
||||
def _assert_key_image_skipped(tmp_path: Path, reason: str) -> None:
|
||||
"""002 这一例记 C14 跳过、涉及值是原因;001 照常进库。"""
|
||||
(library_dir,) = list((tmp_path / "library").iterdir())
|
||||
answers_dir = tmp_path / "answers" / library_dir.name
|
||||
assert [r.case_no for r in read_aliases(answers_dir)] == ["001"]
|
||||
assert len(list_aliases(library_dir)) == 1
|
||||
skipped = _csv_rows(answers_dir / "skipped.csv")
|
||||
assert [row[:4] for row in skipped[1:]] == [
|
||||
["002", "C14", "关键图能原样重编码", "CEUS_agent/002/U002_key.png"]
|
||||
]
|
||||
assert json.loads(skipped[1][5]) == reason
|
||||
|
||||
|
||||
def test_key_image_with_transparency_stops_build(tmp_path: Path) -> None:
|
||||
def test_key_image_16_bit_source_skips_case(tmp_path: Path) -> None:
|
||||
_build_with_key_image(tmp_path, Image.new("I;16", (4, 4)))
|
||||
_assert_key_image_skipped(tmp_path, "位深 16、颜色类型 0 不能逐像素保真重编码")
|
||||
|
||||
|
||||
def test_key_image_with_transparency_skips_case(tmp_path: Path) -> None:
|
||||
image = Image.new("RGB", (4, 4))
|
||||
image.info["transparency"] = (0, 0, 0)
|
||||
with pytest.raises(ValueError, match=r"U002_key\.png"):
|
||||
_build_with_key_image(tmp_path, image)
|
||||
_build_with_key_image(tmp_path, image)
|
||||
_assert_key_image_skipped(tmp_path, "带 tRNS 透明色,重编码会丢透明")
|
||||
|
||||
|
||||
def test_key_image_palette_source_stops_build(tmp_path: Path) -> None:
|
||||
with pytest.raises(ValueError, match=r"U002_key\.png"):
|
||||
_build_with_key_image(tmp_path, Image.new("P", (4, 4)))
|
||||
def test_key_image_palette_source_skips_case(tmp_path: Path) -> None:
|
||||
_build_with_key_image(tmp_path, Image.new("P", (4, 4)))
|
||||
# 只有一种颜色的调色板图,Pillow 存成 1 位。
|
||||
_assert_key_image_skipped(tmp_path, "位深 1、颜色类型 3 不能逐像素保真重编码")
|
||||
|
||||
|
||||
def test_key_image_not_png_skips_case(tmp_path: Path) -> None:
|
||||
root = make_delivery(2).write(tmp_path / "delivery")
|
||||
Image.new("RGB", (4, 4)).save(root / "CEUS_agent" / "002" / "U002_key.png", format="JPEG")
|
||||
run_build(write_config(tmp_path, root, 1, None), REPO_ROOT, NOW)
|
||||
_assert_key_image_skipped(tmp_path, "不是 PNG")
|
||||
|
||||
|
||||
def test_write_library_rejects_unchecked_key_image(tmp_path: Path) -> None:
|
||||
"""build 漏了 C14 检查时,library 写图那一步报错停下。"""
|
||||
source = tmp_path / "key.png"
|
||||
Image.new("P", (4, 4)).save(source, format="PNG")
|
||||
target = tmp_path / "out"
|
||||
target.mkdir()
|
||||
case = LibraryCase(
|
||||
alias="abcd",
|
||||
clinical_fields={},
|
||||
ultrasound_report="",
|
||||
key_image_source=source,
|
||||
mr_report="",
|
||||
)
|
||||
with pytest.raises(ValueError, match="check_key_image"):
|
||||
write_library(target, "b", "c", True, {}, False, (), None, [case])
|
||||
|
||||
|
||||
def test_key_image_one_bit_kept_as_is(tmp_path: Path) -> None:
|
||||
|
||||
Reference in New Issue
Block a user