diff --git a/research-wiki/任务书/02-CEUS定量.md b/research-wiki/任务书/02-CEUS定量.md index 5e84868..594332a 100644 --- a/research-wiki/任务书/02-CEUS定量.md +++ b/research-wiki/任务书/02-CEUS定量.md @@ -78,13 +78,18 @@ - 旧 `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` 两段。 -- 下面「四处要改的地方」用不到的旧字段(映射锚点、`source_view`、只给 `service.py` 用的裁黑边设置、D1 取医生时刻的那个模式开关)直接删掉,不留着不用。 +- 下面「四处要改的地方」用不到的旧字段(映射锚点(改成每段视频配准,见下文第 1 条)、`source_view`、只给 `service.py` 用的裁黑边设置、D1 取医生时刻的那个模式开关)直接删掉,不留着不用。 ### 四处要改的地方 这四处在旧代码里是错的,或者在 40 例上会出问题,按下面改,各配手算预期值的测试。 -1. **左右两半之间的坐标换算。** 程序要把造影画面上的绿框挪到另一半画面的同一位置。两半大小相同、只差一个水平平移,旧代码却把换算用的锚点按某一种视频宽度(1856 像素)写死成像素坐标(旧 yaml 第 45–49 行、旧 `mapping.py:91`),40 例有 5 种宽度,其余宽度会挪偏一两百像素。改成:平移量 = 目标那一半视图的左上角 − 造影那一半视图的左上角,直接用第 1 步切分出来的两个视图矩形算。测试:至少 3 种宽度,手算平移量。 +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 秒,未定位。 @@ -164,6 +169,27 @@ work/ `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)。 @@ -191,6 +217,7 @@ work/ - 旧测试: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 的附加数据被去掉。 @@ -201,6 +228,7 @@ work/ | 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`。 @@ -220,9 +248,10 @@ work/ 2. `make check` 的完整输出;`gpu` 测试的输出。 3. 旧测试:搬了多少条,删改了哪些、为什么。 4. 真实运行:运行号、跑了几例、成功几例、按步分的失败数、关键帧按槽位分的定位数、总耗时和显存峰值。 -5. **紫圈检出率**:每例一个数(有多少比例的帧认出了紫圈),只报分布(最小、四分位、最大),以及低于 50% 的有几例。给 `待定问题/08` 用。 -6. **请负责人目视检查的样例**:挑 3 例 `overlay.mp4` 和 `tic.png` 的路径(成功、关键帧缺槽位、紫圈检出率最低的各一例),只给路径不描述内容。 -7. 重新构建的资料库:构建号、进库数、C15 跳过数、扫描结果。 -8. 审阅意见和处理。 -9. 和本任务书、`结构.md`、`AGENTS.md` 不一致的地方,以及为什么。 +5. **配准**:两种方法差值的分布和定下的门槛;目视核对的运行号;阴性对照各判对几张;真实病例通过、不通过、看不清各几例;两个子 Agent 判定不一致几例。另给每种帧宽各 1 例的棋盘图路径,供监督者抽查。 +6. **紫圈检出率**:每例一个数(有多少比例的帧认出了紫圈),只报分布(最小、四分位、最大),以及低于 50% 的有几例。给 `待定问题/08` 用。 +7. **请负责人目视检查的样例**:挑 3 例 `overlay.mp4` 和 `tic.png` 的路径(成功、关键帧缺槽位、紫圈检出率最低的各一例),只给路径不描述内容。 +8. 重新构建的资料库:构建号、进库数、C15 跳过数、扫描结果。 +9. 审阅意见和处理。 +10. 和本任务书、`结构.md`、`AGENTS.md` 不一致的地方,以及为什么。 10. 规则不清楚的地方:哪里犹豫、哪里靠猜。没有就写「没有」。