Files
CADx-4/research-wiki/任务书/02-CEUS定量.md
T
iomgaa e78d1b50b8 docs(任务书02): 两半坐标换算改为每段视频配准,全量运行前由子 Agent 带阴性对照做目视核对
来源:执行者 2026-10-16 实测两半残余错位 −10 到 +182 像素;负责人 2026-10-16 同意配准方案和子 Agent 目视核对。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 05:54:15 -04:00

258 lines
27 KiB
Markdown
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:CEUS 定量
把旧项目的 CEUS(超声造影视频)定量算法迁到本仓库,在本机显卡上跑完全部病例。对每段视频,程序要圈出病灶、画出亮度随时间变化的曲线、挑出关键帧、算出灌注指标,最后由 build 把每例的 7 张关键帧和指标装进资料库的超声一路。资料库里原有医生截的关键结构图,加上这 7 张,一共 8 张图。
对应 `research-wiki/explanation/结构.md` 第七部分第 3 步。前提:任务书 01 已完成(提交 bceb01e)。
一份任务书分四个阶段,每个阶段做完单独提交,四个阶段都完成后一起交报告(负责人 2026-10-16:「任务书一份」)。
来源:
- 负责人 2026-10-16 定:CEUS 代码「直接一个个迁移」,不在本机重跑旧代码作对比基线(design 0002 决定 4)。
- 负责人 2026-10-16 定:「推注开始时间s」是视频第 0 秒时已经推注了多少秒;40 例没有医生标的造影剂到达时间,关键帧全部由程序从曲线上算或按推注时间取(design 0002 决定 5、6)。
- 负责人 2026-10-16 定:MedSAM2 用最快的方式装(从远程服务器拷代码和权重);紫圈的颜色阈值先照搬旧值,记为待定问题(`待定问题/08`);先把关键帧和指标放进资料库,「一个患者的资料库中有 8 帧」。
- 其余做法(四处已知问题怎么改、中间文件的布局、复用的键、对外接口)是监督者 2026-10-16 提出的,负责人确认任务书时一并认可。
## 先认识几个词
- **CEUS、造影画面**:往静脉推注造影剂后连续录下的超声视频。超声机录像时屏幕左右各一半:一半是造影画面(造影剂到了就变亮),另一半是普通灰阶超声画面。哪一半是造影画面,各例不一定相同。
- **绿框**:医生在造影画面上画的病灶框,烧录在视频里。
- **紫圈**:医生用紫色轮廓圈的一块正常胰腺(003、010、012 例圈的是肝,033 例没有),用作对照(`待定问题/08`)。
- **提示帧**:绿框位置或形状明显变化的那几帧。程序把这几帧的绿框交给分割模型,告诉它「病灶在这里」。
- **MedSAM2**:医学图像分割模型(GitHub `bowang-lab/MedSAM2`),给它几帧的提示,它能把病灶轮廓传播到整段视频的每一帧。要用显卡,是整个流程里最慢的一步。
- **ROI**:最终圈出的病灶区域。**暗区**:病灶里装囊液、不进造影剂、一直发黑的部分。
- **TIC**(时间-强度曲线):ROI 平均亮度随时间的变化,先变亮再变暗。用 Lognormal(对数正态)形状的光滑曲线去拟合它,后面的时刻和指标都在拟合出的光滑曲线上算。
- **关键帧**:B0(刚开始变亮之前)、D1(开始变亮)、D2(变亮最快)、D3(最亮)、D4(变暗最快)这 5 张从拟合曲线上取;F1、F2 是推注后第 20、60 秒。定义见 `结构.md` 第四部分「CEUS 中间文件」。
- **灌注指标**:从拟合曲线算出的 11 个数(峰值强度、达峰时间、洗入速率等),口径仿照商用造影定量软件 VueBOX。
- **`work/`**:中间文件目录,不进 git,可以删掉重算。
- **两套编号**:本任务书的「第一阶段」到「第四阶段」是你交付的四个阶段;「第 1 步」到「第 9 步」是一段视频在 CEUS 定量里依次经过的 9 步(`结构.md` 第四部分有表):1 解码与分屏,2 找绿框、选提示帧,3 MedSAM2 分割,4 整理轮廓(平滑、与绿框融合),5 找暗区,6 TIC 采样,7 拟合,8 关键帧,9 灌注指标。
- **在哪一半上分割**:沿用旧代码。绿框画在造影那一半;造影画面里病灶边界随造影剂进出时明时暗,所以旧代码把绿框挪到灰阶那一半,让 MedSAM2 在灰阶画面上分割,再把轮廓挪回造影那一半,在造影画面上采亮度。
- **监督者**:和负责人讨论、写任务书并验收的会话;执行者是拿着这份任务书写代码的你。
## 开工前先读
- `AGENTS.md`:全部,尤其是「不设默认值」「罕见情况怎么办」「错误怎么处理」「测试」。
- `research-wiki/explanation/结构.md`:第二部分(框架图)、第三部分(ceus、segment 的职责和「几条边界」)、第四部分(「CEUS 定量的产物」的 9 步和关键帧表)、第五部分(「配置」、文件格式、导入契约)、第八部分(任务书 01 已有的接口)。
- `research-wiki/design/0002-四段流水线与两个库.md`:决定 4、5、6。
- `research-wiki/待定问题/01`(绿框在最终轮廓里占多少)、`02`(参数何时冻结)、`07`(图上的信息栏)、`08`(紫圈阈值)。这一步都不处理,只要知道它们存在、不要顺手改。
- `research-wiki/任务书/01-装配两个库.md` 和 `src/cadx/` 现有代码。
- 旧代码(只读,路径相对 `reference/CADx-4-CAD/`):
- `app/domain/ceus_roi/` 除 `service.py` 外的 17 个模块,约 7300 行,都不依赖数据库或 HTTP。
- `app/providers/video_decoder.py`(视频解码)。
- 实际跑出 40 例结果的三个脚本:`debugs/t1_local_debug.py` 做第 1–6 步,用子进程调 `debugs/medsam2_local_track.py` 做分割;`debugs/tic_lognormal_replay.py` 读轨迹,做 TIC、拟合、关键帧和指标。**迁移时以这条路径的行为为准**,`service.py` 不搬。
- `config/ceus_roi_default.yaml`:108 个参数。
- `tests/unit/` 下 19 个算法测试文件(约 303 个测试,全用合成数据)。
**旧测试怎么接新配置**:配置去掉默认值以后,旧测试里靠默认值构造配置的地方,改成由一个夹具读入 `config/ceus.yaml`,需要不同取值的测试用 `model_copy(update=...)` 改那一项;断言和预期值不改。
**旧脚本里的编排逻辑放哪**:两个 debug 脚本里按顺序调用各模块的那部分逻辑,收进 ceus 对外的两段接口(第二阶段「ceus 对外交出什么」);`scripts/ceus_quant.py` 只管读配置、找病例、读写文件、分割缓存和逐例循环。
**病人资料不进任何文字**:报告、提交说明、测试、日志里只写结构和计数,不写病例号、视频号、诊断和报告原文。
## 先修任务书 01 的一处
任务书 01 的执行者报告:关键图格式不对(不是 PNG、16 位、带透明)时,`library` 抛 `ValueError`,整次构建停下。这是只落在一个病例上的问题,按 `AGENTS.md`「错误怎么处理」应该跳过这一例。
要做:
- library 提供 `check_key_image(path) -> str | None`:能原样重编码时返回 None,不能时返回原因。现在 `_write_clean_png` 里的那几条判断移到这里。
- build 在写资料库之前对每例调它,返回原因的,这一例跳过,记为 **C14**「关键图能原样重编码」,涉及值写原因。
- `_write_clean_png` 里的判断保留为断言性质的报错(走到这里说明 build 漏了检查)。
- 补测试:一例关键图带透明,这一例被跳过、记 C14、其余照常进库。
- `结构.md` 第八部分相应几行改掉。
和第一阶段一起提交,提交说明里写明。
## 第一阶段:底层和分割之前的算法
迁移以下模块到 `src/cadx/ceus/`(文件名加 `_` 前缀,对外只经 `__init__.py`):geometry、视频解码(旧 `video_decoder.py`)、配置(旧 `research_config.py`)、split_screen、green_prompt、prompt_frames、mapping。
### 怎么迁
- 逐个模块照旧代码搬,去掉产品层的东西(数据库、settings、日志框架、`app.*` 的引用),注释和 docstring 改成中文、只写现在做什么和为什么。
- 对应的旧测试一起搬,改 import,断言和预期值不改。旧测试要是因为下面「四处要改的地方」而不再成立,删掉或改写那一条,在报告里逐条列出。
- 视频解码:旧代码先把整段视频读成字节、写临时文件,再整段读进内存。改成直接按路径用 cv2 打开、逐帧读。行为(读出的帧和时间戳)不变。`test_video_decoder` 原来要一段真实样例视频,改成在测试里用 cv2 现造一段几十帧的小视频。
### 配置
- 旧 `research_config.py` 的 22 个 pydantic 配置类搬过来,**去掉全部默认值**(旧 schema 里约 134 处),每个字段必填;`strict=True`、`extra="forbid"`、`frozen=True`,规则同 `结构.md` 第五部分「配置」。
- 新建 `config/ceus.yaml`:数值逐个照抄旧 `ceus_roi_default.yaml`。每个参数的注释写明出处:旧 yaml 注释里引的出处(临床口径契约、旧 findings、旧 design)照抄并注明「出自旧项目」;没有出处的写「旧项目工程侧定的,无出处」。不改任何数值(`待定问题/02`)。
- `ceus.yaml` 分三段:`paths`(交付包位置、`runs/` 和 `work/` 位置)、`segment`(分割设置,第二阶段加)、`ceus`(算法参数)。字段定义各归其主:`ceus` 段归 ceus,`segment` 段归 segment,最外层和 `paths` 由 `scripts/ceus_quant.py` 定义,它读整份文件、把各段交给各自的模型校验。第一阶段先只有 `paths` 和 `ceus` 两段。
- 下面「四处要改的地方」用不到的旧字段(映射锚点(改成每段视频配准,见下文第 1 条)、`source_view`、只给 `service.py` 用的裁黑边设置、D1 取医生时刻的那个模式开关)直接删掉,不留着不用。
### 四处要改的地方
这四处在旧代码里是错的,或者在 40 例上会出问题,按下面改,各配手算预期值的测试。
1. **左右两半之间的坐标换算:每段视频单独配准。** 程序要把造影画面上的绿框挪到灰阶那一半的同一解剖位置,分割完再把轮廓挪回来采亮度。
- 为什么要配准:旧代码用一对写死的锚点(旧 yaml 第 45–49 行、旧 `mapping.py:91–135`)给所有视频同一个平移量。这对锚点是在一段样本上量的。执行者 2026-10-16 在 8 段视频(覆盖全部 5 种帧宽)上实测:两个视图左上角对齐之后,还剩 −10 到 +182 像素的水平错位,竖直方向 0 到 5 像素。原因是造影那一半的扇形左边有一段黑边,灰阶那一半的扇形贴着分屏线。同一帧宽的两段视频,错位也可能不同。所以平移量既不能写死,也不能只用视图左上角相减。
- 改成:第 1 步分屏之后,在两半的扇形区内对边缘图做互相关,估出 (dx, dy);再用模板匹配独立估一次。两者之差不超过 `ceus.yaml` 里的门槛,取互相关的值;超过就这一例第 1 步失败,原因写「两半配准不一致」。映射仍是「视图局部坐标 + 这个平移」,不做缩放和旋转。
- 门槛:先在 34 例上跑两种方法、看差值分布,再定门槛写进 `ceus.yaml`。注释写明依据,即差值分布的几个分位数。
- 测试:用合成分屏图,已知偏移至少 3 组(含负的水平偏移和非零的竖直偏移),断言估出的偏移等于设定值;再做一张两种方法会给出不同结果的图,断言这一例失败。
- 目视核对用子 Agent,不用人,见下文「配准的目视核对」。
2. **造影画面在哪一半。** 旧代码配置写死造影在左半(旧 yaml 第 15–16 行),013 例造影在右半,找不到绿框。改成:两半各自按旧代码的办法选提示帧(`collect_prompt_observations` + `select_prompt_frames`,参数不变),选出至少一帧就算「这一半有绿框」;恰好一半有,这一半就是造影画面;两半都找到或都找不到,这一例失败,记原因。测试:合成视频,绿框分别在左、在右、两边都有、两边都没有。
3. **D1 不用医生的时刻。** 旧关键帧代码有一个模式让 D1 直接取医生标注的造影剂到达时间(旧 `anchor_frames.py:203–206`)。这条路删掉,只保留从拟合曲线算 D1(design 0002 决定 6)。
4. **推注时间的换算。** X 是超声报告表的「推注开始时间s」,含义是视频第 0 秒时已经推注了 X 秒。F1 取视频第 20 − X 秒,F2 取第 60 − X 秒,取帧的容差窗口照旧 `anchor_frames` 段对 F1、F2 的设置;落在视频之外(小于 0 或超过视频末尾),这一帧记为未定位,写明原因,不拿别的帧补(`结构.md` 第四部分)。旧代码按「推注发生在视频第 X 秒」处理(旧 `anchor_frames.py:204` 的符号),要改。测试手算:X = 7 时 F1 在第 13 秒、F2 在第 53 秒;X = 25 时 F1 在第 −5 秒,未定位。
另有两处旧 debug 路径和 `service.py` 不一样,**按 debug 路径**,因为 40 例的结果是它跑出来的:不裁画面黑边;找分屏线只用均匀抽的 25 帧。
### 收尾点
上面的模块和测试都搬完,`make check` 通过,提交。
## 第二阶段:分割和后半段算法
### MedSAM2
- 从远程服务器拷代码和权重(ssh/rsync 一律走 `wangwenhan@202.38.247.58`,见 `AGENTS.md`「本机环境的坑」):
- 代码:远程 `/home/wangwenhan/MedSAM2/`,排除 `checkpoints/` 和 `data/`(约 25MB),连同 `.git` 一起拷到本机 `reference/MedSAM2/`,提交号从这个 `.git` 读出,不写死。它是 GitHub `bowang-lab/MedSAM2` 的 332f30d 提交,远程只改了 `download.sh`,不影响代码。
- 权重:只拷 `checkpoints/MedSAM2_latest.pt`(约 156MB)到本机 `models/MedSAM2_latest.pt`。
- 用 `SAM2_BUILD_CUDA=0 uv add --editable reference/MedSAM2` 装进项目环境(不编译 CUDA 扩展;旧项目也没用它)。装完确认 torch 能看到本机显卡。`guides/开发环境.md` 写明这几步,下一个人在新机器上照着就能装好。
- segment 模块(`src/cadx/segment/`):从旧 `medsam2_local_track.py` 改写,**在同一进程里调用**,不再开子进程和 conda 环境。对外一个函数:输入逐帧图像(只是要分割的那一半画面)、若干提示帧的病灶掩码、分割设置,输出每一帧的病灶掩码。它不认识分屏和绿框(`结构.md` 第三部分)。MedSAM2 要求帧以 jpg 文件放在一个目录里,临时目录由 segment 自己建、自己删。
- 分割设置(权重路径、模型配置路径、设备、旧脚本里的两个 offload 开关)写进 `ceus.yaml` 的 `segment` 段,字段定义在 segment 里,全部必填。权重路径不写死在代码里。
- 导入契约:`cadx` 里只有 segment 能引用 `torch`、`sam2`(改 `pyproject.toml` 里现有那条)。
### 其余模块
迁移 polygons、dark_region、reference_region、tic_fitting、tic、anchor_frames、perfusion_metrics、trajectory、tic_curves、tic_plot、overlay,旧测试一起搬,做法同第一阶段。
- 绿框在最终轮廓里的融合权重(旧 `roi_shape` 段)照搬,写在 `ceus.yaml` 里(`待定问题/01`)。
- 紫圈的颜色阈值(旧 `pan_source` 段)照搬(`待定问题/08`)。
- 关键帧各时刻的容差窗口,逐个核对出处,写进 `ceus.yaml` 的注释(`结构.md` 第四部分)。
### ceus 对外交出什么
ceus 不能引用 segment(`结构.md` 第三部分依赖表),所以分两段对外:
1. 第一段:输入视频路径和参数,做第 1–2 步,交出分屏结果、哪一半是造影画面、要交给分割的那一半的逐帧图像,以及提示帧的掩码。
2. 第二段:输入第一段的结果、分割得到的逐帧掩码、推注时间 X 和参数,做第 4–9 步,交出轨迹、TIC 曲线、拟合、7 个关键帧的定位结果、灌注指标。
两段中间由 `scripts/ceus_quant.py` 调 segment。具体类型由执行者定,写进 `结构.md` 第八部分。
### 收尾点
全部模块和测试搬完,`make check` 通过。另写一条标 `gpu` 的测试:在一段合成小视频上真的调一次 MedSAM2,输出掩码的帧数和尺寸对。跑通后提交。
## 第三阶段:`scripts/ceus_quant.py`,跑全部病例
### 跑哪些病例
用 delivery 读交付包,跑**检查全部通过的病例**(目前 34 例)。C14(关键图)在 build 时才查,被它跳过的病例 CEUS 照跑,build 时不用就是了。医生改完数据重新交付后,再跑一次。
delivery 的 `CaseRecord` 目前没有视频路径和推注时间,加上这两项(C04、C08 已经检查过它们)。
### 输出
CEUS 的结果会进资料库,最终可能写进论文,按 `AGENTS.md` 硬性约束 5 要能追到一次真跑的原始记录,所以每次运行放在 `runs/ceus/` 下、只追加不删;只有能重算的分割缓存放 `work/`。
```
runs/
ceus/<运行号>/
snapshot/ 快照:git 状态、ceus.yaml 原文、权重文件的 SHA-256、MedSAM2 的提交号
failed.csv 失败的病例:视频号、失败在第几步、原因
<视频号>/
trajectory.npz 逐帧轮廓(格式同旧 trajectory.py)
tic_curves.npz 曲线(格式同旧 tic_curves.py)
summary.json 拟合结果、灌注指标、紫圈检出率、7 个关键帧的定位(帧号、视频时间、状态、未定位的原因)
tic.png 曲线图,标出 D1–D4
overlay.mp4 带轮廓的回放视频,供人目视检查
keyframes/<槽位>.png 定位到的关键帧,B0、D1……F2,未定位的槽位没有文件
work/
segment_cache/<键>/masks.npz
```
- 运行号的格式同构建号(用 runs 的 `make_build_id`)。运行目录先写成 `.partial`,跑完再改名,已存在就报错,规则同任务书 01。
- 关键帧图是该帧的**整幅原始画面**(左右两半都在),不裁剪。给超声专家时裁不裁、怎么遮信息栏,留给会诊任务书(`待定问题/07`)。
- 只有分割结果跨运行复用(`结构.md` 第四部分)。键是下面几样拼起来的 SHA-256:视频文件的 SHA-256、第 1–3 步用到的全部算法参数(规范化成 JSON;不含设备和两个 offload 开关,它们不改变结果)、权重文件的 SHA-256、MedSAM2 的提交号(从 `reference/MedSAM2/.git` 读)。键相同就直接读缓存,不调显卡。缓存目录也是先写 `.partial` 再改名。
- 某一例在第 1–6 步失败(包括第一阶段第 2 条的「两半都有或都没有绿框」、选不出提示帧),这一例记进 `failed.csv`、接着跑下一例,和交付资料的问题同一个规矩(`AGENTS.md`「错误怎么处理」)。拟合没过质量门不算失败:D1–D4 记为未定位、灌注指标写 null,原因写进 `summary.json`,F1、F2 照常按推注时间取。个别关键帧槽位未定位也不算失败。
- 终端只打印计数:总例数、成功数、按步分的失败数、关键帧按槽位分的定位数、耗时。不打印视频号。
`scripts/ceus_quant.py` 只接收 `--config config/ceus.yaml`。全部病例要跑一阵子,放进 tmux,日志写到文件。
### 配准的目视核对(全量运行之前做)
配准错了不会报错,只会让 TIC 采在病灶旁边,所以全量运行之前,34 例逐例做一次目视核对。核对由子 Agent 来做,负责人不看(负责人 2026-10-16 同意)。子 Agent 判得准不准事先不知道,所以必须混入阴性对照:对照没判出来,这次核对就作废。
1. **出图**:新写 `scripts/ceus_align_review.py --config config/ceus.yaml`,只做第 1 步,不需要显卡。
- 每例出两张图,都只截扇形区:一张棋盘拼图(两半按配准结果交替拼接);一张边缘叠加图(灰阶那一半的边缘画红色,造影那一半配准后的边缘画绿色,重合处呈黄色)。
- 另挑约 10 例,把配准结果故意错开 15–30 像素(方向随机),出同样的两张图,当阴性对照。
- 全部图用随机编号命名、打乱顺序,放进 `runs/ceus_align/<运行号>/images/`。编号和「哪一例、是不是对照、用的 (dx, dy)、视频 SHA-256」的对照表写进同目录的 `key.csv`。这份表不给子 Agent 看。
2. **判读**:派两个互不知情的全新子 Agent,各自看完 `images/` 全部图。每张只判三档:对齐、没对齐、看不清,外加一句依据(例如「血管在棋盘交界处错开」)。
- prompt 里不提有对照,也不提对照的比例。
- 子 Agent 只写编号、判定和依据,不抄图上的字(AGENTS.md 硬性约束 3)。
- 两份判读写成 `verdicts_a.csv`、`verdicts_b.csv`。
3. **计分**:用脚本对照 `key.csv`。
- 任一个子 Agent 把某张阴性对照判成「对齐」或「看不清」,这次核对作废,报告里写明。不放宽标准、不换说法重问。
- 对照都过了:真实病例只有两个子 Agent 都判「对齐」才算通过,其余记为不通过。结果写进 `review.csv`(视频 SHA-256、(dx, dy)、通过与否、两份判定)。
4. **接进全量运行**:`ceus.yaml` 的 `paths` 段加必填字段 `align_review`,指向这次核对的 `review.csv`。
- `ceus_quant` 第 1 步算出配准后,按视频 SHA-256 查这份表:不通过的病例,第 1 步失败,原因写「配准目视核对未通过」。
- 表里没有这段视频,或者 (dx, dy) 和表里记的不一样,直接报错停下:说明核对的不是这次跑的东西。
- 这份 `review.csv` 拷进运行快照。
5. 监督者验收时会自己抽几张图核对。
### 收尾点
在本机跑完全部病例,提交代码(`runs/`、`work/` 不进 git)。
## 第四阶段:build 装入 CEUS 结果
- `build.yaml` 加一个必填字段 `ceus_run`:某次 CEUS 运行目录的路径,或者 null(不装 CEUS 结果,就是现在的情况)。
- 不为 null 时,build 按视频号找到每例的 CEUS 结果。这一例在 `failed.csv` 里或者找不到结果目录,这一例跳过,记为 **C15**「CEUS 定量结果齐全」,涉及值写失败阶段和原因。CEUS 失败的病例要不要改成「超声不可评价、仍参与会诊」,等 `待定问题/03` 定,这一步按跳过处理。
- 资料库超声一路加:
- `ultrasound/keyframes/<槽位>.png`:定位到的关键帧,经 library 去掉附加数据重新编码(同 `key.png`)。加上原有的 `key.png`,每例最多 8 张图。
- `ultrasound/tic.json`:从 `summary.json` 里只挑这几样,此外什么都不写:旧 `tic.py` 四条曲线(病灶整体、病灶去暗区、紫圈、背景圆)各自的拟合是否成功和拟合优度;灌注指标(同旧 replay `summary.json` 的 `perfusion_metrics` 段);紫圈曲线是否可用;7 个槽位各自定位到没有,定位到的写视频时间(秒),未定位的写原因。视频号、文件路径、配置、帧号都不写。
- 资料库 `manifest.json` 的 CEUS 来源字段写 CEUS 运行号。
- 泄露扫描的禁词表加上视频文件名;扫描范围加上 `tic.json`。
- 用 `ceus_run` 指向第三阶段的那次运行,重新构建一次真实资料库,跑 `scripts/scan_library.py`。
### 收尾点
测试通过、真实构建和扫描跑完,提交。
## 测试
预期值照本任务书文字和旧测试写,不从新实现里推。
- 第一阶段的四处修改:各条手算测试,见上文。
- 旧测试:19 个文件中的 18 个(`test_tic_lognormal_replay` 不搬,它测的是旧 replay 脚本读标注工作簿的逻辑,第 3、4 条修改后不再适用)。conftest 里设产品层环境变量的那个夹具不搬。
- segment:一条 `gpu` 测试(第二阶段收尾点)。
- 分割缓存:同样的输入第二次运行不调 segment(用 pytest 的 monkeypatch 把脚本里用到的 segment 函数换成计数的假函数,不为测试加参数);改一个第 1–3 步的参数,键就变。
- 配准核对接入:`review.csv` 里判不通过的病例进 `failed.csv`;表里没有这段视频或 (dx, dy) 对不上时报错。
- `ceus_quant`:合成的两例小视频,一例正常、一例两半都没有绿框,后者进 `failed.csv`,前者有完整输出。
- build:C15 跳过;`tic.json` 只有上面列的几样;keyframes 的附加数据被去掉。
## 规模
| 模块 | 预期行数 |
| --- | --- |
| ceus | 6000–7500(照搬旧算法,去掉产品层) |
| segment | 150–300 |
| `scripts/ceus_quant.py` | ≤ 300 |
| `scripts/ceus_align_review.py`(出图和计分) | ≤ 250 |
| delivery、library、build 的增量 | 各 ≤ 150 |
超过区间上限的两倍就停下检查。完成后更新:`结构.md` 第六部分实际行数、第八部分 ceus 和 segment 的对外接口;`guides/开发环境.md`(MedSAM2 怎么装);新写 `guides/CEUS定量.md`(怎么跑、产物在哪、怎么看 `failed.csv` 和 `overlay.mp4`,过一轮「硕士生阅读」);`WORK_LOG.md`。
## 审阅
- 这一步大部分是「其余部分」那一档(`AGENTS.md`「两档标准」),写完派一个全新上下文的子 Agent 审阅:只给它本任务书、`结构.md` 第三到第五部分和 diff,让它重点看「四处要改的地方」是否改对、迁移有没有漏掉旧代码里的分支、关键帧和指标有没有算错。
- 第四阶段改的是资料库(决定专家能看见什么),属于账本级,另派一个全新上下文的子 Agent 单独审这部分,找:CEUS 结果里有没有带进视频号、文件名、医生的判断;CEUS 失败的病例有没有进库。
- Codex 可用时再加一轮对抗审查(`/codex:rescue --fresh --wait`)。
- 意见的处理同任务书 01。子 Agent 的 prompt 里写明本机 `grep`、`find` 用 `/usr/bin/` 下的版本。
## 报告
四个阶段都完成后,在回复里交一份报告。已验证的事实、推断、未验证的部分分开写。
1. 四个提交号;各模块行数。
2. `make check` 的完整输出;`gpu` 测试的输出。
3. 旧测试:搬了多少条,删改了哪些、为什么。
4. 真实运行:运行号、跑了几例、成功几例、按步分的失败数、关键帧按槽位分的定位数、总耗时和显存峰值。
5. **配准**:两种方法差值的分布和定下的门槛;目视核对的运行号;阴性对照各判对几张;真实病例通过、不通过、看不清各几例;两个子 Agent 判定不一致几例。另给每种帧宽各 1 例的棋盘图路径,供监督者抽查。
6. **紫圈检出率**:每例一个数(有多少比例的帧认出了紫圈),只报分布(最小、四分位、最大),以及低于 50% 的有几例。给 `待定问题/08` 用。
7. **请负责人目视检查的样例**:挑 3 例 `overlay.mp4` 和 `tic.png` 的路径(成功、关键帧缺槽位、紫圈检出率最低的各一例),只给路径不描述内容。
8. 重新构建的资料库:构建号、进库数、C15 跳过数、扫描结果。
9. 审阅意见和处理。
10. 和本任务书、`结构.md`、`AGENTS.md` 不一致的地方,以及为什么。
10. 规则不清楚的地方:哪里犹豫、哪里靠猜。没有就写「没有」。