数据加载器分层设计说明.md 11 KB

数据加载器分层设计说明

目标

当前阶段只做“真实数据的统一索引层设计”,暂时不接训练器。

目标是:

  1. 让不同目录结构的数据集都能转成统一样本记录格式。
  2. 先解决“怎么描述样本”,再解决“怎么训练”。
  3. 避免在训练器里直接写一堆 if dataset_name == ... 的路径解析逻辑。

当前已落地的分层

当前新增了 lib/data,结构如下:

  1. records.py
    • 定义统一样本结构 SegSampleRecord
  2. utils.py
    • 提供图像文件识别和文件名处理工具
  3. indexers.py
    • 提供三类基础索引器
  4. builder.py
    • 负责按数据集名称分发到具体解析逻辑
  5. splits.py
    • 负责读取官方划分文件
  6. datasets.py
    • 负责真正读取图像与掩膜
  7. loaders.py
    • 负责构建 DatasetDataLoader

当前统一样本格式

统一样本记录包含:

  1. dataset_name
  2. image_path
  3. mask_path
  4. split
  5. sample_id
  6. class_name
  7. meta

这意味着后面不管数据来自乳腺、甲状腺还是其他超声任务,训练器看到的都将是统一格式。

当前支持的三种目录结构

1. 标准图像-掩膜成对目录

适用:

  1. BUS-UCLM
  2. TG3K
  3. OTU_2d

规则:

  1. 图像目录和掩膜目录分开
  2. 文件名完全一致

2. 已有训练测试划分目录

适用:

  1. TN3K

规则:

  1. trainval-image
  2. trainval-mask
  3. test-image
  4. test-mask

3. 文件名匹配目录

适用:

  1. BUSI

规则:

  1. 原图文件与掩膜文件放在同一类目录下
  2. 通过 _mask_mask_1 这类后缀匹配
  3. 当前第一版默认取第一个掩膜,并在 meta.mask_count 中保留掩膜数量

当前官方划分支持

当前优先支持已有官方划分的数据集,并且已经实际验证过 Dataset + DataLoader 可正常读取:

1. OTU_2d

支持:

  1. train.txt
  2. val.txt

说明:

  1. 文件中保存的是样本编号
  2. 当前会按编号过滤统一索引结果
  3. 已验证:
    • train.txt -> 1000
    • val.txt -> 469
  4. 图像与掩膜尺寸不统一时,当前批加载会返回列表,不会强行堆叠报错

2. TN3K

支持:

  1. tn3k-trainval.json
  2. tn3k-trainval-fold3.json
  3. tn3k-trainval-fold4.json

说明:

  1. trainval 图像先进入统一索引
  2. 再通过官方 json 中的 train / val 索引过滤
  3. test-image / test-mask 直接作为 test
  4. 已验证:
    • train -> 2303
    • val -> 576
  5. 由于原始图像尺寸不一致,当前批加载默认返回 image listmask list

3. TG3K

支持:

  1. tg3k-trainval.json

说明:

  1. 当前优先读取官方 train / val
  2. test 为空时,不强行虚构测试集
  3. 已验证:
    • train -> 3226
    • val -> 359
  4. 当前前几个样本尺寸一致时可以直接堆叠;若后续批次尺寸不同,也会自动退回列表模式

当前已支持的数据集

  1. BUS-UCLM
  2. TG3K
  3. TN3K
  4. OTU_2d
  5. BUSI
  6. BUS-BRA
  7. BUS_UC
  8. CCAUI
  9. DDTI

当前三类新增支持的说明

1. BUS_UC

采用“分层成对目录”解析:

  1. All/imagesAll/masks
  2. Benign/imagesBenign/masks
  3. Malignant/imagesMalignant/masks

其中:

  1. class_name 会记录 allbenignmalignant
  2. All 当前保留为单独一组记录,不在索引层里自动去重

2. CCAUI

采用标准成对目录解析:

  1. US images
  2. Expert mask images

目录名有空格,但对当前索引层没有影响。

3. DDTI

当前采用“图像 + XML 标注路径”索引方式:

  1. 图像来自根目录下的 *.jpg
  2. 标注来自根目录下的 *.xml
  3. 通过图像名中的前缀编号与 xml 文件名匹配

当前这一层只完成:

  1. 图像路径索引
  2. xml 标注路径关联

更新说明:

  1. 已新增 lib/data/ddti.py
  2. 已支持从 xml 中解析自由手绘轮廓
  3. 已支持按 样本编号_图像序号.jpg -> xml 中 image=图像序号 的规则生成二值掩膜

也就是说,DDTI 现在已经具备“可从标注动态生成掩膜”的能力,并且已经并入通用读取型 Dataset

补充说明:

  1. DDTI 当前没有发现现成的官方 train.txt / val.txt
  2. 现阶段已完成:
    • xml -> polygon 解析
    • polygon -> 二值 mask 动态生成
    • DataLoader 批读取验证
  3. 已验证总样本数为 480

当前项目级划分支持

对于当前 data 目录中没有现成官方 train.txt / val.txt 的数据集,已经补充了统一的项目级划分机制。

支持的数据集

  1. BUSI
  2. BUS-BRA
  3. BUS_UC
  4. CCAUI
  5. DDTI

生成规则

默认会在对应数据集根目录下生成:

  1. splits/project/train.txt
  2. splits/project/val.txt

默认策略:

  1. val_ratio = 0.2
  2. seed = 42
  3. 如果数据集带有 class_name,默认按类别分层抽样

已验证结果

  1. BUSI
    • train = 624
    • val = 156
  2. BUS-BRA
    • train = 1500
    • val = 375
  3. BUS_UC
    • train = 649
    • val = 162
    • 注意:当前正式划分只基于 All 子集生成与读取,避免与 Benign / Malignant 重复
  4. CCAUI
    • train = 880
    • val = 220
  5. DDTI
    • train = 384
    • val = 96

使用方式

先生成:

python tmp/generate_project_split.py --dataset BUSI --root data/BUSI
python tmp/generate_project_split.py --dataset BUS-BRA --root data/BUS-BRA
python tmp/generate_project_split.py --dataset BUS_UC --root data/BUS_UC
python tmp/generate_project_split.py --dataset CCAUI --root data/CCAUI
python tmp/generate_project_split.py --dataset DDTI --root data/DDTI

再读取:

python tmp/inspect_dataloader.py --dataset BUSI --root data/BUSI --split train
python tmp/inspect_dataloader.py --dataset BUS-BRA --root data/BUS-BRA --split val
python tmp/inspect_dataloader.py --dataset BUS_UC --root data/BUS_UC --split train
python tmp/inspect_dataloader.py --dataset CCAUI --root data/CCAUI --split train
python tmp/inspect_dataloader.py --dataset DDTI --root data/DDTI --split val

当前半监督划分支持

在已有 train 基底上,当前已经支持继续生成:

  1. labeled.txt
  2. unlabeled.txt

并支持多种标注比例,例如:

  1. 0.05
  2. 0.1
  3. 0.2

存储结构

生成结果默认写入:

数据集根目录/
  splits/
    project/
      train.txt
      val.txt
      semi_supervised/
        train/
          seed_42/
            ratio_0p05/
              labeled.txt
              unlabeled.txt
            ratio_0p1/
              labeled.txt
              unlabeled.txt
            ratio_0p2/
              labeled.txt
              unlabeled.txt

已验证结果

1. BUSI

基于 train = 624

  1. 0.05 -> labeled = 31, unlabeled = 593
  2. 0.1 -> labeled = 63, unlabeled = 561
  3. 0.2 -> labeled = 125, unlabeled = 499

2. BUS_UC

基于 train = 649

  1. 0.05 -> labeled = 32, unlabeled = 617
  2. 0.1 -> labeled = 65, unlabeled = 584
  3. 0.2 -> labeled = 130, unlabeled = 519

3. DDTI

基于 train = 384

  1. 0.05 -> labeled = 19, unlabeled = 365
  2. 0.1 -> labeled = 38, unlabeled = 346
  3. 0.2 -> labeled = 77, unlabeled = 307

生成方式

python tmp/generate_project_split.py --dataset BUSI --root data/BUSI --labeled-ratios 0.05,0.1,0.2
python tmp/generate_project_split.py --dataset BUS_UC --root data/BUS_UC --labeled-ratios 0.05,0.1,0.2
python tmp/generate_project_split.py --dataset DDTI --root data/DDTI --labeled-ratios 0.05,0.1,0.2

读取方式

python tmp/inspect_dataloader.py --dataset BUSI --root data/BUSI --split labeled --labeled-ratio 0.1
python tmp/inspect_dataloader.py --dataset BUS_UC --root data/BUS_UC --split unlabeled --labeled-ratio 0.2
python tmp/inspect_dataloader.py --dataset DDTI --root data/DDTI --split labeled --labeled-ratio 0.05

设计说明

  1. labeled / unlabeled 当前默认都从 train 基底继续切分
  2. 若数据集带 class_name,默认继续按类别分层抽样
  3. BUS_UC 仍然只基于 All 子集进行半监督划分,避免重复样本

为什么现在先不接训练器

因为当前最重要的是把“数据集结构差异”收敛掉。

如果现在直接在训练器里接,会导致:

  1. 路径解析逻辑和训练逻辑混在一起
  2. 后续新增数据集时必须改训练器
  3. 不利于调试单个数据集的索引是否正确

当前调试方式

增加了:

  1. tmp/inspect_dataset_index.py
  2. tmp/generate_project_split.py

可用来快速检查索引结果,例如:

python tmp/inspect_dataset_index.py --dataset BUS-UCLM --root data/BUS-UCLM
python tmp/inspect_dataset_index.py --dataset TN3K --root data/TN3K
python tmp/inspect_dataset_index.py --dataset TG3K --root data/TG3K
python tmp/inspect_dataset_index.py --dataset BUSI --root data/BUSI
python tmp/inspect_dataset_index.py --dataset BUS_UC --root data/BUS_UC
python tmp/inspect_dataset_index.py --dataset CCAUI --root data/CCAUI
python tmp/inspect_dataset_index.py --dataset DDTI --root data/DDTI
python tmp/inspect_ddti_mask.py --image data/DDTI/106_3.jpg --xml data/DDTI/106.xml --save tmp/ddti_106_3_mask.png
python tmp/inspect_dataloader.py --dataset OTU_2d --root data/OTU_2d --split train
python tmp/inspect_dataloader.py --dataset OTU_2d --root data/OTU_2d --split val
python tmp/inspect_dataloader.py --dataset TN3K --root data/TN3K --split train
python tmp/inspect_dataloader.py --dataset TN3K --root data/TN3K --split val
python tmp/inspect_dataloader.py --dataset TG3K --root data/TG3K --split train
python tmp/inspect_dataloader.py --dataset TG3K --root data/TG3K --split val
python tmp/inspect_dataloader.py --dataset DDTI --root data/DDTI
python tmp/generate_project_split.py --dataset BUSI --root data/BUSI
python tmp/generate_project_split.py --dataset BUS-BRA --root data/BUS-BRA
python tmp/generate_project_split.py --dataset BUS_UC --root data/BUS_UC
python tmp/generate_project_split.py --dataset CCAUI --root data/CCAUI
python tmp/generate_project_split.py --dataset DDTI --root data/DDTI
python tmp/generate_project_split.py --dataset BUSI --root data/BUSI --labeled-ratios 0.05,0.1,0.2
python tmp/inspect_dataloader.py --dataset BUSI --root data/BUSI --split labeled --labeled-ratio 0.1

下一步建议

在数据加载器分层已经确定后,下一步再做:

  1. 在配置层显式声明 dataset_name / split / split_file / image_size / batch_collate_strategy
  2. 再把 SegSampleRecord 和读取型 Dataset 接入训练器
  3. 在训练配置层加入 labeled_ratio / semi_seed / semi_base_split
  4. 之后再考虑把 DDTI 生成后的掩膜缓存到磁盘还是运行时动态生成