This document describes the two-pipeline structure of Vesuvius and how a Kaggle submission flows from raw CT volumes to a submission.zip.
┌───────────────────────────────┐
│ data/ │
│ ├── download.py │ Kaggle CLI wrapper with retry/resume
│ ├── download_vesuvius.py │ Vesuvius competition data download
│ └── download_model.py │ kagglehub model download (nnUNet ckpt)
└──────────────┬──────────────────┘
│ produces DATA_ROOT/
│ ├── train_images/*.tif
│ ├── train_labels/*.tif
│ ├── test_images/*.tif
│ └── test.csv
▼
┌────────────────────────────┴────────────────────────────┐
│ │
▼ ▼
┌──────────────────────────────┐ ┌──────────────────────────────────┐
│ Pipeline A: nnunet/ │ │ Pipeline B: segm3d/ │
│ nnUNetv2 CLI wrapper │ │ MONAI + PyTorch Lightning │
│ │ │ │
│ data_process.py │ │ train.py (resize to cube) │
│ → nnUNetv2_plan_and_ │ │ train_v1.py (random patch) │
│ preprocess │ │ infer.py (resize back) │
│ train.py │ │ infer_v1.py (sliding window) │
│ → nnUNetv2_train (DDP) │ │ │
│ infer.py │ │ Networks: SegResNet, SwinUNETR │
│ → nnUNetv2_predict + │ │ Loss: DiceCE / Tversky │
│ binarize + zip │ │ (ignore=2 skipped) │
└──────────────┬───────────────┘ └────────────────┬─────────────────┘
│ │
│ optional predictions-style postprocess: │
│ 3D hysteresis + anisotropic closing + │
│ small-CC removal (built into both infer_*) │
└────────────────────┬───────────────────────────┘
▼
┌──────────────────┐
│ submission.zip │
│ (1-bit TIFF │
│ per test id) │
└──────────────────┘
nnunet/ is a thin Python wrapper around the nnUNetv2_* CLIs. It does not reimplement segmentation; it prepares inputs, launches training/inference as subprocesses, and converts nnUNet output back into the binary TIFFs the competition expects.
nnunet/data_process.py— Step 1/2. ConvertsDATA_ROOT/train_images+train_labelsintonnUNet_raw/Dataset100_Vesuvius/(TIFF + JSON spacing sidecar), then invokesnnUNetv2_plan_and_preprocessto producennUNet_preprocessed/.nnunet/train.py— Step 2/2. Sets thennUNet_raw/nnUNet_preprocessed/nnUNet_resultsenv vars and invokesnnUNetv2_trainfor the requested fold(s). Handles the DDP constraint (global batch_size >= num_gpus) and offers a parallel-folds mode to fill 8 GPUs when a single DDP run is capped at 2.nnunet/infer.py— Renamestest_images/*.tifto the*_0000.tifnaming nnUNet expects, writes spacing sidecars, invokesnnUNetv2_predict, then converts the predicted.npz/.tif/.nii.gzto 1-bit TIFFs and packssubmission.zip. Optional 3D hysteresis + closing + small-CC removal postprocess.
segm3d/ is a self-contained MONAI + Lightning implementation. The network is built and trained in-process (no external CLI). Two generations live side-by-side:
segm3d/train.py(base) — Resizes each volume to--model-input-size(default 160³), trains aSegResNet/SwinUNETRwithDiceCELoss(ignore=2 excluded), checkpoints onval_dice.segm3d/infer.py(base) — Same resize-back path: predicts on the resized cube and resamples the mask to the original spacing. Faster, but resize-then-resample introduces topological artifacts at the surface boundary.segm3d/train_v1.py— Random-patch training at--patch-size 192³with pos/neg guided sampling (foreground-center probability--pos-fraction 0.7). EMA of weights, monitored onval_fg_dice(foreground class=1 only). Strict ignore mask in both loss and metrics.segm3d/infer_v1.py— Native-resolutionsliding_window_inferencewith--overlap 0.5~0.7and optional light TTA (noise / scale jitter, no flips). Avoids the resize-then-resample topological hard-failure of baseinfer.py.
| Concern | nnunet/ | segm3d/ base | segm3d/ v1 |
|---|---|---|---|
| Reproducible SOTA baseline | ✅ | — | — |
| Topology-correct surface mask | ✅ (with postprocess) | — | ✅ |
| Easy to extend (new loss, TTA, sampler) | — (CLI) | ✅ | ✅ |
| Multi-GPU on 8 cards | parallel folds | DDP / ddp_spawn | DDP / ddp_spawn |
| Lowest effort to retrain from scratch | medium (need plan_and_preprocess) | low | medium |
python data/download_vesuvius.py --out ./kaggle_data/vesuvius_surface_detectionpython nnunet/data_process.py --input-dir DATA_ROOT --work-dir ./work --dataset-id 100 \
--configuration 3d_fullres --planner nnUNetPlannerResEncM
python nnunet/train.py --work-dir ./work --dataset-id 100 \
--configuration 3d_fullres --plans-name nnUNetResEncUNetMPlans \
--fold all --epochs 250 --num-gpus 1python 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# Pipeline A
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
# Pipeline B v1
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 1Both produce work-*/submission.zip containing one 1-bit TIFF per id in test.csv.
--postprocess 1 enables the "predictions-style" 3D postprocess originally prototyped in notebooks/vesuvius-predictions-1feb.ipynb:
- 3D hysteresis thresholding — low threshold
--T-low 0.30, high threshold--T-high 0.80(voxels above high are kept; voxels above low are kept only if 6-connected to a high one). - Anisotropic closing — structure element extended along z (
--z-radius 3) vs xy (--xy-radius 2) to match the CT volume's anisotropic spacing. - Small-CC removal —
--dust-min-size 100drops tiny isolated components likely to be false positives.
Requires scipy + scikit-image (already in requirements.txt).