Kaggle Silver Medal solution for the Vesuvius Challenge - Surface Detection competition.
🥈 Silver Medalist · Final LB 0.59757 · Top 5% · 2026 (1st place: 0.62702)
- 背景
- 最终成绩
- 仓库结构
- 架构
- 快速开始
- 数据目录约定
- 方案 A:3D-SegM(MONAI + Lightning)
- 方案 B:nnUNetv2
- 可选:predictions 风格后处理
- 常见问题
- 关键依赖
- 文档
- 贡献
- 引用
- License
Vesuvius Challenge - Surface Detection 要求从卷积 CT 体数据中分割出可展开的 papyrus 表面层 — 一个 3D 二值分割问题,体数据各向异性、表面薄且拓扑敏感,后处理(3D hysteresis + 各向异性 closing + 去小连通域)对 LB 影响显著。
本仓库把两条常见 baseline 流程脚本化,便于在本地(含 Windows)训练与生成 submission.zip:
- nnUNetv2 路线(
nnunet/):自动规划 / 预处理 / 训练 / 推理,调用nnUNetv2_*CLI - 3D-SegM 路线(
segm3d/):MONAI + PyTorch Lightning 的 3D 分割 baseline(SegResNet / SwinUNETR),含 v1 的 patch 训练 + 原尺寸滑窗推理
🥈 Silver Medalist — Kaggle Vesuvius Challenge - Surface Detection 银牌。
| 指标 | 数值 |
|---|---|
| 最终 Public/Private LB | 0.59757 |
| 排名 | Top 5% |
| 第一名分数 | 0.62702 |
| 比赛年份 | 2026 |
Vesuvius/
├── data/ # Kaggle 数据 / 模型下载工具
│ ├── download.py # 通用 Kaggle CLI 包装,带重试/续传
│ ├── download_vesuvius.py # Vesuvius 比赛数据下载 + 解压
│ └── download_model.py # kagglehub 预训练模型下载
├── nnunet/ # Pipeline A:nnUNetv2 CLI 包装
│ ├── data_process.py # 准备 nnUNet_raw + plan_and_preprocess
│ ├── train.py # 调用 nnUNetv2_train(支持多 fold 并行)
│ └── infer.py # 调用 nnUNetv2_predict + 打包 submission.zip
├── segm3d/ # Pipeline B:MONAI + PyTorch Lightning
│ ├── train.py # 训练(resize 到立方体,base 版)
│ ├── train_v1.py # 训练(随机 patch 采样 + EMA + pos/neg 引导)
│ ├── infer.py # 推理(resize 回原尺寸,base 版)
│ └── infer_v1.py # 推理(原尺寸 sliding_window_inference)
├── notebooks/ # 三个参考 notebook
│ ├── surface-nnunet-training-inference-with-2xt4.ipynb
│ ├── surface-train-inference-3d-segm-gpu-augment.ipynb
│ └── vesuvius-predictions-1feb.ipynb
├── docs/
│ └── kaggle-silver-medal.png # 银牌证书
├── .github/ # CI + issue/PR 模板 + dependabot
│ ├── workflows/ci.yml # py_compile + markdown-link-check
│ ├── ISSUE_TEMPLATE/ # bug_report.md, enhancement.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ ├── dependabot.yml
│ ├── FUNDING.yml
│ └── markdown-link-check.json
├── ARCHITECTURE.md # 双管线架构图 + 数据流
├── CHANGELOG.md # 版本里程碑
├── CITATION.cff # 学术引用
├── CONTRIBUTING.md # 贡献指南
├── CODE_OF_CONDUCT.md # Contributor Covenant 2.1
├── LICENSE # MIT
├── README.md # 本文件
└── requirements.txt # Python 依赖
双管线结构,详见 ARCHITECTURE.md:
┌────────────────────────┐
│ data/ (download*) │ → DATA_ROOT/
└──────────┬─────────────┘
▼
┌─────────────────────┴─────────────────────┐
▼ ▼
┌────────────────────┐ ┌─────────────────────┐
│ nnunet/ (CLI) │ │ segm3d/ (MONAI) │
│ data_process.py │ │ train.py │
│ train.py │ │ train_v1.py │
│ infer.py │ │ infer.py │
│ │ │ infer_v1.py │
└──────────┬─────────┘ └──────────┬──────────┘
│ --postprocess 1: 3D hysteresis + │
│ anisotropic closing + small-CC │
└──────────────────┬────────────────────┘
▼
submission.zip
git clone https://github.com/Calix-L/Vesuvius.git
cd Vesuvius
python -m venv venv
# PowerShell
.\venv\Scripts\Activate.ps1
# bash
source venv/bin/activate
# 1) 安装 PyTorch(按官方向导选 CUDA/CPU 版本)
# https://pytorch.org/get-started/locally/
# 2) 安装其余依赖
pip install -r requirements.txt验证安装:
python -c "import torch, monai, pytorch_lightning, nnunetv2, tifffile; \
print('torch', torch.__version__, 'monai', monai.__version__, \
'pl', pytorch_lightning.__version__, 'nnunetv2', nnunetv2.__version__)"下面把比赛数据根目录记为 DATA_ROOT:
DATA_ROOT/
train_images/
<case_id>.tif
...
train_labels/
<case_id>.tif
...
test_images/
<case_id>.tif
...
test.csv
train_images与train_labels必须文件名一一对应(同名.tif / .npy / .npz也可用于 3D-SegM)。test.csv里id列决定最终需要打包进 zip 的 mask 文件名。
下载比赛数据:
python data/download_vesuvius.py --out ./kaggle_data/vesuvius_surface_detectionpython segm3d/train.py \
--train-images-dir "DATA_ROOT/train_images" \
--train-labels-dir "DATA_ROOT/train_labels" \
--output-dir "./work_3d_segm" \
--arch segresnet \
--model-input-size 160 160 160 \
--max-epochs 20 \
--batch-size 1 \
--num-workers 2 \
--gpu-transforms 1- 断点续训(从
output-dir自动找val_dice最好的 ckpt 继续):
python segm3d/train.py ... --resume-best 1segm3d/infer.py 需要 --root-dir 指向 DATA_ROOT(包含 test_images/ 与 test.csv)。
python segm3d/infer.py \
--root-dir "DATA_ROOT" \
--checkpoint-dir "./work_3d_segm" \
--work-dir "./work_3d_segm_infer" \
--postprocess 1输出:
work-dir/submission_masks/*.tifwork-dir/submission.zip
如果你希望避免 "resize 到固定立方体再插回去" 带来的拓扑硬伤,可以使用 v1 脚本:
- 训练:
segm3d/train_v1.py(随机采样 patch,默认 192³) - 推理:
segm3d/infer_v1.py(原尺寸sliding_window_inference)
python segm3d/train_v1.py \
--train-images-dir "DATA_ROOT/train_images" \
--train-labels-dir "DATA_ROOT/train_labels" \
--output-dir "./work_3d_segm_v1" \
--arch segresnet \
--patch-size 192 192 192 \
--samples-per-volume 16 \
--pos-fraction 0.7 \
--batch-size 1 \
--max-epochs 20 \
--ema 1 \
--ema-decay 0.999多 GPU 训练(Lightning 原生支持):
- Linux(推荐 DDP):
python segm3d/train_v1.py \
--train-images-dir "DATA_ROOT/train_images" \
--train-labels-dir "DATA_ROOT/train_labels" \
--output-dir "./work_3d_segm_v1" \
--devices 4 \
--strategy ddp- Windows(通常用 spawn):
python segm3d/train_v1.py \
--train-images-dir "DATA_ROOT/train_images" \
--train-labels-dir "DATA_ROOT/train_labels" \
--output-dir "./work_3d_segm_v1" \
--devices 2 \
--strategy ddp_spawn关键参数:
--patch-size 192 192 192:训练 patch 尺寸(起步推荐 192³)。--samples-per-volume:每个 volume 每个 epoch 采多少个 patch(越大越"见得多",但训练越慢)。--pos-fraction:pos/neg 引导采样中"采到前景中心"的概率(尽量多采到标注区域,避免全 ignore patch)。--ema/--ema-decay:启用 EMA;验证与保存 best 会使用 EMA 权重,监控指标为val_fg_dice(只算 class=1)。--devices:使用哪些 GPU(auto/1/0,1,2,3)。--strategy:分布式策略(auto/ddp/ddp_spawn)。--num-nodes:多机训练节点数(默认 1)。
python segm3d/infer_v1.py \
--root-dir "DATA_ROOT" \
--checkpoint-dir "./work_3d_segm_v1" \
--work-dir "./work_3d_segm_v1_infer" \
--roi-size 192 192 192 \
--overlap 0.6 \
--sw-batch-size 1 \
--tta 1 \
--postprocess 1关键参数:
--roi-size 192 192 192:滑窗推理窗口大小;也可试224 224 224。--overlap 0.5~0.7:越大越平滑、越不容易断裂/块状边界,但更慢更吃显存。--tta 1:轻扰动 TTA(不翻转),做两次推理取均值(可用--tta-noise-std/--tta-scale-jitter微调)。
先把比赛数据转换为 nnUNet 目录结构,并运行 nnUNetv2_plan_and_preprocess:
python nnunet/data_process.py \
--input-dir "DATA_ROOT" \
--work-dir "./work" \
--dataset-id 100 \
--configuration 3d_fullres \
--planner nnUNetPlannerResEncM- 只做"准备数据"(不跑 preprocess):
--do-preprocess 0 - 只做"preprocess"(不重新准备数据):
--do-prepare 0 - Windows 下如果 symlink 权限有问题:加
--no-symlinks
python nnunet/train.py \
--work-dir "./work" \
--dataset-id 100 \
--configuration 3d_fullres \
--plans-name nnUNetResEncUNetMPlans \
--fold all \
--epochs 250 \
--num-gpus 1nnUNet 的 DDP 多卡训练有一个硬性约束:global batch_size 必须 >= GPU 数(否则会 assert 报错)。
- 例如你贴出来的
3d_fullres/3d_lowres在 plans 里batch_size=2,所以:- 单次 DDP 最多只能用 2 张卡:
--num-gpus 2 - 直接
--num-gpus 8一定会失败
- 单次 DDP 最多只能用 2 张卡:
- 我已在脚本里加入"提前检查":当
--num-gpus超过 plans 的batch_size时,会直接给出更清晰的报错与建议。
当单次 DDP 只能用 12 卡时,想把 8 卡吃满,推荐改为"并行启动多个 fold",每个 fold 占用 12 卡。
示例:8 张卡并行跑 4 个 fold(每个 fold 2 卡):
python nnunet/train.py \
--work-dir "./work" \
--dataset-id 100 \
--configuration 3d_fullres \
--plans-name nnUNetResEncUNetMPlans \
--fold all \
--epochs 250 \
--parallel-folds 1 \
--num-gpus 8 \
--devices 0,1,2,3,4,5,6,7 \
--gpus-per-job 2参数说明:
--parallel-folds 1:开启并行 fold 模式(多进程)。--devices:指定 GPU 池(会为每个子进程设置CUDA_VISIBLE_DEVICES)。--gpus-per-job:每个 fold 占用几张卡(必须<= plans batch_size;你这个 plans 是 2)。
小技巧:
- 不写
--devices时:会优先尝试读取环境变量CUDA_VISIBLE_DEVICES作为 GPU 池;如果也没有,会假设0..num_gpus-1。 --fold除了all,也支持0或0,1,2这种形式(方便你只跑部分 fold)。
python nnunet/infer.py \
--root-dir "DATA_ROOT" \
--work-dir "./work_infer" \
--dataset-id 100 \
--configuration 3d_fullres \
--plans-name nnUNetResEncUNetMPlans \
--trainer nnUNetTrainer_250epochs \
--fold all \
--checkpoint checkpoint_final.pth \
--save-probabilities 1 \
--postprocess 1输出:
work-dir/submission_masks/*.tifwork-dir/submission.zip
两个推理脚本(nnunet/infer.py、segm3d/infer.py、segm3d/infer_v1.py)都支持 --postprocess 1,原型来自 notebooks/vesuvius-predictions-1feb.ipynb。
默认参数(可按需调整):
--T-low 0.30/--T-high 0.80:3D hysteresis 阈值--z-radius 3/--xy-radius 2:各向异性 closing 结构元--dust-min-size 100:去除小连通域
依赖:scipy + scikit-image(已在 requirements.txt 中列出)。
- 找不到数据/文件名不匹配:确保
train_images与train_labels文件名完全一致;并确认test.csv存在且test_images下有对应.tif。 - Windows symlink 失败:nnUNet 相关脚本可用
--no-symlinks,或开启 Windows 开发者模式/管理员权限。 - 显存不够(OOM):优先减小
--model-input-size(3D-SegM)或减小 batch、关闭--gpu-transforms;nnUNet 则考虑换配置/patch size 或减少并行。 - nnUNet DDP
--num-gpus 8失败:plans 的batch_size=2,单次 DDP 最多 2 卡;改用--parallel-folds 1 --gpus-per-job 2并行多个 fold。
- Python 3.10+
- PyTorch(https://pytorch.org/get-started/locally/)
monai、pytorch-lightning— 3D-SegM 路线nnunetv2— nnUNet 路线tifffile— TIFF I/Oscipy+scikit-image— predictions 风格后处理pandas—test.csv处理kagglehub—data/download_model.py的预训练模型下载
完整列表见 requirements.txt。
ARCHITECTURE.md— 双管线架构图与数据流,nnUNet CLI 包装 vs MONAI+Lightning 自实现,v1 与 base 的差异CHANGELOG.md— 版本里程碑CONTRIBUTING.md— 贡献指南CODE_OF_CONDUCT.md— 行为准则CITATION.cff— 学术引用
欢迎提交管线修复、后处理改进或文档补全,请先读 CONTRIBUTING.md。提交 PR 前运行 pre-commit run --all-files。
@software{calix-l_vesuvius_2026,
author = {Calix-L},
title = {vesuvius: Surface Detection baselines (nnUNetv2 + MONAI 3D-SegM)},
year = {2026},
url = {https://github.com/Calix-L/Vesuvius},
version = {1.1.0}
}MIT License — 冲榜代码,仅供学习参考。比赛规则的所有内容归 Kaggle / Vesuvius Challenge 主办方所有。