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:
2026-10-16 03:46:56 -04:00
parent de1d6b2d51
commit 9a916c65a5
29 changed files with 3634 additions and 45 deletions
+155
View File
@@ -0,0 +1,155 @@
# CEUS 定量的配置,给 scripts/ceus_quant.py 用。
#
# 分三段:paths(位置)、segment(分割设置,任务书 02 第二阶段加)、ceus(算法参数)。
# ceus 段的字段定义和校验在 src/cadx/ceus/_config.py;paths 段和最外层由 scripts/ceus_quant.py 定义。
# 每个字段必填,可以没有的值写 null。
#
# ceus 段的数值逐个照抄旧项目 config/ceus_roi_default.yaml,没有改动(待定问题/02:参数何时冻结)。
# 注释里的出处:
# - 「出自旧项目」:旧 yaml 注释原样引用的来源。其中 findings 指旧项目
# research-wiki/findings/2026-06-16-roi-tic-config-parameter-reference.md(伙伴参考组件
# references/ROI/ 那套参数的原始口径),§2A 指「删键即静默退化、yaml 值与代码默认不同」的那批;
# design/00NN 指旧项目 research-wiki/design/;《临床口径契约》在 Linear,抄本可能过期。
# - 「旧项目工程侧定的,无出处」:旧 yaml 没给来源的值。
paths:
delivery_dir: data/交付包_20260908_40例 # 交付包位置,相对仓库根目录
runs_root: runs/ceus # 每次运行一个子目录,只追加不删
work_root: work # 可删掉重算的中间文件(分割缓存)
ceus:
config_version: "4" # 参数口径版本号,由人改。出自旧项目:Lognormal-v2 参数口径(design/0044)
views: # 出自旧项目:findings §3.1 视图切分
layout: horizontal_split # 屏幕左右各一半。出自旧项目 findings §3.1
split_sample_frames: 25 # 找分屏线只用均匀抽的 25 帧。旧 debug 脚本写死的数,旧项目工程侧定的,无出处
split_search_band: [0.35, 0.65] # 只在宽度的这一段里找分屏线。旧 detect_split_x 的参数默认值,旧项目工程侧定的,无出处
prompt_source: # 出自旧项目:findings §3.2 绿框检测
type: color_polygon # 出自旧项目 findings §3.2
hsv_lower: [35, 60, 60] # 出自旧项目 findings §3.2
hsv_upper: [90, 255, 255] # 出自旧项目 findings §3.2
ignore_regions: [[0.0, 0.0, 1.0, 0.0834]] # 抹掉顶部约 8.34% 的文字带。出自旧项目 findings §3.2
min_area: 10000 # 外接框面积 w×h(findings §2D)。出自旧项目 findings §3.2
pan_source: # 紫圈检测(待定问题/08)。出自旧项目 design/0030 决策四;旧 yaml 注明全部是占位、待真实素材标定
type: color_polygon # 旧项目工程侧定的,无出处
hsv_lower: [125, 60, 60] # 占位,待标定。出自旧项目 design/0030 决策四
hsv_upper: [165, 255, 255] # 占位,待标定。出自旧项目 design/0030 决策四
ignore_regions: [[0.0, 0.0, 1.0, 0.0834]] # 占位,同绿框那条顶部文字带。出自旧项目 design/0030 决策四
min_area: 2000 # 占位,待标定。出自旧项目 design/0030 决策四
min_detected_frame_fraction: 0.9 # 占位,待标定。出自旧项目 design/0030 决策四
keyframes: # 提示帧选择(不是第 8 步的关键帧)。出自旧项目:findings §3.3
change_metric: mask_iou # 出自旧项目 findings §3.3
iou_threshold: 0.92 # 出自旧项目 findings §3.3
min_gap_frames: 1 # 出自旧项目 findings §3.3
smoothing: # 出自旧项目:findings §3.6 轮廓分辨率与时间平滑
contour_points: 64 # 出自旧项目 findings §3.6
temporal_window: 15 # 须奇数。出自旧项目 findings §3.6
roi_shape: # 最终轮廓与绿框的融合(待定问题/01)。出自旧项目:findings §3.7
source: prompt_prior # findings §2A:非代码默认 medsam2。出自旧项目 findings §3.7
keyframe_shape_strength: 1.0 # 出自旧项目 findings §3.7
propagation_shape_strength: 0.75 # 出自旧项目 findings §3.7
min_area_ratio_to_prompt: 0.70 # 须 < max。出自旧项目 findings §3.7
max_area_ratio_to_prompt: 1.25 # 出自旧项目 findings §3.7
interior_dark_region: # 暗区识别。出自旧项目:findings §3.8
enabled: true # findings §2A:非代码默认 false。出自旧项目 findings §3.8
analysis_view: source # findings §2A:非代码默认 target。出自旧项目 findings §3.8
exclude_prompt_color: true # findings §2A:非代码默认 false。出自旧项目 findings §3.8
threshold_method: auto_ensemble # findings §2A:非代码默认 otsu。出自旧项目 findings §3.8
candidate_methods: [otsu, adaptive, percentile, kmeans, clahe_otsu] # 出自旧项目 findings §3.8
threshold_offset: 0 # 出自旧项目 findings §3.8
min_area: 300 # findings §2A:非代码默认 64。出自旧项目 findings §3.8
min_area_ratio: 0.015 # findings §2A:非代码默认 0.0。出自旧项目 findings §3.8
preserve_holes: true # 出自旧项目 findings §3.8
fill_holes: false # 出自旧项目 findings §3.8
morphology_kernel: 5 # 出自旧项目 findings §3.8
fragment_penalty_weight: 0.08 # 出自旧项目 findings §3.8
temporal_consistency_weight: 0.35 # 出自旧项目 findings §3.8
contour_points: 64 # 出自旧项目 findings §3.8
# 以下是各方法的子参数:参考 yaml 没列,取参考代码的默认值。出自旧项目 findings §2C、§3.8
percentile: 45.0 # 出自旧项目 findings §2C
threshold: 96 # absolute 法的固定灰度阈值。出自旧项目 findings §2C
adaptive_block_size: 31 # 须奇数 ≥3。出自旧项目 findings §2C
adaptive_c: 5.0 # 出自旧项目 findings §2C
kmeans_max_iter: 30 # 出自旧项目 findings §2C
kmeans_attempts: 3 # 出自旧项目 findings §2C
clahe_clip_limit: 2.0 # 出自旧项目 findings §2C
clahe_tile_grid_size: 8 # 出自旧项目 findings §2C
constraints: # 出自旧项目:findings §3.9 提示帧位置约束
keyframe_position:
enabled: true # findings §2A:非代码默认 false。出自旧项目 findings §3.9
mode: center_interpolation # 出自旧项目 findings §3.9
strength: 1.0 # 截到 [0, 1]。出自旧项目 findings §3.9
tic_analysis: # 出自旧项目:findings §3.11 TIC 曲线(参考 yaml 整块不存在,取参考代码默认值)
brightness_metric: grayscale_mean # 出自旧项目 findings §3.11
exclude_prompt_color: true # 出自旧项目 findings §3.11
prompt_exclude_dilate: 3 # 出自旧项目 findings §3.11
baseline: # 增强前基线。出自旧项目 design/0030 决策二
preferred_duration_s: 2.0 # 出自旧项目:Linear《临床口径契约》
minimum_duration_s: 1.0 # 出自旧项目:Linear《临床口径契约》
stability_sd_multiplier: 3.0 # 临床口径里没有这个判据,旧项目工程侧占位,无出处
reference_region: # 病灶外背景圆。出自旧项目 findings §3.11
enabled: true # 出自旧项目 findings §3.11
mode: auto_near_roi # 出自旧项目 findings §3.11
radius_px: 24 # 出自旧项目 findings §3.11
min_distance_px: 12 # 出自旧项目 findings §3.11
nonblack_threshold: 12 # 出自旧项目 findings §3.11
min_valid_fraction: 0.65 # 出自旧项目 findings §3.11
center_xy: null # manual 模式必填。出自旧项目 findings §3.11
fitting: # Lognormal 拟合。出自旧项目 design/0044:工程验收占位,代表性开发集标定后一次性锁定
algorithm_version: lognormal-v2 # 出自旧项目 design/0044
detect_duplicate_frames: true # 出自旧项目 design/0044
candidate_window_s: 1.5 # 二阶 Savitzky-Golay 只做候选检测,窗口按秒。出自旧项目 design/0044
candidate_long_window_s: 5.0 # 宏观团注候选,抑制逐帧掩膜与压缩波动。出自旧项目 design/0044
candidate_polyorder: 2 # 出自旧项目 design/0044
max_candidate_gap_s: 0.5 # 出自旧项目 design/0044
duplicate_mean_absdiff_threshold: 0.25 # 8 位灰度的平均绝对差。出自旧项目 design/0044
minimum_valid_samples: 20 # 出自旧项目 design/0044
minimum_peak_distance_s: 1.0 # 出自旧项目 design/0044
minimum_relative_peak_height: 0.10 # 出自旧项目 design/0044
multi_peak_minimum_separation_s: 5.0 # 出自旧项目 design/0044
multi_peak_minimum_relative_height: 0.50 # 出自旧项目 design/0044
multi_peak_maximum_valley_fraction: 0.25 # 出自旧项目 design/0044
sigma_min: 0.08 # 出自旧项目 design/0044
sigma_max: 2.5 # 出自旧项目 design/0044
robust_loss: soft_l1 # 出自旧项目 design/0044
max_nfev: 4000 # 出自旧项目 design/0044
minimum_r2: 0.35 # 出自旧项目 design/0044
maximum_nrmse: 0.45 # 出自旧项目 design/0044
maximum_residual_noise_ratio: 2.0 # 残差不高于基线噪声同量级时允许低 R²。出自旧项目 design/0044
boundary_margin_s: 0.25 # 出自旧项目 design/0044
estimated_baseline_percentile: 15.0 # 无稳定开头时的有界基线先验。出自旧项目 design/0044
estimated_baseline_lookback_s: 10.0 # 允许 t0 位于视频起点前,但截图不外推。出自旧项目 design/0044
maximum_perf_area_step_fraction: 0.15 # 相邻帧去暗区面积比例最大变化。出自旧项目 design/0044
parameter_boundary_fraction: 0.001 # 形状参数贴边判据占允许区间的比例。出自旧项目 design/0044
anchor_frames: # 7 张关键帧定位。出自旧项目 design/0040 决策二
# 下面这批数旧项目注明镜像自 Linear《临床口径契约》(2026-08-25 版,同步日期 2026-09-01),
# 动任何一个之前先去那边核对。各容差窗口的出处在第二阶段迁移关键帧模块时逐个再核对。
arrival: # 到达判据,门槛取两个候选的高者。出自旧项目:《临床口径契约》5.2
baseline_sd_multiplier: 3.0 # 候选一:基线抖动 ×3.0。出自旧项目:《临床口径契约》5.2
relative_peak_threshold: 0.10 # 候选二:峰高 ×0.10。出自旧项目:《临床口径契约》5.2
persistence_s: 0.5 # 越线后连续保持 0.5 秒。出自旧项目:《临床口径契约》(08-23 确认)
# 主峰突出度门槛:基线抖动 ×3。与到达判据那个 3.0 是两个独立的数,改一个不连带改另一个。
# 出自旧项目:《临床口径契约》第六节
peak_prominence_baseline_sd_multiplier: 3.0
baseline_frame_backoff_s: 0.3 # B0 锚点 = 到达时刻再往前 0.3 秒。出自旧项目:《临床口径契约》5.11
d1_window_after_s: 0.5 # D1 窗 [锚点, 锚点+0.5]。出自旧项目:《临床口径契约》第 12 题
d2_window_s: 0.3 # D2 窗 ±0.3 秒。出自旧项目:《临床口径契约》第 12 题
d3_window_s: 0.5 # D3 窗 ±0.5 秒。出自旧项目:《临床口径契约》第 12 题
# D4 窗 ±0.5 秒:契约空着、第 12 题委托工程侧。旧项目工程侧定,理由是 D4 与 D2/D3 同属曲线
# 形状锚点,洗出段比洗入段平缓、导数极值定位误差更大,取 D2/D3 中较宽的一档。无外部出处
d4_window_s: 0.5
b0_window_s: 0.5 # B0 窗 ±0.5 秒:契约空着,旧项目工程侧取与 D4 同档,无外部出处
late_phase:
preferred_time_s: 60.0 # F2 锚点:推注后 60 秒。出自旧项目:《临床口径契约》
tolerance_s: 2.0 # F2 容差 ±2 秒。出自旧项目:《临床口径契约》第 12 题
# F1 锚点:推注后 20 秒。出自旧项目:《临床口径契约》(08-16 口头定)。
# F1 没有容差键:旧项目工程侧暂定「无上限,取最近的一帧」,无出处
f1_anchor_s: 20.0