Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. Β See raw diff
- .gitattributes +41 -0
- README.md +134 -0
- nnUNet/.github/workflows/codespell.yml +22 -0
- nnUNet/.github/workflows/run_tests_nnunet.yml +49 -0
- nnUNet/.gitignore +119 -0
- nnUNet/CONTRIBUTING.md +50 -0
- nnUNet/LICENSE +201 -0
- nnUNet/documentation/__init__.py +0 -0
- nnUNet/documentation/assets/HI_Logo.png +0 -0
- nnUNet/documentation/assets/amos2022_sparseseg10.png +3 -0
- nnUNet/documentation/assets/amos2022_sparseseg10_2d.png +3 -0
- nnUNet/documentation/assets/dkfz_logo.png +0 -0
- nnUNet/documentation/assets/nnU-Net_overview.png +3 -0
- nnUNet/documentation/assets/nnUNetMagician.png +3 -0
- nnUNet/documentation/assets/regions_vs_labels.png +3 -0
- nnUNet/documentation/assets/scribble_example.png +3 -0
- nnUNet/documentation/assets/sparse_annotation_amos.png +0 -0
- nnUNet/documentation/benchmarking.md +115 -0
- nnUNet/documentation/changelog.md +51 -0
- nnUNet/documentation/competitions/AortaSeg24.md +46 -0
- nnUNet/documentation/competitions/AutoPETII.md +129 -0
- nnUNet/documentation/competitions/FLARE24/Task_1/__init__.py +0 -0
- nnUNet/documentation/competitions/FLARE24/Task_1/inference_flare_task1.py +191 -0
- nnUNet/documentation/competitions/FLARE24/Task_1/readme.md +78 -0
- nnUNet/documentation/competitions/FLARE24/Task_2/__init__.py +0 -0
- nnUNet/documentation/competitions/FLARE24/Task_2/inference_flare_task2.py +449 -0
- nnUNet/documentation/competitions/FLARE24/Task_2/readme.md +102 -0
- nnUNet/documentation/competitions/FLARE24/__init__.py +0 -0
- nnUNet/documentation/competitions/Toothfairy2/__init__.py +0 -0
- nnUNet/documentation/competitions/Toothfairy2/inference_script_semseg_only_customInf2.py +343 -0
- nnUNet/documentation/competitions/Toothfairy2/readme.md +208 -0
- nnUNet/documentation/competitions/__init__.py +0 -0
- nnUNet/documentation/convert_msd_dataset.md +3 -0
- nnUNet/documentation/dataset_format.md +254 -0
- nnUNet/documentation/dataset_format_inference.md +39 -0
- nnUNet/documentation/explanation_logging.md +88 -0
- nnUNet/documentation/explanation_normalization.md +45 -0
- nnUNet/documentation/explanation_plans_files.md +185 -0
- nnUNet/documentation/extending_nnunet.md +37 -0
- nnUNet/documentation/how_to_use_nnunet.md +313 -0
- nnUNet/documentation/ignore_label.md +104 -0
- nnUNet/documentation/installation_instructions.md +87 -0
- nnUNet/documentation/manual_data_splits.md +46 -0
- nnUNet/documentation/pretraining_and_finetuning.md +82 -0
- nnUNet/documentation/region_based_training.md +79 -0
- nnUNet/documentation/resenc_presets.md +126 -0
- nnUNet/documentation/run_inference_with_pretrained_models.md +7 -0
- nnUNet/documentation/set_environment_variables.md +78 -0
- nnUNet/documentation/setting_up_paths.md +39 -0
- nnUNet/documentation/tldr_migration_guide_from_v1.md +20 -0
.gitattributes
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
nnUNet/documentation/assets/amos2022_sparseseg10.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
nnUNet/documentation/assets/amos2022_sparseseg10_2d.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
nnUNet/documentation/assets/nnU-Net_overview.png filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
nnUNet/documentation/assets/nnUNetMagician.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
nnUNet/documentation/assets/regions_vs_labels.png filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
nnUNet/documentation/assets/scribble_example.png filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: cc-by-4.0
|
| 3 |
+
---
|
| 4 |
+
This repository provides the nnUNet v2 checkpoint for multi-organ lesion segmentation from abdominal CT images.
|
| 5 |
+
|
| 6 |
+
The model was trained on a large-scale multi-center CT dataset with expert-annotated lesion masks across multiple organs (liver, pancreas, kidney, colon).
|
| 7 |
+
|
| 8 |
+
First of all, you need a Linux OS server and basic terminal usage.
|
| 9 |
+
|
| 10 |
+
## 0. Before you start
|
| 11 |
+
You need:
|
| 12 |
+
CT files in .nii.gz format
|
| 13 |
+
|
| 14 |
+
Each CT is a 3D volume (not single images)
|
| 15 |
+
|
| 16 |
+
## 1. Installation (Environment and nnUNet packages)
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
git clone https://huggingface.co/ChrisXzZ/LesionSegmenter
|
| 20 |
+
conda create -n lesionseg python=3.10 -y
|
| 21 |
+
conda activate lesionseg
|
| 22 |
+
git clone https://github.com/MIC-DKFZ/nnUNet.git
|
| 23 |
+
cd nnUNet
|
| 24 |
+
pip install -e .
|
| 25 |
+
pip uninstall -y torch torchvision torchaudio
|
| 26 |
+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
| 27 |
+
```
|
| 28 |
+
|
| 29 |
+
## 2. Prepare your CT data
|
| 30 |
+
Create the following folder structure:
|
| 31 |
+
```bash
|
| 32 |
+
mkdir -p nnUNet_eval/Dataset1356/imagesTs
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Put your CT files into:
|
| 36 |
+
```bash
|
| 37 |
+
nnUNet_eval/Dataset1356/imagesTs/
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
Each file must be renamed like this:
|
| 41 |
+
```bash
|
| 42 |
+
nnUNet_eval/
|
| 43 |
+
βββ Dataset1356/
|
| 44 |
+
βββ imagesTs/
|
| 45 |
+
βββ ct001_0000.nii.gz
|
| 46 |
+
βββ ct002_0000.nii.gz
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
Key rules:
|
| 50 |
+
Must end with _0000.nii.gz
|
| 51 |
+
Each case = one file
|
| 52 |
+
|
| 53 |
+
#### 3. Put the Model (Checkpoint)
|
| 54 |
+
Download the model checkpoint (provided by us), then place it like this:
|
| 55 |
+
```bash
|
| 56 |
+
nnUNet_results/
|
| 57 |
+
βββ Dataset1356_RADV3S/nnUNetTrainer__nnUNetResEncUNetLPlans__3d_fullres/fold_all/checkpoint_final.pth
|
| 58 |
+
```
|
| 59 |
+
Or
|
| 60 |
+
|
| 61 |
+
```bash
|
| 62 |
+
nnUNet_results/
|
| 63 |
+
βββ Dataset1357_RADN1/nnUNetTrainer__nnUNetResEncUNetLPlans__3d_fullres/fold_all/checkpoint_final.pth
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
#### 4. Run inference
|
| 68 |
+
First set paths:
|
| 69 |
+
```bash
|
| 70 |
+
export nnUNet_N_proc_DA=36
|
| 71 |
+
export nnUNet_results="./nnUNet_results"
|
| 72 |
+
export nnUNet_predictions="./nnUNet_predictions"
|
| 73 |
+
export nnUNet_eval="./nnUNet_eval"
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Then run:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
GPU_ID=0
|
| 80 |
+
DATASET=1356
|
| 81 |
+
|
| 82 |
+
TRAINER=nnUNetTrainer
|
| 83 |
+
|
| 84 |
+
CUDA_VISIBLE_DEVICES=$GPU_ID nnUNetv2_predict -d $DATASET -i $nnUNet_eval/Dataset1356/imagesTs/ -o $nnUNet_predictions/ -tr $TRAINER -d $DATASET -c 3d_fullres -f all -p nnUNetResEncUNetLPlans --continue_prediction
|
| 85 |
+
```
|
| 86 |
+
Or
|
| 87 |
+
|
| 88 |
+
```bash
|
| 89 |
+
export nnUNet_N_proc_DA=36
|
| 90 |
+
export nnUNet_results="./nnUNet_results"
|
| 91 |
+
export nnUNet_predictions="./nnUNet_predictions"
|
| 92 |
+
export nnUNet_eval="./nnUNet_eval"
|
| 93 |
+
|
| 94 |
+
GPU_ID=0
|
| 95 |
+
DATASET=1357
|
| 96 |
+
|
| 97 |
+
TRAINER=nnUNetTrainer
|
| 98 |
+
|
| 99 |
+
CUDA_VISIBLE_DEVICES=$GPU_ID nnUNetv2_predict -d $DATASET -i $nnUNet_eval/Dataset1357/imagesTs// -o $nnUNet_predictions/ -tr $TRAINER -d $DATASET -c 3d_fullres -f all -p nnUNetResEncUNetLPlans --continue_prediction
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
+
### 5. Run post-processing
|
| 103 |
+
This step removes false positives and improves quality.
|
| 104 |
+
|
| 105 |
+
For DATASET=1356 checkpoint, run:
|
| 106 |
+
```bash
|
| 107 |
+
python p-processing.py \
|
| 108 |
+
--input_dir ./nnUNet_predictions \
|
| 109 |
+
--output_dir ./nnUNet_predictions \
|
| 110 |
+
--rules "39:5,40:6|7|8,41:3|4,42:16" \
|
| 111 |
+
--keep_mode any \
|
| 112 |
+
--target_labels "39,40,41,42" \
|
| 113 |
+
--min_size 25 \
|
| 114 |
+
--dilation_iters 1 \
|
| 115 |
+
--connectivity 26 \
|
| 116 |
+
--num_workers 16
|
| 117 |
+
```
|
| 118 |
+
|
| 119 |
+
For DATASET=1357 checkpoint, run:
|
| 120 |
+
```bash
|
| 121 |
+
python p-processing.py \
|
| 122 |
+
--input_dir ./nnUNet_predictions \
|
| 123 |
+
--output_dir ./nnUNet_predictions \
|
| 124 |
+
--rules "116:5|6|7|8|9|10|11|12,117:13|14|15,118:3|4,119:23" \
|
| 125 |
+
--keep_mode any \
|
| 126 |
+
--target_labels "116,117,118,119" \
|
| 127 |
+
--min_size 25 \
|
| 128 |
+
--dilation_iters 1 \
|
| 129 |
+
--connectivity 26 \
|
| 130 |
+
--num_workers 16
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
|
nnUNet/.github/workflows/codespell.yml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: Codespell
|
| 3 |
+
|
| 4 |
+
on:
|
| 5 |
+
push:
|
| 6 |
+
branches: [master]
|
| 7 |
+
pull_request:
|
| 8 |
+
branches: [master]
|
| 9 |
+
|
| 10 |
+
permissions:
|
| 11 |
+
contents: read
|
| 12 |
+
|
| 13 |
+
jobs:
|
| 14 |
+
codespell:
|
| 15 |
+
name: Check for spelling errors
|
| 16 |
+
runs-on: ubuntu-latest
|
| 17 |
+
|
| 18 |
+
steps:
|
| 19 |
+
- name: Checkout
|
| 20 |
+
uses: actions/checkout@v3
|
| 21 |
+
- name: Codespell
|
| 22 |
+
uses: codespell-project/actions-codespell@v2
|
nnUNet/.github/workflows/run_tests_nnunet.yml
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Run nnunet tests on all OS
|
| 2 |
+
on:
|
| 3 |
+
push:
|
| 4 |
+
paths-ignore:
|
| 5 |
+
- '**.md'
|
| 6 |
+
jobs:
|
| 7 |
+
|
| 8 |
+
run-tests:
|
| 9 |
+
strategy:
|
| 10 |
+
matrix:
|
| 11 |
+
# os: [ubuntu-latest, windows-latest, macos-latest] # fails on windows until https://github.com/MIC-DKFZ/nnUNet/issues/2396 is resolved
|
| 12 |
+
os: [ubuntu-latest, macos-latest]
|
| 13 |
+
python-version: ["3.10"]
|
| 14 |
+
runs-on: ${{ matrix.os }}
|
| 15 |
+
|
| 16 |
+
steps:
|
| 17 |
+
- uses: actions/checkout@v4
|
| 18 |
+
|
| 19 |
+
- name: Set up Python ${{ matrix.python-version }}
|
| 20 |
+
uses: actions/setup-python@v5
|
| 21 |
+
with:
|
| 22 |
+
python-version: ${{ matrix.python-version }}
|
| 23 |
+
|
| 24 |
+
- name: Download and extract weights and download example file
|
| 25 |
+
# use python instead of curl to avoid the need to install curl (more difficult for macos)
|
| 26 |
+
run: |
|
| 27 |
+
mkdir -p $HOME/github_actions_nnunet/results
|
| 28 |
+
python -c "import urllib.request; urllib.request.urlretrieve('https://github.com/wasserth/TotalSegmentator/releases/download/v2.0.0-weights/Dataset300_body_6mm_1559subj.zip', '$HOME/github_actions_nnunet/results/tmp_download_file.zip')"
|
| 29 |
+
unzip -o $HOME/github_actions_nnunet/results/tmp_download_file.zip -d $HOME/github_actions_nnunet/results
|
| 30 |
+
rm $HOME/github_actions_nnunet/results/tmp_download_file.zip
|
| 31 |
+
|
| 32 |
+
- name: Install dependencies on Ubuntu
|
| 33 |
+
if: runner.os == 'Linux'
|
| 34 |
+
run: |
|
| 35 |
+
python -m pip install --upgrade pip
|
| 36 |
+
pip install pytest Cython
|
| 37 |
+
pip install torch==2.4.0 -f https://download.pytorch.org/whl/cpu
|
| 38 |
+
pip install .
|
| 39 |
+
|
| 40 |
+
- name: Install dependencies on Windows / MacOS
|
| 41 |
+
if: runner.os == 'Windows' || runner.os == 'macOS'
|
| 42 |
+
run: |
|
| 43 |
+
python -m pip install --upgrade pip
|
| 44 |
+
pip install pytest Cython
|
| 45 |
+
pip install torch==2.4.0
|
| 46 |
+
pip install .
|
| 47 |
+
|
| 48 |
+
- name: Run test script
|
| 49 |
+
run: python nnunetv2/tests/integration_tests/run_nnunet_inference.py
|
nnUNet/.gitignore
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
env/
|
| 12 |
+
build/
|
| 13 |
+
develop-eggs/
|
| 14 |
+
dist/
|
| 15 |
+
downloads/
|
| 16 |
+
eggs/
|
| 17 |
+
.eggs/
|
| 18 |
+
lib/
|
| 19 |
+
lib64/
|
| 20 |
+
parts/
|
| 21 |
+
sdist/
|
| 22 |
+
var/
|
| 23 |
+
*.egg-info/
|
| 24 |
+
.installed.cfg
|
| 25 |
+
*.egg
|
| 26 |
+
|
| 27 |
+
# PyInstaller
|
| 28 |
+
# Usually these files are written by a python script from a template
|
| 29 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 30 |
+
*.manifest
|
| 31 |
+
*.spec
|
| 32 |
+
|
| 33 |
+
# Installer logs
|
| 34 |
+
pip-log.txt
|
| 35 |
+
pip-delete-this-directory.txt
|
| 36 |
+
|
| 37 |
+
# Unit test / coverage reports
|
| 38 |
+
htmlcov/
|
| 39 |
+
.tox/
|
| 40 |
+
.coverage
|
| 41 |
+
.coverage.*
|
| 42 |
+
.cache
|
| 43 |
+
nosetests.xml
|
| 44 |
+
coverage.xml
|
| 45 |
+
*,cover
|
| 46 |
+
.hypothesis/
|
| 47 |
+
|
| 48 |
+
# Translations
|
| 49 |
+
*.mo
|
| 50 |
+
*.pot
|
| 51 |
+
|
| 52 |
+
# Django stuff:
|
| 53 |
+
*.log
|
| 54 |
+
local_settings.py
|
| 55 |
+
|
| 56 |
+
# Flask stuff:
|
| 57 |
+
instance/
|
| 58 |
+
.webassets-cache
|
| 59 |
+
|
| 60 |
+
# Scrapy stuff:
|
| 61 |
+
.scrapy
|
| 62 |
+
|
| 63 |
+
# Sphinx documentation
|
| 64 |
+
docs/_build/
|
| 65 |
+
|
| 66 |
+
# PyBuilder
|
| 67 |
+
target/
|
| 68 |
+
|
| 69 |
+
# IPython Notebook
|
| 70 |
+
.ipynb_checkpoints
|
| 71 |
+
|
| 72 |
+
# pyenv
|
| 73 |
+
.python-version
|
| 74 |
+
|
| 75 |
+
# celery beat schedule file
|
| 76 |
+
celerybeat-schedule
|
| 77 |
+
|
| 78 |
+
# dotenv
|
| 79 |
+
.env
|
| 80 |
+
|
| 81 |
+
# virtualenv
|
| 82 |
+
venv/
|
| 83 |
+
ENV/
|
| 84 |
+
|
| 85 |
+
# Spyder project settings
|
| 86 |
+
.spyderproject
|
| 87 |
+
|
| 88 |
+
# Rope project settings
|
| 89 |
+
.ropeproject
|
| 90 |
+
|
| 91 |
+
*.memmap
|
| 92 |
+
*.png
|
| 93 |
+
*.zip
|
| 94 |
+
*.npz
|
| 95 |
+
*.npy
|
| 96 |
+
*.jpg
|
| 97 |
+
*.jpeg
|
| 98 |
+
.idea
|
| 99 |
+
*.txt
|
| 100 |
+
.idea/*
|
| 101 |
+
*.png
|
| 102 |
+
# *.nii.gz # nifti files needed for example_data for github actions tests
|
| 103 |
+
*.nii
|
| 104 |
+
*.tif
|
| 105 |
+
*.bmp
|
| 106 |
+
*.pkl
|
| 107 |
+
*.xml
|
| 108 |
+
*.pkl
|
| 109 |
+
*.pdf
|
| 110 |
+
*.png
|
| 111 |
+
*.jpg
|
| 112 |
+
*.jpeg
|
| 113 |
+
|
| 114 |
+
*.model
|
| 115 |
+
|
| 116 |
+
!documentation/assets/scribble_example.png
|
| 117 |
+
|
| 118 |
+
CLAUDE.md
|
| 119 |
+
AGENTS.md
|
nnUNet/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to nnU-Net
|
| 2 |
+
|
| 3 |
+
Thank you for your interest in contributing to nnU-Net.
|
| 4 |
+
|
| 5 |
+
nnU-Net is developed and maintained by researchers at DKFZ. There is no dedicated funding or staff for maintaining the
|
| 6 |
+
repository, and development happens alongside research and teaching responsibilities. Our bandwidth for reviewing
|
| 7 |
+
external contributions is therefore limited, and review times may be long.
|
| 8 |
+
|
| 9 |
+
## General principles
|
| 10 |
+
|
| 11 |
+
nnU-Net is intentionally designed to be focused, stable, and generally applicable across datasets and use cases.
|
| 12 |
+
Contributions should respect this philosophy and should not introduce unnecessary complexity or specialization.
|
| 13 |
+
|
| 14 |
+
New functionality must either be generally valid across datasets and setups or convincingly benefit a large enough
|
| 15 |
+
portion of the user base. We aim to avoid bloating the framework or increasing its complexity further.
|
| 16 |
+
|
| 17 |
+
## How to contribute
|
| 18 |
+
|
| 19 |
+
For larger features and refactors, please open a GitHub issue to discuss the idea before starting work. Tag
|
| 20 |
+
@FabianIsensee so that the discussion doesn't get missed.
|
| 21 |
+
|
| 22 |
+
To submit a contribution, fork the repository, make your changes on a branch, and open a pull request.
|
| 23 |
+
|
| 24 |
+
## Bug reports and bug fixes
|
| 25 |
+
|
| 26 |
+
Bug reports must include a minimal reproducible example. Without a repro, it is usually impossible for us to
|
| 27 |
+
investigate issues.
|
| 28 |
+
|
| 29 |
+
Pull requests fixing bugs should also include a clear reproduction of the issue and an explanation of how the fix
|
| 30 |
+
resolves it.
|
| 31 |
+
|
| 32 |
+
## Performance improvements
|
| 33 |
+
|
| 34 |
+
If a pull request claims performance improvements, it must include benchmarks demonstrating the effect. The benchmark
|
| 35 |
+
setup must be described clearly enough for us to reproduce the results independently. We may run additional
|
| 36 |
+
tests ourselves before merging.
|
| 37 |
+
|
| 38 |
+
## Contributions that are unlikely to be merged
|
| 39 |
+
|
| 40 |
+
To keep the framework maintainable and the workload manageable on our end, we deprioritize:
|
| 41 |
+
|
| 42 |
+
- dataset-specific code
|
| 43 |
+
- features that only apply to niche setups
|
| 44 |
+
- narrow custom architectures or training pipelines
|
| 45 |
+
- large refactorings without prior discussion
|
| 46 |
+
- small PRs fixing minor typos or formatting issues
|
| 47 |
+
|
| 48 |
+
## Final note
|
| 49 |
+
|
| 50 |
+
We appreciate the effort people invest in improving nnU-Net!
|
nnUNet/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [2019] [Division of Medical Image Computing, German Cancer Research Center (DKFZ), Heidelberg, Germany]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
nnUNet/documentation/__init__.py
ADDED
|
File without changes
|
nnUNet/documentation/assets/HI_Logo.png
ADDED
|
nnUNet/documentation/assets/amos2022_sparseseg10.png
ADDED
|
Git LFS Details
|
nnUNet/documentation/assets/amos2022_sparseseg10_2d.png
ADDED
|
Git LFS Details
|
nnUNet/documentation/assets/dkfz_logo.png
ADDED
|
nnUNet/documentation/assets/nnU-Net_overview.png
ADDED
|
Git LFS Details
|
nnUNet/documentation/assets/nnUNetMagician.png
ADDED
|
Git LFS Details
|
nnUNet/documentation/assets/regions_vs_labels.png
ADDED
|
Git LFS Details
|
nnUNet/documentation/assets/scribble_example.png
ADDED
|
Git LFS Details
|
nnUNet/documentation/assets/sparse_annotation_amos.png
ADDED
|
nnUNet/documentation/benchmarking.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# nnU-Netv2 benchmarks
|
| 2 |
+
|
| 3 |
+
Does your system run like it should? Is your epoch time longer than expected? What epoch times should you expect?
|
| 4 |
+
|
| 5 |
+
Look no further for we have the solution here!
|
| 6 |
+
|
| 7 |
+
## What does the nnU-netv2 benchmark do?
|
| 8 |
+
|
| 9 |
+
nnU-Net's benchmark trains models for 5 epochs. At the end, the fastest epoch will
|
| 10 |
+
be noted down, along with the GPU name, torch version and cudnn version. You can find the benchmark output in the
|
| 11 |
+
corresponding nnUNet_results subfolder (see example below). Don't worry, we also provide scripts to collect your
|
| 12 |
+
results. Or you just start a benchmark and look at the console output. Everything is possible. Nothing is forbidden.
|
| 13 |
+
|
| 14 |
+
The benchmark implementation revolves around two trainers:
|
| 15 |
+
- `nnUNetTrainerBenchmark_5epochs` runs a regular training for 5 epochs. When completed, writes a .json file with the fastest
|
| 16 |
+
epoch time as well as the GPU used and the torch and cudnn versions. Useful for speed testing the entire pipeline
|
| 17 |
+
(data loading, augmentation, GPU training)
|
| 18 |
+
- `nnUNetTrainerBenchmark_5epochs_noDataLoading` is the same, but it doesn't do any data loading or augmentation. It
|
| 19 |
+
just presents dummy arrays to the GPU. Useful for checking pure GPU speed.
|
| 20 |
+
|
| 21 |
+
## How to run the nnU-Netv2 benchmark?
|
| 22 |
+
It's quite simple, actually. It looks just like a regular nnU-Net training.
|
| 23 |
+
|
| 24 |
+
We provide reference numbers for some of the Medical Segmentation Decathlon datasets because they are easily
|
| 25 |
+
accessible: [download here](https://drive.google.com/drive/folders/1HqEgzS8BV2c7xYNrZdEAnrHk7osJJ--2). If it needs to be
|
| 26 |
+
quick and dirty, focus on Tasks 2 and 4. Download and extract the data and convert them to the nnU-Net format with
|
| 27 |
+
`nnUNetv2_convert_MSD_dataset`.
|
| 28 |
+
Run `nnUNetv2_plan_and_preprocess` for them.
|
| 29 |
+
|
| 30 |
+
Then, for each dataset, run the following commands (only one per GPU! Or one after the other):
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
nnUNetv2_train DATSET_ID 2d 0 -tr nnUNetTrainerBenchmark_5epochs
|
| 34 |
+
nnUNetv2_train DATSET_ID 3d_fullres 0 -tr nnUNetTrainerBenchmark_5epochs
|
| 35 |
+
nnUNetv2_train DATSET_ID 2d 0 -tr nnUNetTrainerBenchmark_5epochs_noDataLoading
|
| 36 |
+
nnUNetv2_train DATSET_ID 3d_fullres 0 -tr nnUNetTrainerBenchmark_5epochs_noDataLoading
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
If you want to inspect the outcome manually, check (for example!) your
|
| 40 |
+
`nnUNet_results/DATASET_NAME/nnUNetTrainerBenchmark_5epochs__nnUNetPlans__3d_fullres/fold_0/` folder for the `benchmark_result.json` file.
|
| 41 |
+
|
| 42 |
+
Note that there can be multiple entries in this file if the benchmark was run on different GPU types, torch versions or cudnn versions!
|
| 43 |
+
|
| 44 |
+
If you want to summarize your results like we did in our [results](#results), check the
|
| 45 |
+
[summary script](../nnunetv2/batch_running/benchmarking/summarize_benchmark_results.py). Here you need to change the
|
| 46 |
+
torch version, cudnn version and dataset you want to summarize, then execute the script. You can find the exact
|
| 47 |
+
values you need to put there in one of your `benchmark_result.json` files.
|
| 48 |
+
|
| 49 |
+
## Results
|
| 50 |
+
We have tested a variety of GPUs and summarized the results in a
|
| 51 |
+
[spreadsheet](https://docs.google.com/spreadsheets/d/12Cvt_gr8XU2qWaE0XJk5jJlxMEESPxyqW0CWbQhTNNY/edit?usp=sharing).
|
| 52 |
+
Note that you can select the torch and cudnn versions at the bottom! There may be comments in this spreadsheet. Read them!
|
| 53 |
+
|
| 54 |
+
## Result interpretation
|
| 55 |
+
|
| 56 |
+
Results are shown as epoch time in seconds. Lower is better (duh). Epoch times can fluctuate between runs, so as
|
| 57 |
+
long as you are within like 5-10% of the numbers we report, everything should be dandy.
|
| 58 |
+
|
| 59 |
+
If not, here is how you can try to find the culprit!
|
| 60 |
+
|
| 61 |
+
The first thing to do is to compare the performance between the `nnUNetTrainerBenchmark_5epochs_noDataLoading` and
|
| 62 |
+
`nnUNetTrainerBenchmark_5epochs` trainers. If the difference is about the same as we report in our spreadsheet, but
|
| 63 |
+
both your numbers are worse, the problem is with your GPU:
|
| 64 |
+
|
| 65 |
+
- Are you certain you compare the correct GPU? (duh)
|
| 66 |
+
- If yes, then you might want to install PyTorch in a different way. Never `pip install torch`! Go to the
|
| 67 |
+
[PyTorch installation](https://pytorch.org/get-started/locally/) page, select the most recent cuda version your
|
| 68 |
+
system supports and only then copy and execute the correct command! Either pip or conda should work
|
| 69 |
+
- If the problem is still not fixed, we recommend you try
|
| 70 |
+
[compiling pytorch from source](https://github.com/pytorch/pytorch#from-source). It's more difficult but that's
|
| 71 |
+
how we roll here at the DKFZ (at least the cool kids here).
|
| 72 |
+
- Another thing to consider is to try exactly the same torch + cudnn version as we did in our spreadsheet.
|
| 73 |
+
Sometimes newer versions can actually degrade performance and there might be bugs from time to time. Older versions
|
| 74 |
+
are also often a lot slower!
|
| 75 |
+
- Finally, some very basic things that could impact your GPU performance:
|
| 76 |
+
- Is the GPU cooled adequately? Check the temperature with `nvidia-smi`. Hot GPUs throttle performance in order to not self-destruct
|
| 77 |
+
- Is your OS using the GPU for displaying your desktop at the same time? If so then you can expect a performance
|
| 78 |
+
penalty (I dunno like 10% !?). That's expected and OK.
|
| 79 |
+
- Are other users using the GPU as well?
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
If you see a large performance difference between `nnUNetTrainerBenchmark_5epochs_noDataLoading` (fast) and
|
| 83 |
+
`nnUNetTrainerBenchmark_5epochs` (slow) then the problem might be related to data loading and augmentation. As a
|
| 84 |
+
reminder, nnU-net does not use pre-augmented images (offline augmentation) but instead generates augmented training
|
| 85 |
+
samples on the fly during training (no, you cannot switch it to offline). This requires that your system can do partial
|
| 86 |
+
reads of the image files fast enough (SSD storage required!) and that your CPU is powerful enough to run the augmentations.
|
| 87 |
+
|
| 88 |
+
Check the following:
|
| 89 |
+
|
| 90 |
+
- [CPU bottleneck] How many CPU threads are running during the training? nnU-Net uses 12 processes for data augmentation by default.
|
| 91 |
+
If you see those 12 running constantly during training, consider increasing the number of processes used for data
|
| 92 |
+
augmentation (provided there is headroom on your CPU!). Increase the number until you see less active workers than
|
| 93 |
+
you configured (or just set the number to 32 and forget about it). You can do so by setting the `nnUNet_n_proc_DA`
|
| 94 |
+
environment variable (Linux: `export nnUNet_n_proc_DA=24`). Read [here](set_environment_variables.md) on how to do this.
|
| 95 |
+
If your CPU does not support more processes (setting more processes than your CPU has threads makes
|
| 96 |
+
no sense!) you are out of luck and in desperate need of a system upgrade!
|
| 97 |
+
- [I/O bottleneck] If you don't see 12 (or nnUNet_n_proc_DA if you set it) processes running but your training times
|
| 98 |
+
are still slow then open up `top` (sorry, Windows users. I don't know how to do this on Windows) and look at the value
|
| 99 |
+
left of 'wa' in the row that begins
|
| 100 |
+
with '%Cpu (s)'. If this is >1.0 (arbitrarily set threshold here, essentially look for unusually high 'wa'. In a
|
| 101 |
+
healthy training 'wa' will be almost 0) then your storage cannot keep up with data loading. Make sure to set
|
| 102 |
+
nnUNet_preprocessed to a folder that is located on an SSD. nvme is preferred over SATA. PCIe3 is enough. 3000MB/s
|
| 103 |
+
sequential read recommended.
|
| 104 |
+
- [funky stuff] Sometimes there is funky stuff going on, especially when batch sizes are large, files are small and
|
| 105 |
+
patch sizes are small as well. As part of the data loading process, nnU-Net needs to open and close a file for each
|
| 106 |
+
training sample. Now imagine a dataset like Dataset004_Hippocampus where for the 2d config we have a batch size of
|
| 107 |
+
366 and we run 250 iterations in <10s on an A100. That's a lotta files per second (366 * 250 / 10 = 9150 files per second).
|
| 108 |
+
Oof. If the files are on some network drive (even if it's nvme) then (probably) good night. The good news: nnU-Net
|
| 109 |
+
has got you covered: add `export nnUNet_keep_files_open=True` to your .bashrc and the problem goes away. The neat
|
| 110 |
+
part: it causes new problems if you are not allowed to have enough open files. You may have to increase the number
|
| 111 |
+
of allowed open files. `ulimit -n` gives your current limit (Linux only). It should not be something like 1024.
|
| 112 |
+
Increasing that to 65535 works well for me. See here for how to change these limits:
|
| 113 |
+
[Link](https://kupczynski.info/posts/ubuntu-18-10-ulimits/)
|
| 114 |
+
(works for Ubuntu 18, google for your OS!).
|
| 115 |
+
|
nnUNet/documentation/changelog.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# What is different in v2?
|
| 2 |
+
|
| 3 |
+
- We now support **hierarchical labels** (named regions in nnU-Net). For example, instead of training BraTS with the
|
| 4 |
+
'edema', 'necrosis' and 'enhancing tumor' labels you can directly train it on the target areas 'whole tumor',
|
| 5 |
+
'tumor core' and 'enhancing tumor'. See [here](region_based_training.md) for a detailed description + also have a look at the
|
| 6 |
+
[BraTS 2021 conversion script](../nnunetv2/dataset_conversion/Dataset137_BraTS21.py).
|
| 7 |
+
- Cross-platform support. Cuda, mps (Apple M1/M2) and of course CPU support! Simply select the device with
|
| 8 |
+
`-device` in `nnUNetv2_train` and `nnUNetv2_predict`.
|
| 9 |
+
- Unified trainer class: nnUNetTrainer. No messing around with cascaded trainer, DDP trainer, region-based trainer,
|
| 10 |
+
ignore trainer etc. All default functionality is in there!
|
| 11 |
+
- Supports more input/output data formats through ImageIO classes.
|
| 12 |
+
- I/O formats can be extended by implementing new Adapters based on `BaseReaderWriter`.
|
| 13 |
+
- The nnUNet_raw_cropped folder no longer exists -> saves disk space at no performance penalty. magic! (no jk the
|
| 14 |
+
saving of cropped npz files was really slow, so it's actually faster to crop on the fly).
|
| 15 |
+
- Preprocessed data and segmentation are stored in different files when unpacked. Seg is stored as int8 and thus
|
| 16 |
+
takes 1/4 of the disk space per pixel (and I/O throughput) as in v1.
|
| 17 |
+
- Native support for multi-GPU (DDP) TRAINING.
|
| 18 |
+
Multi-GPU INFERENCE should still be run with `CUDA_VISIBLE_DEVICES=X nnUNetv2_predict [...] -num_parts Y -part_id X`.
|
| 19 |
+
There is no cross-GPU communication in inference, so it doesn't make sense to add additional complexity with DDP.
|
| 20 |
+
- All nnU-Net functionality is now also accessible via API. Check the corresponding entry point in `setup.py` to see
|
| 21 |
+
what functions you need to call.
|
| 22 |
+
- Dataset fingerprint is now explicitly created and saved in a json file (see nnUNet_preprocessed).
|
| 23 |
+
|
| 24 |
+
- Complete overhaul of plans files (read also [this](explanation_plans_files.md):
|
| 25 |
+
- Plans are now .json and can be opened and read more easily
|
| 26 |
+
- Configurations are explicitly named ("3d_fullres" , ...)
|
| 27 |
+
- Configurations can inherit from each other to make manual experimentation easier
|
| 28 |
+
- A ton of additional functionality is now included in and can be changed through the plans, for example normalization strategy, resampling etc.
|
| 29 |
+
- Stages of the cascade are now explicitly listed in the plans. 3d_lowres has 'next_stage' (which can also be a
|
| 30 |
+
list of configurations!). 3d_cascade_fullres has a 'previous_stage' entry. By manually editing plans files you can
|
| 31 |
+
now connect anything you want, for example 2d with 3d_fullres or whatever. Be wild! (But don't create cycles!)
|
| 32 |
+
- Multiple configurations can point to the same preprocessed data folder to save disk space. Careful! Only
|
| 33 |
+
configurations that use the same spacing, resampling, normalization etc. should share a data source! By default,
|
| 34 |
+
3d_fullres and 3d_cascade_fullres share the same data
|
| 35 |
+
- Any number of configurations can be added to the plans (remember to give them a unique "data_identifier"!)
|
| 36 |
+
|
| 37 |
+
Folder structures are different and more user-friendly:
|
| 38 |
+
- nnUNet_preprocessed
|
| 39 |
+
- By default, preprocessed data is now saved as: `nnUNet_preprocessed/DATASET_NAME/PLANS_IDENTIFIER_CONFIGURATION` to clearly link them to their corresponding plans and configuration
|
| 40 |
+
- Name of the folder containing the preprocessed images can be adapted with the `data_identifier` key.
|
| 41 |
+
- nnUNet_results
|
| 42 |
+
- Results are now sorted as follows: DATASET_NAME/TRAINERCLASS__PLANSIDENTIFIER__CONFIGURATION/FOLD
|
| 43 |
+
|
| 44 |
+
## What other changes are planned and not yet implemented?
|
| 45 |
+
- Integration into MONAI (together with our friends at Nvidia)
|
| 46 |
+
- New pretrained weights for a large number of datasets (coming very soon))
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
[//]: # (- nnU-Net now also natively supports an **ignore label**. Pixels with this label will not contribute to the loss. )
|
| 50 |
+
|
| 51 |
+
[//]: # (Use this to learn from sparsely annotated data, or excluding irrelevant areas from training. Read more [here](ignore_label.md).)
|
nnUNet/documentation/competitions/AortaSeg24.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Authors: \
|
| 2 |
+
Maximilian Rokuss, Michael Baumgartner, Yannick Kirchhoff, Klaus H. Maier-Hein*, Fabian Isensee*
|
| 3 |
+
|
| 4 |
+
*: equal contribution
|
| 5 |
+
|
| 6 |
+
Author Affiliations:\
|
| 7 |
+
Division of Medical Image Computing, German Cancer Research Center (DKFZ), Heidelberg \
|
| 8 |
+
Helmholtz Imaging
|
| 9 |
+
|
| 10 |
+
# Introduction
|
| 11 |
+
|
| 12 |
+
This document describes our submission to the [AortaSeg24 Challenge](https://aortaseg24.grand-challenge.org/).
|
| 13 |
+
Our model is essentially a nnU-Net ResEnc L with modified data augmentation. We disable left/right mirroring and use the heavy data augmentation [DA5 Trainer](../../nnunetv2/training/nnUNetTrainer/variants/data_augmentation/nnUNetTrainerDA5.py). Training was performed on an A100 40GB GPU.
|
| 14 |
+
|
| 15 |
+
# Experiment Planning and Preprocessing
|
| 16 |
+
After converting the data into the [nnUNet format](../../../nnUNet/documentation/dataset_format.md) (either keep and just rename the .mha files or convert them to .nii.gz), you can run the preprocessing:
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
nnUNetv2_plan_and_preprocess -d 610 -c 3d_fullres -pl nnUNetPlannerResEncL -np 16
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
# Training
|
| 23 |
+
We train our model using:
|
| 24 |
+
|
| 25 |
+
```bash
|
| 26 |
+
nnUNetv2_train 610 3d_fullres all -p nnUNetResEncUNetLPlans -tr nnUNetTrainer_onlyMirror01_DA5
|
| 27 |
+
```
|
| 28 |
+
Models are trained from scratch. We train one model using all the images and a five fold cross validation ensemble for the submission.
|
| 29 |
+
|
| 30 |
+
We recommend to increase the number of processes used for data augmentation. Otherwise you can run into CPU bottlenecks.
|
| 31 |
+
Use `export nnUNet_n_proc_DA=32` or higher (if your system permits!).
|
| 32 |
+
|
| 33 |
+
# Inference
|
| 34 |
+
For inference you can use the default [nnUNet inference functionalities](../../../nnUNet/documentation/how_to_use_nnunet.md). Specifically, once the training is finished, run:
|
| 35 |
+
|
| 36 |
+
```bash
|
| 37 |
+
nnUNetv2_predict_from_modelfolder -i INPUT_FOLDER -o OUTPUT_FOLDER -m MODEL_FOLDER -f all
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
for the single model trained on all the data and
|
| 41 |
+
|
| 42 |
+
```bash
|
| 43 |
+
nnUNetv2_predict_from_modelfolder -i INPUT_FOLDER -o OUTPUT_FOLDER -m MODEL_FOLDER
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
for the five fold ensemble.
|
nnUNet/documentation/competitions/AutoPETII.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Look Ma, no code: fine tuning nnU-Net for the AutoPET II challenge by only adjusting its JSON plans
|
| 2 |
+
|
| 3 |
+
Please cite our paper :-*
|
| 4 |
+
|
| 5 |
+
```text
|
| 6 |
+
COMING SOON
|
| 7 |
+
```
|
| 8 |
+
|
| 9 |
+
## Intro
|
| 10 |
+
|
| 11 |
+
See the [Challenge Website](https://autopet-ii.grand-challenge.org/) for details on the challenge.
|
| 12 |
+
|
| 13 |
+
Our solution to this challenge rewuires no code changes at all. All we do is optimize nnU-Net's hyperparameters
|
| 14 |
+
(architecture, batch size, patch size) through modifying the nnUNetplans.json file.
|
| 15 |
+
|
| 16 |
+
## Prerequisites
|
| 17 |
+
Use the latest pytorch version!
|
| 18 |
+
|
| 19 |
+
We recommend you use the latest nnU-Net version as well! We ran our trainings with commit 913705f which you can try in case something doesn't work as expected:
|
| 20 |
+
`pip install git+https://github.com/MIC-DKFZ/nnUNet.git@913705f`
|
| 21 |
+
|
| 22 |
+
## How to reproduce our trainings
|
| 23 |
+
|
| 24 |
+
### Download and convert the data
|
| 25 |
+
1. Download and extract the AutoPET II dataset
|
| 26 |
+
2. Convert it to nnU-Net format by running `python nnunetv2/dataset_conversion/Dataset221_AutoPETII_2023.py FOLDER` where folder is the extracted AutoPET II dataset.
|
| 27 |
+
|
| 28 |
+
### Experiment planning and preprocessing
|
| 29 |
+
We deviate a little from the standard nnU-Net procedure because all our experiments are based on just the 3d_fullres configuration
|
| 30 |
+
|
| 31 |
+
Run the following commands:
|
| 32 |
+
- `nnUNetv2_extract_fingerprint -d 221` extracts the dataset fingerprint
|
| 33 |
+
- `nnUNetv2_plan_experiment -d 221` does the planning for the plain unet
|
| 34 |
+
- `nnUNetv2_plan_experiment -d 221 -pl ResEncUNetPlanner` does the planning for the residual encoder unet
|
| 35 |
+
- `nnUNetv2_preprocess -d 221 -c 3d_fullres` runs all the preprocessing we need
|
| 36 |
+
|
| 37 |
+
### Modification of plans files
|
| 38 |
+
Please read the [information on how to modify plans files](../explanation_plans_files.md) first!!!
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
It is easier to have everything in one plans file, so the first thing we do is transfer the ResEnc UNet to the
|
| 42 |
+
default plans file. We use the configuration inheritance feature of nnU-Net to make it use the same data as the
|
| 43 |
+
3d_fullres configuration.
|
| 44 |
+
Add the following to the 'configurations' dict in 'nnUNetPlans.json':
|
| 45 |
+
|
| 46 |
+
```json
|
| 47 |
+
"3d_fullres_resenc": {
|
| 48 |
+
"inherits_from": "3d_fullres",
|
| 49 |
+
"network_arch_class_name": "ResidualEncoderUNet",
|
| 50 |
+
"n_conv_per_stage_encoder": [
|
| 51 |
+
1,
|
| 52 |
+
3,
|
| 53 |
+
4,
|
| 54 |
+
6,
|
| 55 |
+
6,
|
| 56 |
+
6
|
| 57 |
+
],
|
| 58 |
+
"n_conv_per_stage_decoder": [
|
| 59 |
+
1,
|
| 60 |
+
1,
|
| 61 |
+
1,
|
| 62 |
+
1,
|
| 63 |
+
1
|
| 64 |
+
]
|
| 65 |
+
},
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
(these values are basically just copied from the 'nnUNetResEncUNetPlans.json' file! With everything redundant being omitted thanks to inheritance from 3d_fullres)
|
| 69 |
+
|
| 70 |
+
Now we crank up the patch and batch sizes. Add the following configurations:
|
| 71 |
+
```json
|
| 72 |
+
"3d_fullres_resenc_bs80": {
|
| 73 |
+
"inherits_from": "3d_fullres_resenc",
|
| 74 |
+
"batch_size": 80
|
| 75 |
+
},
|
| 76 |
+
"3d_fullres_resenc_192x192x192_b24": {
|
| 77 |
+
"inherits_from": "3d_fullres_resenc",
|
| 78 |
+
"patch_size": [
|
| 79 |
+
192,
|
| 80 |
+
192,
|
| 81 |
+
192
|
| 82 |
+
],
|
| 83 |
+
"batch_size": 24
|
| 84 |
+
}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
Save the file (and check for potential Syntax Errors!)
|
| 88 |
+
|
| 89 |
+
### Run trainings
|
| 90 |
+
Training each model requires 8 Nvidia A100 40GB GPUs. Expect training to run for 5-7 days. You'll need a really good
|
| 91 |
+
CPU to handle the data augmentation! 128C/256T are a must! If you have less threads available, scale down nnUNet_n_proc_DA accordingly.
|
| 92 |
+
|
| 93 |
+
```bash
|
| 94 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_bs80 0 -num_gpus 8
|
| 95 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_bs80 1 -num_gpus 8
|
| 96 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_bs80 2 -num_gpus 8
|
| 97 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_bs80 3 -num_gpus 8
|
| 98 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_bs80 4 -num_gpus 8
|
| 99 |
+
|
| 100 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_192x192x192_b24 0 -num_gpus 8
|
| 101 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_192x192x192_b24 1 -num_gpus 8
|
| 102 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_192x192x192_b24 2 -num_gpus 8
|
| 103 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_192x192x192_b24 3 -num_gpus 8
|
| 104 |
+
nnUNet_compile=T nnUNet_n_proc_DA=28 nnUNetv2_train 221 3d_fullres_resenc_192x192x192_b24 4 -num_gpus 8
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
Done!
|
| 108 |
+
|
| 109 |
+
(We also provide pretrained weights in case you don't want to invest the GPU resources, see below)
|
| 110 |
+
|
| 111 |
+
## How to make predictions with pretrained weights
|
| 112 |
+
Our final model is an ensemble of two configurations:
|
| 113 |
+
- ResEnc UNet with batch size 80
|
| 114 |
+
- ResEnc UNet with patch size 192x192x192 and batch size 24
|
| 115 |
+
|
| 116 |
+
To run inference with these models, do the following:
|
| 117 |
+
|
| 118 |
+
1. Download the pretrained model weights from [Zenodo](https://zenodo.org/record/8362371)
|
| 119 |
+
2. Install both .zip files using `nnUNetv2_install_pretrained_model_from_zip`
|
| 120 |
+
3. Make sure
|
| 121 |
+
4. Now you can run inference on new cases with `nnUNetv2_predict`:
|
| 122 |
+
- `nnUNetv2_predict -i INPUT -o OUTPUT1 -d 221 -c 3d_fullres_resenc_bs80 -f 0 1 2 3 4 -step_size 0.6 --save_probabilities`
|
| 123 |
+
- `nnUNetv2_predict -i INPUT -o OUTPUT2 -d 221 -c 3d_fullres_resenc_192x192x192_b24 -f 0 1 2 3 4 --save_probabilities`
|
| 124 |
+
- `nnUNetv2_ensemble -i OUTPUT1 OUTPUT2 -o OUTPUT_ENSEMBLE`
|
| 125 |
+
|
| 126 |
+
Note that our inference Docker omitted TTA via mirroring along the axial direction during prediction (only sagittal +
|
| 127 |
+
coronal mirroring). This was
|
| 128 |
+
done to keep the inference time below 10 minutes per image on a T4 GPU (we actually never tested whether we could
|
| 129 |
+
have left this enabled). Just leave it on! You can also leave the step_size at default for the 3d_fullres_resenc_bs80.
|
nnUNet/documentation/competitions/FLARE24/Task_1/__init__.py
ADDED
|
File without changes
|
nnUNet/documentation/competitions/FLARE24/Task_1/inference_flare_task1.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Union, Tuple
|
| 2 |
+
import argparse
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
from os.path import join
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from time import time
|
| 8 |
+
import torch
|
| 9 |
+
from torch._dynamo import OptimizedModule
|
| 10 |
+
|
| 11 |
+
from nnunetv2.utilities.label_handling.label_handling import LabelManager
|
| 12 |
+
|
| 13 |
+
from acvl_utils.cropping_and_padding.bounding_boxes import bounding_box_to_slice
|
| 14 |
+
from batchgenerators.utilities.file_and_folder_operations import load_json
|
| 15 |
+
|
| 16 |
+
import nnunetv2
|
| 17 |
+
from nnunetv2.configuration import default_num_processes
|
| 18 |
+
from nnunetv2.utilities.find_class_by_name import recursive_find_python_class
|
| 19 |
+
from nnunetv2.utilities.label_handling.label_handling import determine_num_input_channels
|
| 20 |
+
from nnunetv2.utilities.plans_handling.plans_handler import PlansManager, ConfigurationManager
|
| 21 |
+
from nnunetv2.inference.predict_from_raw_data import nnUNetPredictor
|
| 22 |
+
from nnunetv2.imageio.nibabel_reader_writer import NibabelIOWithReorient
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class FlarePredictor(nnUNetPredictor):
|
| 26 |
+
def initialize_from_trained_model_folder(self, model_training_output_dir: str,
|
| 27 |
+
use_folds: Union[Tuple[Union[int, str]], None],
|
| 28 |
+
checkpoint_name: str = 'checkpoint_final.pth'):
|
| 29 |
+
"""
|
| 30 |
+
This is used when making predictions with a trained model
|
| 31 |
+
"""
|
| 32 |
+
if use_folds is None:
|
| 33 |
+
use_folds = nnUNetPredictor.auto_detect_available_folds(model_training_output_dir, checkpoint_name)
|
| 34 |
+
|
| 35 |
+
dataset_json = load_json(join(model_training_output_dir, 'dataset.json'))
|
| 36 |
+
plans = load_json(join(model_training_output_dir, 'plans.json'))
|
| 37 |
+
plans_manager = PlansManager(plans)
|
| 38 |
+
|
| 39 |
+
if isinstance(use_folds, str):
|
| 40 |
+
use_folds = [use_folds]
|
| 41 |
+
|
| 42 |
+
parameters = []
|
| 43 |
+
for i, f in enumerate(use_folds):
|
| 44 |
+
f = int(f) if f != 'all' else f
|
| 45 |
+
checkpoint = torch.load(join(model_training_output_dir, f'fold_{f}', checkpoint_name),
|
| 46 |
+
map_location=torch.device('cpu'))
|
| 47 |
+
if i == 0:
|
| 48 |
+
trainer_name = checkpoint['trainer_name']
|
| 49 |
+
configuration_name = checkpoint['init_args']['configuration']
|
| 50 |
+
inference_allowed_mirroring_axes = checkpoint['inference_allowed_mirroring_axes'] if \
|
| 51 |
+
'inference_allowed_mirroring_axes' in checkpoint.keys() else None
|
| 52 |
+
|
| 53 |
+
parameters.append(checkpoint['network_weights'])
|
| 54 |
+
|
| 55 |
+
configuration_manager = plans_manager.get_configuration(configuration_name)
|
| 56 |
+
# restore network
|
| 57 |
+
num_input_channels = determine_num_input_channels(plans_manager, configuration_manager, dataset_json)
|
| 58 |
+
trainer_class = recursive_find_python_class(join(nnunetv2.__path__[0], "training", "nnUNetTrainer"),
|
| 59 |
+
trainer_name, 'nnunetv2.training.nnUNetTrainer')
|
| 60 |
+
if trainer_class is None:
|
| 61 |
+
raise RuntimeError(f'Unable to locate trainer class {trainer_name} in nnunetv2.training.nnUNetTrainer. '
|
| 62 |
+
f'Please place it there (in any .py file)!')
|
| 63 |
+
network = trainer_class.build_network_architecture(
|
| 64 |
+
configuration_manager.network_arch_class_name,
|
| 65 |
+
configuration_manager.network_arch_init_kwargs,
|
| 66 |
+
configuration_manager.network_arch_init_kwargs_req_import,
|
| 67 |
+
num_input_channels,
|
| 68 |
+
plans_manager.get_label_manager(dataset_json).num_segmentation_heads,
|
| 69 |
+
enable_deep_supervision=False
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
self.plans_manager = plans_manager
|
| 73 |
+
self.configuration_manager = configuration_manager
|
| 74 |
+
self.list_of_parameters = parameters
|
| 75 |
+
self.network = network
|
| 76 |
+
self.dataset_json = dataset_json
|
| 77 |
+
self.trainer_name = trainer_name
|
| 78 |
+
self.allowed_mirroring_axes = inference_allowed_mirroring_axes
|
| 79 |
+
self.label_manager = plans_manager.get_label_manager(dataset_json)
|
| 80 |
+
|
| 81 |
+
if ('nnUNet_compile' in os.environ.keys()) and (os.environ['nnUNet_compile'].lower() in ('true', '1', 't')) \
|
| 82 |
+
and not isinstance(self.network, OptimizedModule):
|
| 83 |
+
self.network = torch.compile(self.network)
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def convert_predicted_logits_to_segmentation_with_correct_shape(predicted_logits: Union[torch.Tensor, np.ndarray],
|
| 87 |
+
plans_manager: PlansManager,
|
| 88 |
+
configuration_manager: ConfigurationManager,
|
| 89 |
+
label_manager: LabelManager,
|
| 90 |
+
properties_dict: dict,
|
| 91 |
+
return_probabilities: bool = False,
|
| 92 |
+
num_threads_torch: int = default_num_processes):
|
| 93 |
+
old_threads = torch.get_num_threads()
|
| 94 |
+
torch.set_num_threads(num_threads_torch)
|
| 95 |
+
|
| 96 |
+
# resample to original shape
|
| 97 |
+
current_spacing = configuration_manager.spacing if \
|
| 98 |
+
len(configuration_manager.spacing) == \
|
| 99 |
+
len(properties_dict['shape_after_cropping_and_before_resampling']) else \
|
| 100 |
+
[properties_dict['spacing'][0], *configuration_manager.spacing]
|
| 101 |
+
predicted_logits = configuration_manager.resampling_fn_probabilities(predicted_logits,
|
| 102 |
+
properties_dict['shape_after_cropping_and_before_resampling'],
|
| 103 |
+
current_spacing,
|
| 104 |
+
properties_dict['spacing'])
|
| 105 |
+
# return value of resampling_fn_probabilities can be ndarray or Tensor but that does not matter because
|
| 106 |
+
# apply_inference_nonlin will convert to torch
|
| 107 |
+
# predicted_probabilities = label_manager.apply_inference_nonlin(predicted_logits)
|
| 108 |
+
segmentation = predicted_logits.argmax(0)
|
| 109 |
+
del predicted_logits
|
| 110 |
+
# segmentation = label_manager.convert_probabilities_to_segmentation(predicted_probabilities)
|
| 111 |
+
|
| 112 |
+
# segmentation may be torch.Tensor but we continue with numpy
|
| 113 |
+
if isinstance(segmentation, torch.Tensor):
|
| 114 |
+
segmentation = segmentation.cpu().numpy()
|
| 115 |
+
|
| 116 |
+
# put segmentation in bbox (revert cropping)
|
| 117 |
+
segmentation_reverted_cropping = np.zeros(properties_dict['shape_before_cropping'],
|
| 118 |
+
dtype=np.uint8 if len(label_manager.foreground_labels) < 255 else np.uint16)
|
| 119 |
+
slicer = bounding_box_to_slice(properties_dict['bbox_used_for_cropping'])
|
| 120 |
+
segmentation_reverted_cropping[slicer] = segmentation
|
| 121 |
+
del segmentation
|
| 122 |
+
|
| 123 |
+
# revert transpose
|
| 124 |
+
segmentation_reverted_cropping = segmentation_reverted_cropping.transpose(plans_manager.transpose_backward)
|
| 125 |
+
torch.set_num_threads(old_threads)
|
| 126 |
+
return segmentation_reverted_cropping
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def export_prediction_from_logits(predicted_array_or_file: Union[np.ndarray, torch.Tensor], properties_dict: dict,
|
| 130 |
+
configuration_manager: ConfigurationManager,
|
| 131 |
+
plans_manager: PlansManager,
|
| 132 |
+
dataset_json_dict_or_file: Union[dict, str], output_file_truncated: str,
|
| 133 |
+
save_probabilities: bool = False):
|
| 134 |
+
|
| 135 |
+
if isinstance(dataset_json_dict_or_file, str):
|
| 136 |
+
dataset_json_dict_or_file = load_json(dataset_json_dict_or_file)
|
| 137 |
+
|
| 138 |
+
label_manager = plans_manager.get_label_manager(dataset_json_dict_or_file)
|
| 139 |
+
ret = convert_predicted_logits_to_segmentation_with_correct_shape(
|
| 140 |
+
predicted_array_or_file, plans_manager, configuration_manager, label_manager, properties_dict,
|
| 141 |
+
return_probabilities=save_probabilities
|
| 142 |
+
)
|
| 143 |
+
del predicted_array_or_file
|
| 144 |
+
|
| 145 |
+
segmentation_final = ret
|
| 146 |
+
|
| 147 |
+
rw = NibabelIOWithReorient()
|
| 148 |
+
rw.write_seg(segmentation_final, output_file_truncated + dataset_json_dict_or_file['file_ending'],
|
| 149 |
+
properties_dict)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def predict_flare(input_dir, output_dir, model_folder, folds=("all",)):
|
| 153 |
+
input_dir = Path(input_dir)
|
| 154 |
+
output_dir = Path(output_dir)
|
| 155 |
+
input_files = sorted(input_dir.glob("*.nii.gz"))
|
| 156 |
+
output_files = [str(output_dir / f.name[:-12]) for f in input_files]
|
| 157 |
+
for input_file, output_file in zip(input_files, output_files):
|
| 158 |
+
print(f"Predicting {input_file.name}")
|
| 159 |
+
start = time()
|
| 160 |
+
plans_manager = PlansManager(load_json(join(model_folder, 'plans.json')))
|
| 161 |
+
configuration_manager = plans_manager.get_configuration("3d_fullres")
|
| 162 |
+
dataset_json = load_json(join(model_folder, 'dataset.json'))
|
| 163 |
+
rw = NibabelIOWithReorient()
|
| 164 |
+
image, props = rw.read_images([input_file,])
|
| 165 |
+
with torch.no_grad():
|
| 166 |
+
predictor = FlarePredictor(tile_step_size=0.5, use_mirroring=False)
|
| 167 |
+
predictor.initialize_from_trained_model_folder(model_folder, use_folds=folds)
|
| 168 |
+
preprocessor = configuration_manager.preprocessor_class(verbose=False)
|
| 169 |
+
data, _ = preprocessor.run_case_npy(image,
|
| 170 |
+
None,
|
| 171 |
+
props,
|
| 172 |
+
plans_manager,
|
| 173 |
+
configuration_manager,
|
| 174 |
+
dataset_json)
|
| 175 |
+
data = torch.from_numpy(data).to(dtype=torch.float32, memory_format=torch.contiguous_format)
|
| 176 |
+
predicted_logits = predictor.predict_logits_from_preprocessed_data(data).cpu()
|
| 177 |
+
export_prediction_from_logits(predicted_logits, props, configuration_manager,
|
| 178 |
+
plans_manager, dataset_json, output_file,
|
| 179 |
+
False)
|
| 180 |
+
print(f"Prediction time: {time() - start:.2f}s")
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
if __name__ == '__main__':
|
| 184 |
+
os.environ['nnUNet_compile'] = 'f'
|
| 185 |
+
parser = argparse.ArgumentParser()
|
| 186 |
+
parser.add_argument("-i", "--input", default="/workspace/inputs")
|
| 187 |
+
parser.add_argument("-o", "--output", default="/workspace/outputs")
|
| 188 |
+
parser.add_argument("-m", "--model", default="/opt/app/_trained_model")
|
| 189 |
+
parser.add_argument("-f", "--folds", nargs="+", default=["all"])
|
| 190 |
+
args = parser.parse_args()
|
| 191 |
+
predict_flare(args.input, args.output, args.model, args.folds)
|
nnUNet/documentation/competitions/FLARE24/Task_1/readme.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Authors: \
|
| 2 |
+
Yannick Kirchhoff, Maximilian Rouven Rokuss, Benjamin Hamm, Ashis Ravindran, Constantin Ulrich, Klaus Maier-Hein<sup>†</sup>, Fabian Isensee<sup>†</sup>
|
| 3 |
+
|
| 4 |
+
†: equal contribution
|
| 5 |
+
|
| 6 |
+
# Introduction
|
| 7 |
+
|
| 8 |
+
This document describes our contribution to [Task 1 of the FLARE24 Challenge](https://www.codabench.org/competitions/2319/).
|
| 9 |
+
Our model is basically is a default nnU-Net trained with larger batch size of 4 and 8, respectively. We submitted the batch size 8 model and an ensemble of the batch size 4 and batch size 8 models to the final test set.
|
| 10 |
+
|
| 11 |
+
# Experiment Planning and Preprocessing
|
| 12 |
+
|
| 13 |
+
Bring the downloaded data into the [nnU-Net format](../../../nnUNet/documentation/dataset_format.md) and add the dataset.json file as given here:
|
| 14 |
+
|
| 15 |
+
```json
|
| 16 |
+
{
|
| 17 |
+
"name": "Dataset301_FLARE24Task1_labeled",
|
| 18 |
+
"description": "Pan Cancer Segmentation",
|
| 19 |
+
"labels": {
|
| 20 |
+
"background": 0,
|
| 21 |
+
"lesion": 1
|
| 22 |
+
},
|
| 23 |
+
"file_ending": ".nii.gz",
|
| 24 |
+
"channel_names": {
|
| 25 |
+
"0": "CT"
|
| 26 |
+
},
|
| 27 |
+
"numTraining": 5000
|
| 28 |
+
}
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
Afterwards you can run the default nnU-Net planning and preprocessing
|
| 32 |
+
|
| 33 |
+
```bash
|
| 34 |
+
nnUNetv2_plan_and_preprocess -d 301 -c 3d_fullres
|
| 35 |
+
```
|
| 36 |
+
|
| 37 |
+
## Edit the plans files
|
| 38 |
+
|
| 39 |
+
In the generated `nnUNetPlans.json` file add the following configurations
|
| 40 |
+
|
| 41 |
+
```json
|
| 42 |
+
"3d_fullres_bs4": {
|
| 43 |
+
"inherits_from": "3d_fullres",
|
| 44 |
+
"batch_size": 4
|
| 45 |
+
},
|
| 46 |
+
"3d_fullres_bs8": {
|
| 47 |
+
"inherits_from": "3d_fullres",
|
| 48 |
+
"batch_size": 8
|
| 49 |
+
},
|
| 50 |
+
"3d_fullres_bs4u8": {
|
| 51 |
+
"inherits_from": "3d_fullres",
|
| 52 |
+
"batch_size": 48
|
| 53 |
+
}
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
Note, the last one is only used for the ensemble model during inference!
|
| 57 |
+
|
| 58 |
+
# Model training
|
| 59 |
+
|
| 60 |
+
Run the following commands to train the models with batch size 4 and 8. The large batch size helps stabilize the training despite the partial labels present in the dataset as well as handling the large number of scans in the dataset. We therefore keep the number of epochs at 1000.
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
nnUNetv2_train 301 3d_fullres_bs4 all
|
| 64 |
+
|
| 65 |
+
nnUNetv2_train 301 3d_fullres_bs8 all
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
# Inference
|
| 69 |
+
|
| 70 |
+
Our inference is optimized for efficient single scan prediction. For best performance, we strongly recommend running inference using the default `nnUNetv2_predict` command!
|
| 71 |
+
|
| 72 |
+
In order to run inference with the ensemble model you need to create a folder called `nnUNetTrainer__nnUNetPlans__3d_fullres_bs4u8` in the results folder and copy the `dataset.json`, `dataset_fingerprint.json` and `plans.json` from one of the other results folder as well as the `fold_all` from both trainings as `fold_0` and `fold_1`, respectively, into this new folder. This allows for easy ensembling of both models.
|
| 73 |
+
|
| 74 |
+
To run inference simply run the following commands with `folds` set to `all` for single model inference or `0 1` for the ensemble. `model_folder` is the folder containing the training results, i.e. for example `nnUNetTrainer__nnUNetPlans__3d_fullres_bs8`.
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
python inference_flare_task1.py -i input_folder -o output_folder -m model_folder -f folds
|
| 78 |
+
```
|
nnUNet/documentation/competitions/FLARE24/Task_2/__init__.py
ADDED
|
File without changes
|
nnUNet/documentation/competitions/FLARE24/Task_2/inference_flare_task2.py
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Union, List, Tuple
|
| 2 |
+
import argparse
|
| 3 |
+
import itertools
|
| 4 |
+
import multiprocessing
|
| 5 |
+
import numpy as np
|
| 6 |
+
import os
|
| 7 |
+
from os.path import join
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
from time import time
|
| 10 |
+
import torch
|
| 11 |
+
from torch._dynamo import OptimizedModule
|
| 12 |
+
from tqdm import tqdm
|
| 13 |
+
|
| 14 |
+
import openvino as ov
|
| 15 |
+
import openvino.properties.hint as hints
|
| 16 |
+
|
| 17 |
+
from acvl_utils.cropping_and_padding.bounding_boxes import bounding_box_to_slice
|
| 18 |
+
from acvl_utils.cropping_and_padding.padding import pad_nd_image
|
| 19 |
+
from batchgenerators.utilities.file_and_folder_operations import load_json
|
| 20 |
+
|
| 21 |
+
from nnunetv2.utilities.label_handling.label_handling import LabelManager
|
| 22 |
+
import nnunetv2
|
| 23 |
+
from nnunetv2.configuration import default_num_processes
|
| 24 |
+
from nnunetv2.inference.data_iterators import PreprocessAdapterFromNpy
|
| 25 |
+
from nnunetv2.inference.sliding_window_prediction import compute_gaussian
|
| 26 |
+
from nnunetv2.utilities.find_class_by_name import recursive_find_python_class
|
| 27 |
+
from nnunetv2.utilities.helpers import empty_cache, dummy_context
|
| 28 |
+
from nnunetv2.utilities.label_handling.label_handling import determine_num_input_channels
|
| 29 |
+
from nnunetv2.utilities.plans_handling.plans_handler import PlansManager, ConfigurationManager
|
| 30 |
+
from nnunetv2.inference.predict_from_raw_data import nnUNetPredictor
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
torch.set_num_threads(multiprocessing.cpu_count())
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class FlarePredictor(nnUNetPredictor):
|
| 37 |
+
def __init__(self,
|
| 38 |
+
tile_step_size: float = 0.5,
|
| 39 |
+
use_gaussian: bool = True,
|
| 40 |
+
use_mirroring: bool = True,
|
| 41 |
+
perform_everything_on_device: bool = False,
|
| 42 |
+
device: torch.device = torch.device('cpu'),
|
| 43 |
+
verbose: bool = False,
|
| 44 |
+
verbose_preprocessing: bool = False,
|
| 45 |
+
allow_tqdm: bool = True,
|
| 46 |
+
):
|
| 47 |
+
super().__init__(tile_step_size, use_gaussian, use_mirroring, perform_everything_on_device, device, verbose,
|
| 48 |
+
verbose_preprocessing, allow_tqdm)
|
| 49 |
+
if self.device == torch.device('cuda') or self.device == 'cuda':
|
| 50 |
+
raise RuntimeError('CUDA is not supported for this task')
|
| 51 |
+
|
| 52 |
+
def initialize_from_trained_model_folder(self, model_training_output_dir: str,
|
| 53 |
+
use_folds: Union[Tuple[Union[int, str]], None],
|
| 54 |
+
checkpoint_name: str = 'checkpoint_final.pth',
|
| 55 |
+
save_model: bool = True):
|
| 56 |
+
"""
|
| 57 |
+
This is used when making predictions with a trained model
|
| 58 |
+
"""
|
| 59 |
+
if use_folds is None:
|
| 60 |
+
use_folds = nnUNetPredictor.auto_detect_available_folds(model_training_output_dir, checkpoint_name)
|
| 61 |
+
|
| 62 |
+
dataset_json = load_json(join(model_training_output_dir, 'dataset.json'))
|
| 63 |
+
plans = load_json(join(model_training_output_dir, 'plans.json'))
|
| 64 |
+
plans_manager = PlansManager(plans)
|
| 65 |
+
|
| 66 |
+
if isinstance(use_folds, str):
|
| 67 |
+
use_folds = [use_folds]
|
| 68 |
+
assert len(use_folds) == 1, 'Only one fold is supported for this task'
|
| 69 |
+
|
| 70 |
+
parameters = []
|
| 71 |
+
for i, f in enumerate(use_folds):
|
| 72 |
+
f = int(f) if f != 'all' else f
|
| 73 |
+
checkpoint = torch.load(join(model_training_output_dir, f'fold_{f}', checkpoint_name),
|
| 74 |
+
map_location=torch.device('cpu'))
|
| 75 |
+
if i == 0:
|
| 76 |
+
trainer_name = checkpoint['trainer_name']
|
| 77 |
+
configuration_name = checkpoint['init_args']['configuration']
|
| 78 |
+
inference_allowed_mirroring_axes = checkpoint['inference_allowed_mirroring_axes'] if \
|
| 79 |
+
'inference_allowed_mirroring_axes' in checkpoint.keys() else None
|
| 80 |
+
|
| 81 |
+
if save_model:
|
| 82 |
+
parameters.append(checkpoint['network_weights'])
|
| 83 |
+
|
| 84 |
+
configuration_manager = plans_manager.get_configuration(configuration_name)
|
| 85 |
+
if save_model:
|
| 86 |
+
num_input_channels = determine_num_input_channels(plans_manager, configuration_manager, dataset_json)
|
| 87 |
+
trainer_class = recursive_find_python_class(join(nnunetv2.__path__[0], "training", "nnUNetTrainer"),
|
| 88 |
+
trainer_name, 'nnunetv2.training.nnUNetTrainer')
|
| 89 |
+
if trainer_class is None:
|
| 90 |
+
raise RuntimeError(f'Unable to locate trainer class {trainer_name} in nnunetv2.training.nnUNetTrainer. '
|
| 91 |
+
f'Please place it there (in any .py file)!')
|
| 92 |
+
network = trainer_class.build_network_architecture(
|
| 93 |
+
configuration_manager.network_arch_class_name,
|
| 94 |
+
configuration_manager.network_arch_init_kwargs,
|
| 95 |
+
configuration_manager.network_arch_init_kwargs_req_import,
|
| 96 |
+
num_input_channels,
|
| 97 |
+
plans_manager.get_label_manager(dataset_json).num_segmentation_heads,
|
| 98 |
+
enable_deep_supervision=False
|
| 99 |
+
)
|
| 100 |
+
self.network = network
|
| 101 |
+
self.allowed_mirroring_axes = inference_allowed_mirroring_axes
|
| 102 |
+
|
| 103 |
+
self.plans_manager = plans_manager
|
| 104 |
+
self.configuration_manager = configuration_manager
|
| 105 |
+
self.dataset_json = dataset_json
|
| 106 |
+
self.label_manager = plans_manager.get_label_manager(dataset_json)
|
| 107 |
+
|
| 108 |
+
if save_model:
|
| 109 |
+
if not isinstance(self.network, OptimizedModule):
|
| 110 |
+
self.network.load_state_dict(parameters[0])
|
| 111 |
+
else:
|
| 112 |
+
self.network._orig_mod.load_state_dict(parameters[0])
|
| 113 |
+
self.network.eval()
|
| 114 |
+
|
| 115 |
+
config = {hints.performance_mode: hints.PerformanceMode.LATENCY,
|
| 116 |
+
hints.enable_cpu_pinning(): True,
|
| 117 |
+
}
|
| 118 |
+
core = ov.Core()
|
| 119 |
+
core.set_property(
|
| 120 |
+
"CPU",
|
| 121 |
+
{hints.execution_mode: hints.ExecutionMode.PERFORMANCE},
|
| 122 |
+
)
|
| 123 |
+
if save_model:
|
| 124 |
+
input_tensor = torch.randn(1, num_input_channels, *configuration_manager.patch_size, requires_grad=False)
|
| 125 |
+
ov_model = ov.convert_model(self.network, example_input=input_tensor)
|
| 126 |
+
ov.save_model(ov_model, f"{model_training_output_dir}/model.xml")
|
| 127 |
+
import sys
|
| 128 |
+
sys.exit(0)
|
| 129 |
+
ov_model = core.read_model(f"{model_training_output_dir}/model.xml")
|
| 130 |
+
self.network = core.compile_model(ov_model, "CPU", config= config)
|
| 131 |
+
|
| 132 |
+
def predict_from_files(self,
|
| 133 |
+
list_of_lists_or_source_folder: Union[str, List[List[str]]],
|
| 134 |
+
output_folder_or_list_of_truncated_output_files: Union[str, None, List[str]],
|
| 135 |
+
save_probabilities: bool = False,
|
| 136 |
+
overwrite: bool = True,
|
| 137 |
+
num_processes_preprocessing: int = 1,
|
| 138 |
+
num_processes_segmentation_export: int = 1,
|
| 139 |
+
folder_with_segs_from_prev_stage: str = None,
|
| 140 |
+
num_parts: int = 1,
|
| 141 |
+
part_id: int = 0):
|
| 142 |
+
"""
|
| 143 |
+
This is nnU-Net's default function for making predictions. It works best for batch predictions
|
| 144 |
+
(predicting many images at once).
|
| 145 |
+
"""
|
| 146 |
+
|
| 147 |
+
list_of_lists_or_source_folder, output_filename_truncated, seg_from_prev_stage_files = \
|
| 148 |
+
self._manage_input_and_output_lists(list_of_lists_or_source_folder,
|
| 149 |
+
output_folder_or_list_of_truncated_output_files,
|
| 150 |
+
folder_with_segs_from_prev_stage, overwrite, part_id, num_parts,
|
| 151 |
+
save_probabilities)
|
| 152 |
+
if len(list_of_lists_or_source_folder) == 0:
|
| 153 |
+
return
|
| 154 |
+
|
| 155 |
+
data_iterator = self._internal_get_data_iterator_from_lists_of_filenames(list_of_lists_or_source_folder,
|
| 156 |
+
seg_from_prev_stage_files,
|
| 157 |
+
output_filename_truncated,
|
| 158 |
+
num_processes_preprocessing)
|
| 159 |
+
|
| 160 |
+
return self.predict_from_data_iterator(data_iterator, save_probabilities, num_processes_segmentation_export)
|
| 161 |
+
|
| 162 |
+
def _internal_maybe_mirror_and_predict(self, x: torch.Tensor) -> torch.Tensor:
|
| 163 |
+
mirror_axes = self.allowed_mirroring_axes if self.use_mirroring else None
|
| 164 |
+
if self.use_openvino:
|
| 165 |
+
prediction = torch.from_numpy(self.network(x)[0])
|
| 166 |
+
else:
|
| 167 |
+
prediction = self.network(x)
|
| 168 |
+
|
| 169 |
+
if mirror_axes is not None:
|
| 170 |
+
assert max(mirror_axes) <= x.ndim - 3, 'mirror_axes does not match the dimension of the input!'
|
| 171 |
+
|
| 172 |
+
mirror_axes = [m + 2 for m in mirror_axes]
|
| 173 |
+
axes_combinations = [
|
| 174 |
+
c for i in range(len(mirror_axes)) for c in itertools.combinations(mirror_axes, i + 1)
|
| 175 |
+
]
|
| 176 |
+
for axes in axes_combinations:
|
| 177 |
+
if not self.is_openvino:
|
| 178 |
+
prediction += torch.flip(self.network(torch.flip(x, axes)), axes)
|
| 179 |
+
else:
|
| 180 |
+
temp_pred = torch.from_numpy(self.network(torch.flip(x, axes))[0])
|
| 181 |
+
prediction += torch.flip(temp_pred, axes)
|
| 182 |
+
|
| 183 |
+
prediction /= (len(axes_combinations) + 1)
|
| 184 |
+
return prediction
|
| 185 |
+
|
| 186 |
+
def _internal_predict_sliding_window_return_logits(self,
|
| 187 |
+
data: torch.Tensor,
|
| 188 |
+
slicers,
|
| 189 |
+
do_on_device: bool = True,
|
| 190 |
+
):
|
| 191 |
+
predicted_logits = n_predictions = prediction = gaussian = workon = None
|
| 192 |
+
results_device = self.device if do_on_device else torch.device('cpu')
|
| 193 |
+
|
| 194 |
+
try:
|
| 195 |
+
empty_cache(self.device)
|
| 196 |
+
|
| 197 |
+
# move data to device
|
| 198 |
+
if self.verbose:
|
| 199 |
+
print(f'move image to device {results_device}')
|
| 200 |
+
data = data.to(results_device)
|
| 201 |
+
|
| 202 |
+
# preallocate arrays
|
| 203 |
+
if self.verbose:
|
| 204 |
+
print(f'preallocating results arrays on device {results_device}')
|
| 205 |
+
predicted_logits = torch.zeros((self.label_manager.num_segmentation_heads, *data.shape[1:]),
|
| 206 |
+
dtype=torch.half,
|
| 207 |
+
device=results_device)
|
| 208 |
+
n_predictions = torch.zeros(data.shape[1:], dtype=torch.half, device=results_device)
|
| 209 |
+
|
| 210 |
+
if self.use_gaussian:
|
| 211 |
+
gaussian = compute_gaussian(tuple(self.configuration_manager.patch_size), sigma_scale=1. / 8,
|
| 212 |
+
value_scaling_factor=10,
|
| 213 |
+
device=results_device)
|
| 214 |
+
else:
|
| 215 |
+
gaussian = 1
|
| 216 |
+
|
| 217 |
+
if not self.allow_tqdm and self.verbose:
|
| 218 |
+
print(f'running prediction: {len(slicers)} steps')
|
| 219 |
+
for sl in tqdm(slicers, disable=not self.allow_tqdm):
|
| 220 |
+
workon = data[sl][None]
|
| 221 |
+
workon = workon.to(self.device)
|
| 222 |
+
|
| 223 |
+
prediction = self._internal_maybe_mirror_and_predict(workon)[0].to(results_device)
|
| 224 |
+
|
| 225 |
+
if self.use_gaussian:
|
| 226 |
+
prediction *= gaussian
|
| 227 |
+
predicted_logits[sl] += prediction
|
| 228 |
+
n_predictions[sl[1:]] += gaussian
|
| 229 |
+
|
| 230 |
+
predicted_logits /= n_predictions
|
| 231 |
+
# check for infs
|
| 232 |
+
if torch.any(torch.isinf(predicted_logits)):
|
| 233 |
+
raise RuntimeError('Encountered inf in predicted array. Aborting... If this problem persists, '
|
| 234 |
+
'reduce value_scaling_factor in compute_gaussian or increase the dtype of '
|
| 235 |
+
'predicted_logits to fp32')
|
| 236 |
+
except Exception as e:
|
| 237 |
+
del predicted_logits, n_predictions, prediction, gaussian, workon
|
| 238 |
+
empty_cache(self.device)
|
| 239 |
+
empty_cache(results_device)
|
| 240 |
+
raise e
|
| 241 |
+
return predicted_logits
|
| 242 |
+
|
| 243 |
+
def predict_logits_from_preprocessed_data(self, data: torch.Tensor) -> torch.Tensor:
|
| 244 |
+
"""
|
| 245 |
+
IMPORTANT! IF YOU ARE RUNNING THE CASCADE, THE SEGMENTATION FROM THE PREVIOUS STAGE MUST ALREADY BE STACKED ON
|
| 246 |
+
TOP OF THE IMAGE AS ONE-HOT REPRESENTATION! SEE PreprocessAdapter ON HOW THIS SHOULD BE DONE!
|
| 247 |
+
|
| 248 |
+
RETURNED LOGITS HAVE THE SHAPE OF THE INPUT. THEY MUST BE CONVERTED BACK TO THE ORIGINAL IMAGE SIZE.
|
| 249 |
+
SEE convert_predicted_logits_to_segmentation_with_correct_shape
|
| 250 |
+
"""
|
| 251 |
+
prediction = None
|
| 252 |
+
|
| 253 |
+
if not self.use_openvino:
|
| 254 |
+
for params in self.list_of_parameters:
|
| 255 |
+
|
| 256 |
+
# messing with state dict names...
|
| 257 |
+
if not isinstance(self.network, OptimizedModule):
|
| 258 |
+
self.network.load_state_dict(params)
|
| 259 |
+
else:
|
| 260 |
+
self.network._orig_mod.load_state_dict(params)
|
| 261 |
+
|
| 262 |
+
# why not leave prediction on device if perform_everything_on_device? Because this may cause the
|
| 263 |
+
# second iteration to crash due to OOM. Grabbing that with try except cause way more bloated code than
|
| 264 |
+
# this actually saves computation time
|
| 265 |
+
if prediction is None:
|
| 266 |
+
prediction = self.predict_sliding_window_return_logits(data).to('cpu')
|
| 267 |
+
else:
|
| 268 |
+
prediction += self.predict_sliding_window_return_logits(data).to('cpu')
|
| 269 |
+
|
| 270 |
+
if len(self.list_of_parameters) > 1:
|
| 271 |
+
prediction /= len(self.list_of_parameters)
|
| 272 |
+
|
| 273 |
+
else:
|
| 274 |
+
if prediction is None:
|
| 275 |
+
prediction = self.predict_sliding_window_return_logits(data)
|
| 276 |
+
else:
|
| 277 |
+
prediction += self.predict_sliding_window_return_logits(data)
|
| 278 |
+
|
| 279 |
+
if self.verbose: print('Prediction done')
|
| 280 |
+
return prediction
|
| 281 |
+
|
| 282 |
+
@torch.inference_mode()
|
| 283 |
+
def predict_sliding_window_return_logits(self, input_image: torch.Tensor) \
|
| 284 |
+
-> Union[np.ndarray, torch.Tensor]:
|
| 285 |
+
assert isinstance(input_image, torch.Tensor)
|
| 286 |
+
if self.device not in [torch.device('cpu'), 'cpu']:
|
| 287 |
+
self.network = self.network.to(self.device)
|
| 288 |
+
self.network.eval()
|
| 289 |
+
|
| 290 |
+
empty_cache(self.device)
|
| 291 |
+
|
| 292 |
+
# Autocast can be annoying
|
| 293 |
+
# If the device_type is 'cpu' then it's slow as heck on some CPUs (no auto bfloat16 support detection)
|
| 294 |
+
# and needs to be disabled.
|
| 295 |
+
# If the device_type is 'mps' then it will complain that mps is not implemented, even if enabled=False
|
| 296 |
+
# is set. Whyyyyyyy. (this is why we don't make use of enabled=False)
|
| 297 |
+
# So autocast will only be active if we have a cuda device.
|
| 298 |
+
with torch.autocast(self.device.type, enabled=True) if self.device.type == 'cuda' else dummy_context():
|
| 299 |
+
assert input_image.ndim == 4, 'input_image must be a 4D np.ndarray or torch.Tensor (c, x, y, z)'
|
| 300 |
+
|
| 301 |
+
if self.verbose:
|
| 302 |
+
print(f'Input shape: {input_image.shape}')
|
| 303 |
+
print("step_size:", self.tile_step_size)
|
| 304 |
+
print("mirror_axes:", self.allowed_mirroring_axes if self.use_mirroring else None)
|
| 305 |
+
|
| 306 |
+
# if input_image is smaller than tile_size we need to pad it to tile_size.
|
| 307 |
+
data, slicer_revert_padding = pad_nd_image(input_image, self.configuration_manager.patch_size,
|
| 308 |
+
'constant', {'value': 0}, True,
|
| 309 |
+
None)
|
| 310 |
+
|
| 311 |
+
slicers = self._internal_get_sliding_window_slicers(data.shape[1:])
|
| 312 |
+
|
| 313 |
+
if self.perform_everything_on_device and self.device != 'cpu':
|
| 314 |
+
# we need to try except here because we can run OOM in which case we need to fall back to CPU as a results device
|
| 315 |
+
try:
|
| 316 |
+
predicted_logits = self._internal_predict_sliding_window_return_logits(data, slicers,
|
| 317 |
+
self.perform_everything_on_device)
|
| 318 |
+
except RuntimeError:
|
| 319 |
+
print(
|
| 320 |
+
'Prediction on device was unsuccessful, probably due to a lack of memory. Moving results arrays to CPU')
|
| 321 |
+
empty_cache(self.device)
|
| 322 |
+
predicted_logits = self._internal_predict_sliding_window_return_logits(data, slicers, False)
|
| 323 |
+
else:
|
| 324 |
+
predicted_logits = self._internal_predict_sliding_window_return_logits(data, slicers,
|
| 325 |
+
self.perform_everything_on_device)
|
| 326 |
+
|
| 327 |
+
empty_cache(self.device)
|
| 328 |
+
# revert padding
|
| 329 |
+
predicted_logits = predicted_logits[(slice(None), *slicer_revert_padding[1:])]
|
| 330 |
+
return predicted_logits
|
| 331 |
+
|
| 332 |
+
def convert_predicted_logits_to_segmentation_with_correct_shape(self, predicted_logits: Union[torch.Tensor, np.ndarray],
|
| 333 |
+
plans_manager: PlansManager,
|
| 334 |
+
configuration_manager: ConfigurationManager,
|
| 335 |
+
label_manager: LabelManager,
|
| 336 |
+
properties_dict: dict,
|
| 337 |
+
return_probabilities: bool = False,
|
| 338 |
+
num_threads_torch: int = default_num_processes):
|
| 339 |
+
|
| 340 |
+
# resample to original shape
|
| 341 |
+
current_spacing = configuration_manager.spacing if \
|
| 342 |
+
len(configuration_manager.spacing) == \
|
| 343 |
+
len(properties_dict['shape_after_cropping_and_before_resampling']) else \
|
| 344 |
+
[properties_dict['spacing'][0], *configuration_manager.spacing]
|
| 345 |
+
predicted_logits = configuration_manager.resampling_fn_probabilities(predicted_logits,
|
| 346 |
+
properties_dict['shape_after_cropping_and_before_resampling'],
|
| 347 |
+
current_spacing,
|
| 348 |
+
properties_dict['spacing'])
|
| 349 |
+
segmentation = predicted_logits.argmax(0)
|
| 350 |
+
del predicted_logits
|
| 351 |
+
|
| 352 |
+
# segmentation may be torch.Tensor but we continue with numpy
|
| 353 |
+
if isinstance(segmentation, torch.Tensor):
|
| 354 |
+
segmentation = segmentation.cpu().numpy()
|
| 355 |
+
|
| 356 |
+
# put segmentation in bbox (revert cropping)
|
| 357 |
+
segmentation_reverted_cropping = np.zeros(properties_dict['shape_before_cropping'],
|
| 358 |
+
dtype=np.uint8 if len(label_manager.foreground_labels) < 255 else np.uint16)
|
| 359 |
+
slicer = bounding_box_to_slice(properties_dict['bbox_used_for_cropping'])
|
| 360 |
+
segmentation_reverted_cropping[slicer] = segmentation
|
| 361 |
+
del segmentation
|
| 362 |
+
|
| 363 |
+
# revert transpose
|
| 364 |
+
segmentation_reverted_cropping = segmentation_reverted_cropping.transpose(plans_manager.transpose_backward)
|
| 365 |
+
return segmentation_reverted_cropping
|
| 366 |
+
|
| 367 |
+
def export_prediction_from_logits(self, predicted_array_or_file: Union[np.ndarray, torch.Tensor], properties_dict: dict,
|
| 368 |
+
configuration_manager: ConfigurationManager,
|
| 369 |
+
plans_manager: PlansManager,
|
| 370 |
+
dataset_json_dict_or_file: Union[dict, str], output_file_truncated: str,
|
| 371 |
+
save_probabilities: bool = False):
|
| 372 |
+
|
| 373 |
+
if isinstance(dataset_json_dict_or_file, str):
|
| 374 |
+
dataset_json_dict_or_file = load_json(dataset_json_dict_or_file)
|
| 375 |
+
|
| 376 |
+
label_manager = plans_manager.get_label_manager(dataset_json_dict_or_file)
|
| 377 |
+
ret = self.convert_predicted_logits_to_segmentation_with_correct_shape(
|
| 378 |
+
predicted_array_or_file, plans_manager, configuration_manager, label_manager, properties_dict,
|
| 379 |
+
return_probabilities=save_probabilities
|
| 380 |
+
)
|
| 381 |
+
del predicted_array_or_file
|
| 382 |
+
|
| 383 |
+
segmentation_final = ret
|
| 384 |
+
|
| 385 |
+
rw = plans_manager.image_reader_writer_class()
|
| 386 |
+
rw.write_seg(segmentation_final, output_file_truncated + dataset_json_dict_or_file['file_ending'],
|
| 387 |
+
properties_dict)
|
| 388 |
+
|
| 389 |
+
def predict_single_npy_array(self, input_image: np.ndarray, image_properties: dict,
|
| 390 |
+
segmentation_previous_stage: np.ndarray = None,
|
| 391 |
+
output_file_truncated: str = None,
|
| 392 |
+
save_or_return_probabilities: bool = False):
|
| 393 |
+
"""
|
| 394 |
+
WARNING: SLOW. ONLY USE THIS IF YOU CANNOT GIVE NNUNET MULTIPLE IMAGES AT ONCE FOR SOME REASON.
|
| 395 |
+
|
| 396 |
+
|
| 397 |
+
input_image: Make sure to load the image in the way nnU-Net expects! nnU-Net is trained on a certain axis
|
| 398 |
+
ordering which cannot be disturbed in inference,
|
| 399 |
+
otherwise you will get bad results. The easiest way to achieve that is to use the same I/O class
|
| 400 |
+
for loading images as was used during nnU-Net preprocessing! You can find that class in your
|
| 401 |
+
plans.json file under the key "image_reader_writer". If you decide to freestyle, know that the
|
| 402 |
+
default axis ordering for medical images is the one from SimpleITK. If you load with nibabel,
|
| 403 |
+
you need to transpose your axes AND your spacing from [x,y,z] to [z,y,x]!
|
| 404 |
+
image_properties must only have a 'spacing' key!
|
| 405 |
+
"""
|
| 406 |
+
ppa = PreprocessAdapterFromNpy([input_image], [segmentation_previous_stage], [image_properties],
|
| 407 |
+
[output_file_truncated],
|
| 408 |
+
self.plans_manager, self.dataset_json, self.configuration_manager,
|
| 409 |
+
num_threads_in_multithreaded=1, verbose=self.verbose)
|
| 410 |
+
if self.verbose:
|
| 411 |
+
print('preprocessing')
|
| 412 |
+
dct = next(ppa)
|
| 413 |
+
|
| 414 |
+
if self.verbose:
|
| 415 |
+
print('predicting')
|
| 416 |
+
predicted_logits = self.predict_logits_from_preprocessed_data(dct['data']).cpu()
|
| 417 |
+
|
| 418 |
+
if self.verbose:
|
| 419 |
+
print('resampling to original shape')
|
| 420 |
+
self.export_prediction_from_logits(predicted_logits, dct['data_properties'], self.configuration_manager,
|
| 421 |
+
self.plans_manager, self.dataset_json, output_file_truncated,
|
| 422 |
+
save_or_return_probabilities)
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
def predict_flare(input_dir, output_dir, model_folder, save_model):
|
| 426 |
+
input_dir = Path(input_dir)
|
| 427 |
+
output_dir = Path(output_dir)
|
| 428 |
+
output_dir.mkdir(exist_ok=True, parents=True)
|
| 429 |
+
input_files = list(input_dir.glob("*.nii.gz"))
|
| 430 |
+
output_files = [str(output_dir / f.name[:-12]) for f in input_files]
|
| 431 |
+
for input_file, output_file in zip(input_files, output_files):
|
| 432 |
+
print(f"Predicting {input_file.name}")
|
| 433 |
+
start = time()
|
| 434 |
+
predictor = FlarePredictor(tile_step_size=0.5, use_mirroring=False, device=torch.device("cpu"))
|
| 435 |
+
predictor.initialize_from_trained_model_folder(model_folder, ("all",), save_model=save_model)
|
| 436 |
+
rw = predictor.plans_manager.image_reader_writer_class()
|
| 437 |
+
image, props = rw.read_images([input_file,])
|
| 438 |
+
_ = predictor.predict_single_npy_array(image, props, None, output_file, False)
|
| 439 |
+
print(f"Prediction time: {time() - start:.2f}s")
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
if __name__ == '__main__':
|
| 443 |
+
parser = argparse.ArgumentParser()
|
| 444 |
+
parser.add_argument("-i", "--input", default="/workspace/inputs")
|
| 445 |
+
parser.add_argument("-o", "--output", default="/workspace/outputs")
|
| 446 |
+
parser.add_argument("-m", "--model", default="/opt/app/_trained_model")
|
| 447 |
+
parser.add_argument("-save_model", action="store_true")
|
| 448 |
+
args = parser.parse_args()
|
| 449 |
+
predict_flare(args.input, args.output, args.model, args.save_model)
|
nnUNet/documentation/competitions/FLARE24/Task_2/readme.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Authors: \
|
| 2 |
+
Yannick Kirchhoff*, Ashis Ravindran*, Maximilian Rouven Rokuss, Benjamin Hamm, Constantin Ulrich, Klaus Maier-Hein<sup>†</sup>, Fabian Isensee<sup>†</sup>
|
| 3 |
+
|
| 4 |
+
*: equal contribution \
|
| 5 |
+
†: equal contribution
|
| 6 |
+
|
| 7 |
+
# Introduction
|
| 8 |
+
|
| 9 |
+
This document describes our contribution to [Task 2 of the FLARE24 Challenge](https://www.codabench.org/competitions/2320/).
|
| 10 |
+
Our model is basically is a default nnU-Net with a custom low resolution setting and OpenVINO optimizations for faster CPU inference.
|
| 11 |
+
|
| 12 |
+
# Experiment Planning and Preprocessing
|
| 13 |
+
|
| 14 |
+
Bring the downloaded data into the [nnU-Net format](../../../nnUNet/documentation/dataset_format.md) and add the dataset.json file as given here:
|
| 15 |
+
|
| 16 |
+
```json
|
| 17 |
+
{
|
| 18 |
+
"name": "Dataset311_FLARE24Task2_labeled",
|
| 19 |
+
"description": "Abdominal Organ Segmentation",
|
| 20 |
+
"labels": {
|
| 21 |
+
"background": 0,
|
| 22 |
+
"liver": 1,
|
| 23 |
+
"right kidney": 2,
|
| 24 |
+
"spleen": 3,
|
| 25 |
+
"pancreas": 4,
|
| 26 |
+
"aorta": 5,
|
| 27 |
+
"ivc": 6,
|
| 28 |
+
"rag": 7,
|
| 29 |
+
"lag": 8,
|
| 30 |
+
"gallbladder": 9,
|
| 31 |
+
"esophagus": 10,
|
| 32 |
+
"stomach": 11,
|
| 33 |
+
"duodenum": 12,
|
| 34 |
+
"left kidney": 13
|
| 35 |
+
},
|
| 36 |
+
"file_ending": ".nii.gz",
|
| 37 |
+
"channel_names": {
|
| 38 |
+
"0": "CT"
|
| 39 |
+
},
|
| 40 |
+
"overwrite_image_reader_writer": "NibabelIOWithReorient",
|
| 41 |
+
"numTraining": 50
|
| 42 |
+
}
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
Afterwards you can run the default nnU-Net planning and preprocessing
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
nnUNetv2_plan_and_preprocess -d 311 -c 3d_fullres
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
## Edit the plans files
|
| 52 |
+
|
| 53 |
+
The generated `nnUNetPlans.json` file needs to be edited to incorporate the custom low resolution setting.
|
| 54 |
+
|
| 55 |
+
```json
|
| 56 |
+
"3d_halfres": {
|
| 57 |
+
"inherits_from": "3d_fullres",
|
| 58 |
+
"data_identifier": "nnUNetPlans_3d_halfres",
|
| 59 |
+
"spacing": [
|
| 60 |
+
5,
|
| 61 |
+
1.6,
|
| 62 |
+
1.6
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
"3d_halfiso": {
|
| 66 |
+
"inherits_from": "3d_fullres",
|
| 67 |
+
"data_identifier": "nnUNetPlans_3d_halfiso",
|
| 68 |
+
"spacing": [
|
| 69 |
+
2.5,
|
| 70 |
+
2.5,
|
| 71 |
+
2.5
|
| 72 |
+
]
|
| 73 |
+
},
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
`3d_halfres` is a configuration with exactly half resolution, used as an ablation of our submission, `3d_halfiso` is the isotropic configuration we submitted as a final solution.
|
| 77 |
+
|
| 78 |
+
# Model training
|
| 79 |
+
|
| 80 |
+
Run one of the following commands to train the respective configurations. `3d_halfiso` yielded significantly better results in our experiments as well as on the final test set and is the recommended configuration.
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
nnUNetv2_train 311 3d_halfres all
|
| 84 |
+
|
| 85 |
+
nnUNetv2_train 311 3d_halfiso all
|
| 86 |
+
```
|
| 87 |
+
|
| 88 |
+
# Inference
|
| 89 |
+
|
| 90 |
+
Our inference is optimized for efficient single scan prediction. For best performance, we strongly recommend running inference using the default `nnUNetv2_predict` command!
|
| 91 |
+
|
| 92 |
+
Inference using the provided script requires OpenVINO, which can easily be installed via
|
| 93 |
+
|
| 94 |
+
```bash
|
| 95 |
+
pip install openvino
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
To run inference simply run the following commands. `model_folder` is the folder containing the training results, i.e. for example `nnUNetTrainer__nnUNetPlans__3d_halfiso`. `-save_model` needs to be set to precompile the model once using OpenVINO. If not precompiled model exists, the inference script will fail!
|
| 99 |
+
|
| 100 |
+
```bash
|
| 101 |
+
python inference_flare_task1.py -i input_folder -o output_folder -m model_folder [-save_model]
|
| 102 |
+
```
|
nnUNet/documentation/competitions/FLARE24/__init__.py
ADDED
|
File without changes
|
nnUNet/documentation/competitions/Toothfairy2/__init__.py
ADDED
|
File without changes
|
nnUNet/documentation/competitions/Toothfairy2/inference_script_semseg_only_customInf2.py
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import gc
|
| 3 |
+
import os
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from queue import Queue
|
| 6 |
+
from threading import Thread
|
| 7 |
+
from typing import Union, Tuple
|
| 8 |
+
|
| 9 |
+
import nnunetv2
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from acvl_utils.cropping_and_padding.bounding_boxes import bounding_box_to_slice
|
| 13 |
+
from acvl_utils.cropping_and_padding.padding import pad_nd_image
|
| 14 |
+
from batchgenerators.utilities.file_and_folder_operations import load_json, join
|
| 15 |
+
from nnunetv2.imageio.simpleitk_reader_writer import SimpleITKIO
|
| 16 |
+
from nnunetv2.inference.predict_from_raw_data import nnUNetPredictor
|
| 17 |
+
from nnunetv2.inference.sliding_window_prediction import compute_gaussian
|
| 18 |
+
from nnunetv2.utilities.find_class_by_name import recursive_find_python_class
|
| 19 |
+
from nnunetv2.utilities.helpers import empty_cache, dummy_context
|
| 20 |
+
from nnunetv2.utilities.label_handling.label_handling import determine_num_input_channels
|
| 21 |
+
from nnunetv2.utilities.plans_handling.plans_handler import PlansManager
|
| 22 |
+
from torch._dynamo import OptimizedModule
|
| 23 |
+
from torch.backends import cudnn
|
| 24 |
+
from tqdm import tqdm
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class CustomPredictor(nnUNetPredictor):
|
| 28 |
+
def predict_single_npy_array(self, input_image: np.ndarray, image_properties: dict,
|
| 29 |
+
segmentation_previous_stage: np.ndarray = None):
|
| 30 |
+
torch.set_num_threads(7)
|
| 31 |
+
with torch.no_grad():
|
| 32 |
+
self.network = self.network.to(self.device)
|
| 33 |
+
self.network.eval()
|
| 34 |
+
|
| 35 |
+
if self.verbose:
|
| 36 |
+
print('preprocessing')
|
| 37 |
+
preprocessor = self.configuration_manager.preprocessor_class(verbose=self.verbose)
|
| 38 |
+
data, _, image_properties = preprocessor.run_case_npy(input_image, None, image_properties,
|
| 39 |
+
self.plans_manager,
|
| 40 |
+
self.configuration_manager,
|
| 41 |
+
self.dataset_json)
|
| 42 |
+
|
| 43 |
+
data = torch.from_numpy(data)
|
| 44 |
+
del input_image
|
| 45 |
+
if self.verbose:
|
| 46 |
+
print('predicting')
|
| 47 |
+
|
| 48 |
+
predicted_logits = self.predict_preprocessed_image(data)
|
| 49 |
+
|
| 50 |
+
if self.verbose: print('Prediction done')
|
| 51 |
+
|
| 52 |
+
segmentation = self.convert_predicted_logits_to_segmentation_with_correct_shape(predicted_logits,
|
| 53 |
+
image_properties)
|
| 54 |
+
return segmentation
|
| 55 |
+
|
| 56 |
+
def initialize_from_trained_model_folder(self, model_training_output_dir: str,
|
| 57 |
+
use_folds: Union[Tuple[Union[int, str]], None],
|
| 58 |
+
checkpoint_name: str = 'checkpoint_final.pth'):
|
| 59 |
+
"""
|
| 60 |
+
This is used when making predictions with a trained model
|
| 61 |
+
"""
|
| 62 |
+
if use_folds is None:
|
| 63 |
+
use_folds = nnUNetPredictor.auto_detect_available_folds(model_training_output_dir, checkpoint_name)
|
| 64 |
+
|
| 65 |
+
dataset_json = load_json(join(model_training_output_dir, 'dataset.json'))
|
| 66 |
+
plans = load_json(join(model_training_output_dir, 'plans.json'))
|
| 67 |
+
plans_manager = PlansManager(plans)
|
| 68 |
+
|
| 69 |
+
if isinstance(use_folds, str):
|
| 70 |
+
use_folds = [use_folds]
|
| 71 |
+
|
| 72 |
+
parameters = []
|
| 73 |
+
for i, f in enumerate(use_folds):
|
| 74 |
+
f = int(f) if f != 'all' else f
|
| 75 |
+
checkpoint = torch.load(join(model_training_output_dir, f'fold_{f}', checkpoint_name),
|
| 76 |
+
map_location=torch.device('cpu'), weights_only=False)
|
| 77 |
+
if i == 0:
|
| 78 |
+
trainer_name = checkpoint['trainer_name']
|
| 79 |
+
configuration_name = checkpoint['init_args']['configuration']
|
| 80 |
+
inference_allowed_mirroring_axes = checkpoint['inference_allowed_mirroring_axes'] if \
|
| 81 |
+
'inference_allowed_mirroring_axes' in checkpoint.keys() else None
|
| 82 |
+
|
| 83 |
+
parameters.append(join(model_training_output_dir, f'fold_{f}', checkpoint_name))
|
| 84 |
+
|
| 85 |
+
configuration_manager = plans_manager.get_configuration(configuration_name)
|
| 86 |
+
# restore network
|
| 87 |
+
num_input_channels = determine_num_input_channels(plans_manager, configuration_manager, dataset_json)
|
| 88 |
+
trainer_class = recursive_find_python_class(join(nnunetv2.__path__[0], "training", "nnUNetTrainer"),
|
| 89 |
+
trainer_name, 'nnunetv2.training.nnUNetTrainer')
|
| 90 |
+
if trainer_class is None:
|
| 91 |
+
raise RuntimeError(f'Unable to locate trainer class {trainer_name} in nnunetv2.training.nnUNetTrainer. '
|
| 92 |
+
f'Please place it there (in any .py file)!')
|
| 93 |
+
network = trainer_class.build_network_architecture(
|
| 94 |
+
configuration_manager.network_arch_class_name,
|
| 95 |
+
configuration_manager.network_arch_init_kwargs,
|
| 96 |
+
configuration_manager.network_arch_init_kwargs_req_import,
|
| 97 |
+
num_input_channels,
|
| 98 |
+
plans_manager.get_label_manager(dataset_json).num_segmentation_heads,
|
| 99 |
+
enable_deep_supervision=False
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
self.plans_manager = plans_manager
|
| 103 |
+
self.configuration_manager = configuration_manager
|
| 104 |
+
self.list_of_parameters = parameters
|
| 105 |
+
self.network = network
|
| 106 |
+
self.dataset_json = dataset_json
|
| 107 |
+
self.trainer_name = trainer_name
|
| 108 |
+
self.allowed_mirroring_axes = inference_allowed_mirroring_axes
|
| 109 |
+
self.label_manager = plans_manager.get_label_manager(dataset_json)
|
| 110 |
+
if ('nnUNet_compile' in os.environ.keys()) and (os.environ['nnUNet_compile'].lower() in ('true', '1', 't')) \
|
| 111 |
+
and not isinstance(self.network, OptimizedModule):
|
| 112 |
+
print('Using torch.compile')
|
| 113 |
+
self.network = torch.compile(self.network)
|
| 114 |
+
|
| 115 |
+
@torch.inference_mode(mode=True)
|
| 116 |
+
def predict_preprocessed_image(self, image):
|
| 117 |
+
empty_cache(self.device)
|
| 118 |
+
data_device = torch.device('cpu')
|
| 119 |
+
predicted_logits_device = torch.device('cpu')
|
| 120 |
+
gaussian_device = torch.device('cpu')
|
| 121 |
+
compute_device = torch.device('cuda:0')
|
| 122 |
+
|
| 123 |
+
data, slicer_revert_padding = pad_nd_image(image, self.configuration_manager.patch_size,
|
| 124 |
+
'constant', {'value': 0}, True,
|
| 125 |
+
None)
|
| 126 |
+
del image
|
| 127 |
+
|
| 128 |
+
slicers = self._internal_get_sliding_window_slicers(data.shape[1:])
|
| 129 |
+
|
| 130 |
+
empty_cache(self.device)
|
| 131 |
+
|
| 132 |
+
data = data.to(data_device)
|
| 133 |
+
predicted_logits = torch.zeros((self.label_manager.num_segmentation_heads, *data.shape[1:]),
|
| 134 |
+
dtype=torch.half,
|
| 135 |
+
device=predicted_logits_device)
|
| 136 |
+
gaussian = compute_gaussian(tuple(self.configuration_manager.patch_size), sigma_scale=1. / 8,
|
| 137 |
+
value_scaling_factor=10,
|
| 138 |
+
device=gaussian_device, dtype=torch.float16)
|
| 139 |
+
|
| 140 |
+
if not self.allow_tqdm and self.verbose:
|
| 141 |
+
print(f'running prediction: {len(slicers)} steps')
|
| 142 |
+
|
| 143 |
+
for p in self.list_of_parameters:
|
| 144 |
+
# network weights have to be updated outside autocast!
|
| 145 |
+
# we are loading parameters on demand instead of loading them upfront. This reduces memory footprint a lot.
|
| 146 |
+
# each set of parameters is only used once on the test set (one image) so run time wise this is almost the
|
| 147 |
+
# same
|
| 148 |
+
self.network.load_state_dict(torch.load(p, map_location=compute_device)['network_weights'], weights_only=False)
|
| 149 |
+
with torch.autocast(self.device.type, enabled=True):
|
| 150 |
+
for sl in tqdm(slicers, disable=not self.allow_tqdm):
|
| 151 |
+
pred = self._internal_maybe_mirror_and_predict(data[sl][None].to(compute_device))[0].to(
|
| 152 |
+
predicted_logits_device)
|
| 153 |
+
pred /= (pred.max() / 100)
|
| 154 |
+
predicted_logits[sl] += (pred * gaussian)
|
| 155 |
+
del pred
|
| 156 |
+
empty_cache(self.device)
|
| 157 |
+
return predicted_logits
|
| 158 |
+
|
| 159 |
+
def convert_predicted_logits_to_segmentation_with_correct_shape(self, predicted_logits, props):
|
| 160 |
+
old = torch.get_num_threads()
|
| 161 |
+
torch.set_num_threads(7)
|
| 162 |
+
|
| 163 |
+
# resample to original shape
|
| 164 |
+
spacing_transposed = [props['spacing'][i] for i in self.plans_manager.transpose_forward]
|
| 165 |
+
current_spacing = self.configuration_manager.spacing if \
|
| 166 |
+
len(self.configuration_manager.spacing) == \
|
| 167 |
+
len(props['shape_after_cropping_and_before_resampling']) else \
|
| 168 |
+
[spacing_transposed[0], *self.configuration_manager.spacing]
|
| 169 |
+
predicted_logits = self.configuration_manager.resampling_fn_probabilities(predicted_logits,
|
| 170 |
+
props[
|
| 171 |
+
'shape_after_cropping_and_before_resampling'],
|
| 172 |
+
current_spacing,
|
| 173 |
+
[props['spacing'][i] for i in
|
| 174 |
+
self.plans_manager.transpose_forward])
|
| 175 |
+
|
| 176 |
+
segmentation = None
|
| 177 |
+
pp = None
|
| 178 |
+
try:
|
| 179 |
+
with torch.no_grad():
|
| 180 |
+
pp = predicted_logits.to('cuda:0')
|
| 181 |
+
segmentation = pp.argmax(0).cpu()
|
| 182 |
+
del pp
|
| 183 |
+
except RuntimeError:
|
| 184 |
+
del segmentation, pp
|
| 185 |
+
torch.cuda.empty_cache()
|
| 186 |
+
segmentation = predicted_logits.argmax(0)
|
| 187 |
+
del predicted_logits
|
| 188 |
+
|
| 189 |
+
# segmentation may be torch.Tensor but we continue with numpy
|
| 190 |
+
if isinstance(segmentation, torch.Tensor):
|
| 191 |
+
segmentation = segmentation.cpu().numpy()
|
| 192 |
+
|
| 193 |
+
# put segmentation in bbox (revert cropping)
|
| 194 |
+
segmentation_reverted_cropping = np.zeros(props['shape_before_cropping'],
|
| 195 |
+
dtype=np.uint8 if len(
|
| 196 |
+
self.label_manager.foreground_labels) < 255 else np.uint16)
|
| 197 |
+
slicer = bounding_box_to_slice(props['bbox_used_for_cropping'])
|
| 198 |
+
segmentation_reverted_cropping[slicer] = segmentation
|
| 199 |
+
del segmentation
|
| 200 |
+
|
| 201 |
+
# revert transpose
|
| 202 |
+
segmentation_reverted_cropping = segmentation_reverted_cropping.transpose(self.plans_manager.transpose_backward)
|
| 203 |
+
torch.set_num_threads(old)
|
| 204 |
+
return segmentation_reverted_cropping
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
def predict_semseg(im, prop, semseg_trained_model, semseg_folds):
|
| 208 |
+
# initialize predictors
|
| 209 |
+
pred_semseg = CustomPredictor(
|
| 210 |
+
tile_step_size=0.5,
|
| 211 |
+
use_mirroring=True,
|
| 212 |
+
use_gaussian=True,
|
| 213 |
+
perform_everything_on_device=False,
|
| 214 |
+
allow_tqdm=True
|
| 215 |
+
)
|
| 216 |
+
pred_semseg.initialize_from_trained_model_folder(
|
| 217 |
+
semseg_trained_model,
|
| 218 |
+
use_folds=semseg_folds,
|
| 219 |
+
checkpoint_name='checkpoint_final.pth'
|
| 220 |
+
)
|
| 221 |
+
|
| 222 |
+
semseg_pred = pred_semseg.predict_single_npy_array(
|
| 223 |
+
im, prop, None
|
| 224 |
+
)
|
| 225 |
+
torch.cuda.empty_cache()
|
| 226 |
+
gc.collect()
|
| 227 |
+
return semseg_pred
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def map_labels_to_toothfairy(predicted_seg: np.ndarray) -> np.ndarray:
|
| 231 |
+
# Create an array that maps the labels directly
|
| 232 |
+
max_label = 42
|
| 233 |
+
mapping = np.arange(max_label + 1)
|
| 234 |
+
|
| 235 |
+
# Define the specific remapping
|
| 236 |
+
remapping = {19: 21, 20: 22, 21: 23, 22: 24, 23: 25, 24: 26, 25: 27, 26: 28,
|
| 237 |
+
27: 31, 28: 32, 29: 33, 30: 34, 31: 35, 32: 36, 33: 37, 34: 38,
|
| 238 |
+
35: 41, 36: 42, 37: 43, 38: 44, 39: 45, 40: 46, 41: 47, 42: 48}
|
| 239 |
+
|
| 240 |
+
# Apply the remapping
|
| 241 |
+
for k, v in remapping.items():
|
| 242 |
+
mapping[k] = v
|
| 243 |
+
|
| 244 |
+
return mapping[predicted_seg]
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def postprocess(prediction_npy, vol_per_voxel, verbose: bool = False):
|
| 248 |
+
cutoffs = {1: 0.0,
|
| 249 |
+
2: 78411.5,
|
| 250 |
+
3: 0.0,
|
| 251 |
+
4: 0.0,
|
| 252 |
+
5: 2800.0,
|
| 253 |
+
6: 1216.5,
|
| 254 |
+
7: 0.0,
|
| 255 |
+
8: 6222.0,
|
| 256 |
+
9: 1573.0,
|
| 257 |
+
10: 946.0,
|
| 258 |
+
11: 0.0,
|
| 259 |
+
12: 6783.5,
|
| 260 |
+
13: 9469.5,
|
| 261 |
+
14: 0.0,
|
| 262 |
+
15: 2260.0,
|
| 263 |
+
16: 3566.0,
|
| 264 |
+
17: 6321.0,
|
| 265 |
+
18: 4221.5,
|
| 266 |
+
19: 5829.0,
|
| 267 |
+
20: 0.0,
|
| 268 |
+
21: 0.0,
|
| 269 |
+
22: 468.0,
|
| 270 |
+
23: 1555.0,
|
| 271 |
+
24: 1291.5,
|
| 272 |
+
25: 2834.5,
|
| 273 |
+
26: 584.5,
|
| 274 |
+
27: 0.0,
|
| 275 |
+
28: 0.0,
|
| 276 |
+
29: 0.0,
|
| 277 |
+
30: 0.0,
|
| 278 |
+
31: 1935.5,
|
| 279 |
+
32: 0.0,
|
| 280 |
+
33: 0.0,
|
| 281 |
+
34: 6140.0,
|
| 282 |
+
35: 0.0,
|
| 283 |
+
36: 0.0,
|
| 284 |
+
37: 0.0,
|
| 285 |
+
38: 2710.0,
|
| 286 |
+
39: 0.0,
|
| 287 |
+
40: 0.0,
|
| 288 |
+
41: 0.0,
|
| 289 |
+
42: 970.0}
|
| 290 |
+
|
| 291 |
+
vol_per_voxel_cutoffs = 0.3 * 0.3 * 0.3
|
| 292 |
+
for c in cutoffs.keys():
|
| 293 |
+
co = cutoffs[c]
|
| 294 |
+
if co > 0:
|
| 295 |
+
mask = prediction_npy == c
|
| 296 |
+
pred_vol = np.sum(mask) * vol_per_voxel
|
| 297 |
+
if 0 < pred_vol < (co * vol_per_voxel_cutoffs):
|
| 298 |
+
prediction_npy[mask] = 0
|
| 299 |
+
if verbose:
|
| 300 |
+
print(
|
| 301 |
+
f'removed label {c} because predicted volume of {pred_vol} is less than the cutoff {co * vol_per_voxel_cutoffs}')
|
| 302 |
+
return prediction_npy
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
if __name__ == '__main__':
|
| 306 |
+
os.environ['nnUNet_compile'] = 'f'
|
| 307 |
+
|
| 308 |
+
parser = argparse.ArgumentParser()
|
| 309 |
+
parser.add_argument('-i', '--input_folder', type=Path, default="/input/images/cbct/")
|
| 310 |
+
parser.add_argument('-o', '--output_folder', type=Path, default="/output/images/oral-pharyngeal-segmentation/")
|
| 311 |
+
parser.add_argument('-sem_mod', '--semseg_trained_model', type=str,
|
| 312 |
+
default="/opt/app/_trained_model/semseg_trained_model")
|
| 313 |
+
parser.add_argument('--semseg_folds', type=str, nargs='+', default=[0, 1])
|
| 314 |
+
args = parser.parse_args()
|
| 315 |
+
|
| 316 |
+
args.output_folder.mkdir(exist_ok=True, parents=True)
|
| 317 |
+
|
| 318 |
+
semseg_folds = [i if i == 'all' else int(i) for i in args.semseg_folds]
|
| 319 |
+
semseg_trained_model = args.semseg_trained_model
|
| 320 |
+
|
| 321 |
+
rw = SimpleITKIO()
|
| 322 |
+
|
| 323 |
+
input_files = list(args.input_folder.glob('*.nii.gz')) + list(args.input_folder.glob('*.mha'))
|
| 324 |
+
|
| 325 |
+
for input_fname in input_files:
|
| 326 |
+
output_fname = args.output_folder / input_fname.name
|
| 327 |
+
|
| 328 |
+
# we start with the instance seg because we can then start converting that while semseg is being predicted
|
| 329 |
+
# load test image
|
| 330 |
+
im, prop = rw.read_images([input_fname])
|
| 331 |
+
|
| 332 |
+
with torch.no_grad():
|
| 333 |
+
semseg_pred = predict_semseg(im, prop, semseg_trained_model, semseg_folds)
|
| 334 |
+
torch.cuda.empty_cache()
|
| 335 |
+
gc.collect()
|
| 336 |
+
|
| 337 |
+
# now postprocess
|
| 338 |
+
semseg_pred = postprocess(semseg_pred, np.prod(prop['spacing']), True)
|
| 339 |
+
|
| 340 |
+
semseg_pred = map_labels_to_toothfairy(semseg_pred)
|
| 341 |
+
|
| 342 |
+
# now save
|
| 343 |
+
rw.write_seg(semseg_pred, output_fname, prop)
|
nnUNet/documentation/competitions/Toothfairy2/readme.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Authors: \
|
| 2 |
+
Fabian Isensee*, Yannick Kirchhoff*, Lars Kraemer, Max Rokuss, Constantin Ulrich, Klaus H. Maier-Hein
|
| 3 |
+
|
| 4 |
+
*: equal contribution
|
| 5 |
+
|
| 6 |
+
Author Affiliations:\
|
| 7 |
+
Division of Medical Image Computing, German Cancer Research Center (DKFZ), Heidelberg \
|
| 8 |
+
Helmholtz Imaging
|
| 9 |
+
|
| 10 |
+
# Introduction
|
| 11 |
+
|
| 12 |
+
This document describes our submission to the [Toothfairy2 Challenge](https://toothfairy2.grand-challenge.org/toothfairy2/).
|
| 13 |
+
Our model is essentially a nnU-Net ResEnc L with the patch size upscaled to 160x320x320 pixels. We disable left/right
|
| 14 |
+
mirroring and train for 1500 instead of the standard 1000 epochs. Training was either done on 2xA100 40GB or one GH200 96GB.
|
| 15 |
+
|
| 16 |
+
# Dataset Conversion
|
| 17 |
+
|
| 18 |
+
# Experiment Planning and Preprocessing
|
| 19 |
+
Adapt and run the [dataset conversion script](../../../nnunetv2/dataset_conversion/Dataset119_ToothFairy2_All.py).
|
| 20 |
+
This script just converts the mha files to nifti (smaller file size) and removes the unused label ids.
|
| 21 |
+
|
| 22 |
+
## Extract fingerprint:
|
| 23 |
+
`nnUNetv2_extract_fingerprint -d 119 -np 48`
|
| 24 |
+
|
| 25 |
+
## Run planning:
|
| 26 |
+
`nnUNetv2_plan_experiment -d 119 -pl nnUNetPlannerResEncL_torchres`
|
| 27 |
+
|
| 28 |
+
This planner not only uses the ResEncL configuration but also replaces the default resampling scheme with one that is
|
| 29 |
+
faster (but less precise). Since all images in the challenge (train and test) should already have 0.3x0.3x0.3 spacing
|
| 30 |
+
resampling is not required. This is just here as a safety measure. The speed is needed at inference time because grand
|
| 31 |
+
challenge imposes a limit of 10 minutes per case.
|
| 32 |
+
|
| 33 |
+
## Edit the plans files
|
| 34 |
+
Add the following configuration to the generated plans file:
|
| 35 |
+
|
| 36 |
+
```json
|
| 37 |
+
"3d_fullres_torchres_ps160x320x320_bs2": {
|
| 38 |
+
"inherits_from": "3d_fullres",
|
| 39 |
+
"data_identifier": "nnUNetPlans_3d_fullres_torchres_ctnorm",
|
| 40 |
+
"patch_size": [
|
| 41 |
+
160,
|
| 42 |
+
320,
|
| 43 |
+
320
|
| 44 |
+
],
|
| 45 |
+
"normalization_schemes": [
|
| 46 |
+
"CTNormalization"
|
| 47 |
+
],
|
| 48 |
+
"architecture": {
|
| 49 |
+
"network_class_name": "dynamic_network_architectures.architectures.unet.ResidualEncoderUNet",
|
| 50 |
+
"arch_kwargs": {
|
| 51 |
+
"n_stages": 7,
|
| 52 |
+
"features_per_stage": [
|
| 53 |
+
32,
|
| 54 |
+
64,
|
| 55 |
+
128,
|
| 56 |
+
256,
|
| 57 |
+
320,
|
| 58 |
+
320,
|
| 59 |
+
320
|
| 60 |
+
],
|
| 61 |
+
"conv_op": "torch.nn.modules.conv.Conv3d",
|
| 62 |
+
"kernel_sizes": [
|
| 63 |
+
[
|
| 64 |
+
3,
|
| 65 |
+
3,
|
| 66 |
+
3
|
| 67 |
+
],
|
| 68 |
+
[
|
| 69 |
+
3,
|
| 70 |
+
3,
|
| 71 |
+
3
|
| 72 |
+
],
|
| 73 |
+
[
|
| 74 |
+
3,
|
| 75 |
+
3,
|
| 76 |
+
3
|
| 77 |
+
],
|
| 78 |
+
[
|
| 79 |
+
3,
|
| 80 |
+
3,
|
| 81 |
+
3
|
| 82 |
+
],
|
| 83 |
+
[
|
| 84 |
+
3,
|
| 85 |
+
3,
|
| 86 |
+
3
|
| 87 |
+
],
|
| 88 |
+
[
|
| 89 |
+
3,
|
| 90 |
+
3,
|
| 91 |
+
3
|
| 92 |
+
],
|
| 93 |
+
[
|
| 94 |
+
3,
|
| 95 |
+
3,
|
| 96 |
+
3
|
| 97 |
+
]
|
| 98 |
+
],
|
| 99 |
+
"strides": [
|
| 100 |
+
[
|
| 101 |
+
1,
|
| 102 |
+
1,
|
| 103 |
+
1
|
| 104 |
+
],
|
| 105 |
+
[
|
| 106 |
+
2,
|
| 107 |
+
2,
|
| 108 |
+
2
|
| 109 |
+
],
|
| 110 |
+
[
|
| 111 |
+
2,
|
| 112 |
+
2,
|
| 113 |
+
2
|
| 114 |
+
],
|
| 115 |
+
[
|
| 116 |
+
2,
|
| 117 |
+
2,
|
| 118 |
+
2
|
| 119 |
+
],
|
| 120 |
+
[
|
| 121 |
+
2,
|
| 122 |
+
2,
|
| 123 |
+
2
|
| 124 |
+
],
|
| 125 |
+
[
|
| 126 |
+
2,
|
| 127 |
+
2,
|
| 128 |
+
2
|
| 129 |
+
],
|
| 130 |
+
[
|
| 131 |
+
1,
|
| 132 |
+
2,
|
| 133 |
+
2
|
| 134 |
+
]
|
| 135 |
+
],
|
| 136 |
+
"n_blocks_per_stage": [
|
| 137 |
+
1,
|
| 138 |
+
3,
|
| 139 |
+
4,
|
| 140 |
+
6,
|
| 141 |
+
6,
|
| 142 |
+
6,
|
| 143 |
+
6
|
| 144 |
+
],
|
| 145 |
+
"n_conv_per_stage_decoder": [
|
| 146 |
+
1,
|
| 147 |
+
1,
|
| 148 |
+
1,
|
| 149 |
+
1,
|
| 150 |
+
1,
|
| 151 |
+
1
|
| 152 |
+
],
|
| 153 |
+
"conv_bias": true,
|
| 154 |
+
"norm_op": "torch.nn.modules.instancenorm.InstanceNorm3d",
|
| 155 |
+
"norm_op_kwargs": {
|
| 156 |
+
"eps": 1e-05,
|
| 157 |
+
"affine": true
|
| 158 |
+
},
|
| 159 |
+
"dropout_op": null,
|
| 160 |
+
"dropout_op_kwargs": null,
|
| 161 |
+
"nonlin": "torch.nn.LeakyReLU",
|
| 162 |
+
"nonlin_kwargs": {
|
| 163 |
+
"inplace": true
|
| 164 |
+
}
|
| 165 |
+
},
|
| 166 |
+
"_kw_requires_import": [
|
| 167 |
+
"conv_op",
|
| 168 |
+
"norm_op",
|
| 169 |
+
"dropout_op",
|
| 170 |
+
"nonlin"
|
| 171 |
+
]
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
```
|
| 175 |
+
Aside from changing the patch size this makes the architecture one stage deeper (one more pooling + res blocks), enabling
|
| 176 |
+
it to make effective use of the larger input
|
| 177 |
+
|
| 178 |
+
# Preprocessing
|
| 179 |
+
`nnUNetv2_preprocess -d 119 -c 3d_fullres_torchres_ps160x320x320_bs2 -plans_name nnUNetResEncUNetLPlans_torchres -np 48`
|
| 180 |
+
|
| 181 |
+
# Training
|
| 182 |
+
We train two models on all training cases:
|
| 183 |
+
|
| 184 |
+
```bash
|
| 185 |
+
nnUNetv2_train 119 3d_fullres_torchres_ps160x320x320_bs2 all -p nnUNetResEncUNetLPlans_torchres -tr nnUNetTrainer_onlyMirror01_1500ep
|
| 186 |
+
nnUNet_results=${nnUNet_results}_2 nnUNetv2_train 119 3d_fullres_torchres_ps160x320x320_bs2 all -p nnUNetResEncUNetLPlans_torchres -tr nnUNetTrainer_onlyMirror01_1500ep
|
| 187 |
+
```
|
| 188 |
+
Models are trained from scratch.
|
| 189 |
+
|
| 190 |
+
Note how in the second line we overwrite the nnUNet_results variable in order to be able to train the same model twice without overwriting the results
|
| 191 |
+
|
| 192 |
+
We recommend to increase the number of processes used for data augmentation. Otherwise you can run into CPU bottlenecks.
|
| 193 |
+
Use `export nnUNet_n_proc_DA=32` or higher (if your system permits!).
|
| 194 |
+
|
| 195 |
+
# Inference
|
| 196 |
+
We ensemble the two models from above. On a technical level we copy the two fold_all folders into one training output
|
| 197 |
+
directory and rename them to fold_0 and fold_1. This lets us use nnU-Net's cross-validation ensembling strategy which
|
| 198 |
+
is more computationally efficient (needed for time limit on grand-challenge.org).
|
| 199 |
+
|
| 200 |
+
Run inference with the [inference script](inference_script_semseg_only_customInf2.py)
|
| 201 |
+
|
| 202 |
+
# Postprocessing
|
| 203 |
+
If the prediction of a class on some test case is smaller than the corresponding cutoff size then it is removed
|
| 204 |
+
(replaced with background).
|
| 205 |
+
|
| 206 |
+
Cutoff values were optimized using a five-fold cross-validation on the Toothfairy2 training data. We optimize HD95 and Dice separately.
|
| 207 |
+
The final cutoff for each class is then the smaller value between the two metrics. You can find our volume cutoffs in the inference
|
| 208 |
+
script as part of our `postprocess` function.
|
nnUNet/documentation/competitions/__init__.py
ADDED
|
File without changes
|
nnUNet/documentation/convert_msd_dataset.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Use `nnUNetv2_convert_MSD_dataset`.
|
| 2 |
+
|
| 3 |
+
Read `nnUNetv2_convert_MSD_dataset -h` for usage instructions.
|
nnUNet/documentation/dataset_format.md
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# nnU-Net dataset format
|
| 2 |
+
The only way to bring your data into nnU-Net is by storing it in a specific format. Due to nnU-Net's roots in the
|
| 3 |
+
[Medical Segmentation Decathlon](http://medicaldecathlon.com/) (MSD), its dataset is heavily inspired but has since
|
| 4 |
+
diverged (see also [here](#how-to-use-decathlon-datasets)) from the format used in the MSD.
|
| 5 |
+
|
| 6 |
+
Datasets consist of three components: raw images, corresponding segmentation maps and a dataset.json file specifying
|
| 7 |
+
some metadata.
|
| 8 |
+
|
| 9 |
+
If you are migrating from nnU-Net v1, read [this](#how-to-use-nnu-net-v1-tasks) to convert your existing Tasks.
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
## What do training cases look like?
|
| 13 |
+
Each training case is associated with an identifier = a unique name for that case. This identifier is used by nnU-Net to
|
| 14 |
+
connect images with the correct segmentation.
|
| 15 |
+
|
| 16 |
+
A training case consists of images and their corresponding segmentation.
|
| 17 |
+
|
| 18 |
+
**Images** is plural because nnU-Net supports arbitrarily many input channels. In order to be as flexible as possible,
|
| 19 |
+
nnU-net requires each input channel to be stored in a separate image (with the sole exception being RGB natural
|
| 20 |
+
images). So these images could for example be a T1 and a T2 MRI (or whatever else you want). The different input
|
| 21 |
+
channels MUST have the same geometry (same shape, spacing (if applicable) etc.) and
|
| 22 |
+
must be co-registered (if applicable). Input channels are identified by nnU-Net by their FILE_ENDING: a four-digit integer at the end
|
| 23 |
+
of the filename. Image files must therefore follow the following naming convention: {CASE_IDENTIFIER}_{XXXX}.{FILE_ENDING}.
|
| 24 |
+
Hereby, XXXX is the 4-digit modality/channel identifier (should be unique for each modality/channel, e.g., β0000β for T1, β0001β for
|
| 25 |
+
T2 MRI, β¦) and FILE_ENDING is the file extension used by your image format (.png, .nii.gz, ...). See below for concrete examples.
|
| 26 |
+
The dataset.json file connects channel names with the channel identifiers in the 'channel_names' key (see below for details).
|
| 27 |
+
|
| 28 |
+
Side note: Typically, each channel/modality needs to be stored in a separate file and is accessed with the XXXX channel identifier.
|
| 29 |
+
Exception are natural images (RGB; .png) where the three color channels can all be stored in one file (see the
|
| 30 |
+
[road segmentation](../nnunetv2/dataset_conversion/Dataset120_RoadSegmentation.py) dataset as an example).
|
| 31 |
+
|
| 32 |
+
**Segmentations** must share the same geometry with their corresponding images (same shape etc.). Segmentations are
|
| 33 |
+
integer maps with each value representing a semantic class. The background must be 0. If there is no background, then
|
| 34 |
+
do not use the label 0 for something else! Integer values of your semantic classes must be consecutive (0, 1, 2, 3,
|
| 35 |
+
...). Of course, not all labels have to be present in each training case. Segmentations are saved as {CASE_IDENTIFER}.{FILE_ENDING} .
|
| 36 |
+
|
| 37 |
+
Within a training case, all image geometries (input channels, corresponding segmentation) must match. Between training
|
| 38 |
+
cases, they can of course differ. nnU-Net takes care of that.
|
| 39 |
+
|
| 40 |
+
Important: The input channels must be consistent! Concretely, **all images need the same input channels in the same
|
| 41 |
+
order and all input channels have to be present every time**. This is also true for inference!
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
## Supported file formats
|
| 45 |
+
nnU-Net expects the same file format for images and segmentations! These will also be used for inference. For now, it
|
| 46 |
+
is thus not possible to train .png and then run inference on .jpg.
|
| 47 |
+
|
| 48 |
+
One big change in nnU-Net V2 is the support of multiple input file types. Gone are the days of converting everything to .nii.gz!
|
| 49 |
+
This is implemented by abstracting the input and output of images + segmentations through `BaseReaderWriter`. nnU-Net
|
| 50 |
+
comes with a broad collection of Readers+Writers and you can even add your own to support your data format!
|
| 51 |
+
See [here](../nnunetv2/imageio/readme.md).
|
| 52 |
+
|
| 53 |
+
As a nice bonus, nnU-Net now also natively supports 2D input images and you no longer have to mess around with
|
| 54 |
+
conversions to pseudo 3D niftis. Yuck. That was disgusting.
|
| 55 |
+
|
| 56 |
+
Note that internally (for storing and accessing preprocessed images) nnU-Net will use its own file format, irrespective
|
| 57 |
+
of what the raw data was provided in! This is for performance reasons.
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
By default, the following file formats are supported:
|
| 61 |
+
|
| 62 |
+
- NaturalImage2DIO: .png, .bmp, .tif
|
| 63 |
+
- NibabelIO: .nii.gz, .nrrd, .mha
|
| 64 |
+
- NibabelIOWithReorient: .nii.gz, .nrrd, .mha. This reader will reorient images to RAS!
|
| 65 |
+
- SimpleITKIO: .nii.gz, .nrrd, .mha
|
| 66 |
+
- Tiff3DIO: .tif, .tiff. 3D tif images! Since TIF does not have a standardized way of storing spacing information,
|
| 67 |
+
nnU-Net expects each TIF file to be accompanied by an identically named .json file that contains this information (see
|
| 68 |
+
[here](#datasetjson)).
|
| 69 |
+
|
| 70 |
+
The file extension lists are not exhaustive and depend on what the backend supports. For example, nibabel and SimpleITK
|
| 71 |
+
support more than the three given here. The file endings given here are just the ones we tested!
|
| 72 |
+
|
| 73 |
+
IMPORTANT: nnU-Net can only be used with file formats that use lossless (or no) compression! Because the file
|
| 74 |
+
format is defined for an entire dataset (and not separately for images and segmentations, this could be a todo for
|
| 75 |
+
the future), we must ensure that there are no compression artifacts that destroy the segmentation maps. So no .jpg and
|
| 76 |
+
the likes!
|
| 77 |
+
|
| 78 |
+
## Dataset folder structure
|
| 79 |
+
Datasets must be located in the `nnUNet_raw` folder (which you either define when installing nnU-Net or export/set every
|
| 80 |
+
time you intend to run nnU-Net commands!).
|
| 81 |
+
Each segmentation dataset is stored as a separate 'Dataset'. Datasets are associated with a dataset ID, a three digit
|
| 82 |
+
integer, and a dataset name (which you can freely choose): For example, Dataset005_Prostate has 'Prostate' as dataset name and
|
| 83 |
+
the dataset id is 5. Datasets are stored in the `nnUNet_raw` folder like this:
|
| 84 |
+
|
| 85 |
+
nnUNet_raw/
|
| 86 |
+
βββ Dataset001_BrainTumour
|
| 87 |
+
βββ Dataset002_Heart
|
| 88 |
+
βββ Dataset003_Liver
|
| 89 |
+
βββ Dataset004_Hippocampus
|
| 90 |
+
βββ Dataset005_Prostate
|
| 91 |
+
βββ ...
|
| 92 |
+
|
| 93 |
+
Within each dataset folder, the following structure is expected:
|
| 94 |
+
|
| 95 |
+
Dataset001_BrainTumour/
|
| 96 |
+
βββ dataset.json
|
| 97 |
+
βββ imagesTr
|
| 98 |
+
βββ imagesTs # optional
|
| 99 |
+
βββ labelsTr
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
When adding your custom dataset, take a look at the [dataset_conversion](../nnunetv2/dataset_conversion) folder and
|
| 103 |
+
pick an id that is not already taken. IDs 001-010 are for the Medical Segmentation Decathlon.
|
| 104 |
+
|
| 105 |
+
- **imagesTr** contains the images belonging to the training cases. nnU-Net will perform pipeline configuration, training with
|
| 106 |
+
cross-validation, as well as finding postprocessing and the best ensemble using this data.
|
| 107 |
+
- **imagesTs** (optional) contains the images that belong to the test cases. nnU-Net does not use them! This could just
|
| 108 |
+
be a convenient location for you to store these images. Remnant of the Medical Segmentation Decathlon folder structure.
|
| 109 |
+
- **labelsTr** contains the images with the ground truth segmentation maps for the training cases.
|
| 110 |
+
- **dataset.json** contains metadata of the dataset.
|
| 111 |
+
|
| 112 |
+
The scheme introduced [above](#what-do-training-cases-look-like) results in the following folder structure. Given
|
| 113 |
+
is an example for the first Dataset of the MSD: BrainTumour. This dataset hat four input channels: FLAIR (0000),
|
| 114 |
+
T1w (0001), T1gd (0002) and T2w (0003). Note that the imagesTs folder is optional and does not have to be present.
|
| 115 |
+
|
| 116 |
+
nnUNet_raw/Dataset001_BrainTumour/
|
| 117 |
+
βββ dataset.json
|
| 118 |
+
βββ imagesTr
|
| 119 |
+
βΒ Β βββ BRATS_001_0000.nii.gz
|
| 120 |
+
βΒ Β βββ BRATS_001_0001.nii.gz
|
| 121 |
+
βΒ Β βββ BRATS_001_0002.nii.gz
|
| 122 |
+
βΒ Β βββ BRATS_001_0003.nii.gz
|
| 123 |
+
βΒ Β βββ BRATS_002_0000.nii.gz
|
| 124 |
+
βΒ Β βββ BRATS_002_0001.nii.gz
|
| 125 |
+
βΒ Β βββ BRATS_002_0002.nii.gz
|
| 126 |
+
βΒ Β βββ BRATS_002_0003.nii.gz
|
| 127 |
+
βΒ Β βββ ...
|
| 128 |
+
βββ imagesTs
|
| 129 |
+
βΒ Β βββ BRATS_485_0000.nii.gz
|
| 130 |
+
βΒ Β βββ BRATS_485_0001.nii.gz
|
| 131 |
+
βΒ Β βββ BRATS_485_0002.nii.gz
|
| 132 |
+
βΒ Β βββ BRATS_485_0003.nii.gz
|
| 133 |
+
βΒ Β βββ BRATS_486_0000.nii.gz
|
| 134 |
+
βΒ Β βββ BRATS_486_0001.nii.gz
|
| 135 |
+
βΒ Β βββ BRATS_486_0002.nii.gz
|
| 136 |
+
βΒ Β βββ BRATS_486_0003.nii.gz
|
| 137 |
+
βΒ Β βββ ...
|
| 138 |
+
βββ labelsTr
|
| 139 |
+
βββ BRATS_001.nii.gz
|
| 140 |
+
βββ BRATS_002.nii.gz
|
| 141 |
+
βββ ...
|
| 142 |
+
|
| 143 |
+
Here is another example of the second dataset of the MSD, which has only one input channel:
|
| 144 |
+
|
| 145 |
+
nnUNet_raw/Dataset002_Heart/
|
| 146 |
+
βββ dataset.json
|
| 147 |
+
βββ imagesTr
|
| 148 |
+
βΒ Β βββ la_003_0000.nii.gz
|
| 149 |
+
βΒ Β βββ la_004_0000.nii.gz
|
| 150 |
+
βΒ Β βββ ...
|
| 151 |
+
βββ imagesTs
|
| 152 |
+
βΒ Β βββ la_001_0000.nii.gz
|
| 153 |
+
βΒ Β βββ la_002_0000.nii.gz
|
| 154 |
+
βΒ Β βββ ...
|
| 155 |
+
βββ labelsTr
|
| 156 |
+
βββ la_003.nii.gz
|
| 157 |
+
βββ la_004.nii.gz
|
| 158 |
+
βββ ...
|
| 159 |
+
|
| 160 |
+
Remember: For each training case, all images must have the same geometry to ensure that their pixel arrays are aligned. Also
|
| 161 |
+
make sure that all your data is co-registered!
|
| 162 |
+
|
| 163 |
+
See also [dataset format inference](dataset_format_inference.md)!!
|
| 164 |
+
|
| 165 |
+
## dataset.json
|
| 166 |
+
The dataset.json contains metadata that nnU-Net needs for training. We have greatly reduced the number of required
|
| 167 |
+
fields since version 1!
|
| 168 |
+
|
| 169 |
+
Here is what the dataset.json should look like at the example of the Dataset005_Prostate from the MSD:
|
| 170 |
+
|
| 171 |
+
{
|
| 172 |
+
"channel_names": { # formerly modalities
|
| 173 |
+
"0": "T2",
|
| 174 |
+
"1": "ADC"
|
| 175 |
+
},
|
| 176 |
+
"labels": { # THIS IS DIFFERENT NOW!
|
| 177 |
+
"background": 0,
|
| 178 |
+
"PZ": 1,
|
| 179 |
+
"TZ": 2
|
| 180 |
+
},
|
| 181 |
+
"numTraining": 32,
|
| 182 |
+
"file_ending": ".nii.gz"
|
| 183 |
+
"overwrite_image_reader_writer": "SimpleITKIO" # optional! If not provided nnU-Net will automatically determine the ReaderWriter
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
The channel_names determine the normalization used by nnU-Net. If a channel is marked as 'CT', then a global
|
| 187 |
+
normalization based on the intensities in the foreground pixels will be used. If it is something else, per-channel
|
| 188 |
+
z-scoring will be used. Refer to the methods section in [our paper](https://www.nature.com/articles/s41592-020-01008-z)
|
| 189 |
+
for more details. nnU-Net v2 introduces a few more normalization schemes to
|
| 190 |
+
choose from and allows you to define your own, see [here](explanation_normalization.md) for more information.
|
| 191 |
+
|
| 192 |
+
Important changes relative to nnU-Net v1:
|
| 193 |
+
- "modality" is now called "channel_names" to remove strong bias to medical images
|
| 194 |
+
- labels are structured differently (name -> int instead of int -> name). This was needed to support [region-based training](region_based_training.md)
|
| 195 |
+
- "file_ending" is added to support different input file types
|
| 196 |
+
- "overwrite_image_reader_writer" optional! Can be used to specify a certain (custom) ReaderWriter class that should
|
| 197 |
+
be used with this dataset. If not provided, nnU-Net will automatically determine the ReaderWriter
|
| 198 |
+
- "regions_class_order" only used in [region-based training](region_based_training.md)
|
| 199 |
+
|
| 200 |
+
There is a utility with which you can generate the dataset.json automatically. You can find it
|
| 201 |
+
[here](../nnunetv2/dataset_conversion/generate_dataset_json.py).
|
| 202 |
+
See our examples in [dataset_conversion](../nnunetv2/dataset_conversion) for how to use it. And read its documentation!
|
| 203 |
+
|
| 204 |
+
As described above, a json file that contains spacing information is required for TIFF files.
|
| 205 |
+
An example for a 3D TIFF stack with units corresponding to 7.6 in x and y, 80 in z is:
|
| 206 |
+
|
| 207 |
+
```
|
| 208 |
+
{
|
| 209 |
+
"spacing": [7.6, 7.6, 80.0]
|
| 210 |
+
}
|
| 211 |
+
```
|
| 212 |
+
|
| 213 |
+
Within the dataset folder, this file (named `cell6.json` in this example) would be placed in the following folders:
|
| 214 |
+
|
| 215 |
+
nnUNet_raw/Dataset123_Foo/
|
| 216 |
+
βββ dataset.json
|
| 217 |
+
βββ imagesTr
|
| 218 |
+
βΒ Β βββ cell6.json
|
| 219 |
+
βΒ Β βββ cell6_0000.tif
|
| 220 |
+
βββ labelsTr
|
| 221 |
+
βββ cell6.json
|
| 222 |
+
βββ cell6.tif
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
## How to use nnU-Net v1 Tasks
|
| 226 |
+
If you are migrating from the old nnU-Net, convert your existing datasets with `nnUNetv2_convert_old_nnUNet_dataset`!
|
| 227 |
+
|
| 228 |
+
Example for migrating a nnU-Net v1 Task:
|
| 229 |
+
```bash
|
| 230 |
+
nnUNetv2_convert_old_nnUNet_dataset /media/isensee/raw_data/nnUNet_raw_data_base/nnUNet_raw_data/Task027_ACDC Dataset027_ACDC
|
| 231 |
+
```
|
| 232 |
+
Use `nnUNetv2_convert_old_nnUNet_dataset -h` for detailed usage instructions.
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
## How to use decathlon datasets
|
| 236 |
+
See [convert_msd_dataset.md](convert_msd_dataset.md)
|
| 237 |
+
|
| 238 |
+
## How to use 2D data with nnU-Net
|
| 239 |
+
2D is now natively supported (yay!). See [here](#supported-file-formats) as well as the example dataset in this
|
| 240 |
+
[script](../nnunetv2/dataset_conversion/Dataset120_RoadSegmentation.py).
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
## How to update an existing dataset
|
| 244 |
+
When updating a dataset it is best practice to remove the preprocessed data in `nnUNet_preprocessed/DatasetXXX_NAME`
|
| 245 |
+
to ensure a fresh start. Then replace the data in `nnUNet_raw` and rerun `nnUNetv2_plan_and_preprocess`. Optionally,
|
| 246 |
+
also remove the results from old trainings.
|
| 247 |
+
|
| 248 |
+
# Example dataset conversion scripts
|
| 249 |
+
In the `dataset_conversion` folder (see [here](../nnunetv2/dataset_conversion)) are multiple example scripts for
|
| 250 |
+
converting datasets into nnU-Net format. These scripts cannot be run as they are (you need to open them and change
|
| 251 |
+
some paths) but they are excellent examples for you to learn how to convert your own datasets into nnU-Net format.
|
| 252 |
+
Just pick the dataset that is closest to yours as a starting point.
|
| 253 |
+
The list of dataset conversion scripts is continually updated. If you find that some publicly available dataset is
|
| 254 |
+
missing, feel free to open a PR to add it!
|
nnUNet/documentation/dataset_format_inference.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Data format for Inference
|
| 2 |
+
Read the documentation on the overall [data format](dataset_format.md) first!
|
| 3 |
+
|
| 4 |
+
The data format for inference must match the one used for the raw data (**specifically, the images must be in exactly
|
| 5 |
+
the same format as in the imagesTr folder**). As before, the filenames must start with a
|
| 6 |
+
unique identifier, followed by a 4-digit modality identifier. Here is an example for two different datasets:
|
| 7 |
+
|
| 8 |
+
1) Task005_Prostate:
|
| 9 |
+
|
| 10 |
+
This task has 2 modalities, so the files in the input folder must look like this:
|
| 11 |
+
|
| 12 |
+
input_folder
|
| 13 |
+
βββ prostate_03_0000.nii.gz
|
| 14 |
+
βββ prostate_03_0001.nii.gz
|
| 15 |
+
βββ prostate_05_0000.nii.gz
|
| 16 |
+
βββ prostate_05_0001.nii.gz
|
| 17 |
+
βββ prostate_08_0000.nii.gz
|
| 18 |
+
βββ prostate_08_0001.nii.gz
|
| 19 |
+
βββ ...
|
| 20 |
+
|
| 21 |
+
_0000 has to be the T2 image and _0001 has to be the ADC image (as specified by 'channel_names' in the
|
| 22 |
+
dataset.json), exactly the same as was used for training.
|
| 23 |
+
|
| 24 |
+
2) Task002_Heart:
|
| 25 |
+
|
| 26 |
+
imagesTs
|
| 27 |
+
βββ la_001_0000.nii.gz
|
| 28 |
+
βββ la_002_0000.nii.gz
|
| 29 |
+
βββ la_006_0000.nii.gz
|
| 30 |
+
βββ ...
|
| 31 |
+
|
| 32 |
+
Task002 only has one modality, so each case only has one _0000.nii.gz file.
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
The segmentations in the output folder will be named {CASE_IDENTIFIER}.nii.gz (omitting the modality identifier).
|
| 36 |
+
|
| 37 |
+
Remember that the file format used for inference (.nii.gz in this example) must be the same as was used for training
|
| 38 |
+
(and as was specified in 'file_ending' in the dataset.json)!
|
| 39 |
+
|
nnUNet/documentation/explanation_logging.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logging in nnU-Net v2
|
| 2 |
+
|
| 3 |
+
## Introduction
|
| 4 |
+
|
| 5 |
+
Logging in nnU-Net is intentionally simple and centralized in
|
| 6 |
+
`nnunetv2/training/logging/nnunet_logger.py`.
|
| 7 |
+
|
| 8 |
+
The trainer talks to one object, `MetaLogger`, and `MetaLogger` fans out logs to:
|
| 9 |
+
|
| 10 |
+
- `LocalLogger` (always enabled): the source of truth for training curves, checkpoint logging state, and `progress.png`
|
| 11 |
+
- optional external loggers (currently `WandbLogger`)
|
| 12 |
+
|
| 13 |
+
This keeps training code clean while still allowing external tracking backends.
|
| 14 |
+
|
| 15 |
+
## Default behaviour
|
| 16 |
+
|
| 17 |
+
Without any setup, nnU-Net uses only `LocalLogger`.
|
| 18 |
+
|
| 19 |
+
Per epoch, it stores:
|
| 20 |
+
|
| 21 |
+
- `mean_fg_dice` and `ema_fg_dice` (EMA is computed automatically)
|
| 22 |
+
- `dice_per_class_or_region`
|
| 23 |
+
- `train_losses`, `val_losses`
|
| 24 |
+
- `lrs`
|
| 25 |
+
- `epoch_start_timestamps`, `epoch_end_timestamps`
|
| 26 |
+
|
| 27 |
+
From these values, `progress.png` is updated in the fold output folder.
|
| 28 |
+
On checkpoint save/load, the local logging state is also saved/restored, so curves continue correctly after resume.
|
| 29 |
+
|
| 30 |
+
## How to enable W&B
|
| 31 |
+
|
| 32 |
+
1. Install W&B:
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
pip install wandb
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
2. Enable the backend via environment variables:
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
export nnUNet_wandb_enabled=1
|
| 42 |
+
export nnUNet_wandb_project=nnunet
|
| 43 |
+
export nnUNet_wandb_mode=online # or offline
|
| 44 |
+
```
|
| 45 |
+
|
| 46 |
+
3. Run training normally:
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
nnUNetv2_train DATASET_NAME_OR_ID 3d_fullres 0
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
Notes:
|
| 53 |
+
|
| 54 |
+
- `nnUNet_wandb_enabled` accepts `0/1` and `false/true` (case-insensitive). Other values raise an error.
|
| 55 |
+
- When resuming (`--c`), W&B resume metadata in `fold_x/wandb/latest-run` is reused and duplicate older steps are skipped.
|
| 56 |
+
|
| 57 |
+
## How to integrate a custom logger
|
| 58 |
+
|
| 59 |
+
Add a new logger class with the same minimal interface used by `MetaLogger`:
|
| 60 |
+
|
| 61 |
+
- `update_config(self, config: dict)`
|
| 62 |
+
- `log(self, key, value, step: int)`
|
| 63 |
+
- `log_summary(self, key, value)`
|
| 64 |
+
|
| 65 |
+
Example skeleton:
|
| 66 |
+
|
| 67 |
+
```python
|
| 68 |
+
class MyLogger:
|
| 69 |
+
def __init__(self, output_folder, resume):
|
| 70 |
+
self.output_folder = output_folder
|
| 71 |
+
self.resume = resume
|
| 72 |
+
|
| 73 |
+
def update_config(self, config: dict):
|
| 74 |
+
...
|
| 75 |
+
|
| 76 |
+
def log(self, key, value, step: int):
|
| 77 |
+
...
|
| 78 |
+
|
| 79 |
+
def log_summary(self, key, value):
|
| 80 |
+
...
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
Then register it in `MetaLogger.__init__` (for example behind an env var switch), similar to how `WandbLogger` is added.
|
| 84 |
+
|
| 85 |
+
Important integration detail:
|
| 86 |
+
|
| 87 |
+
- `MetaLogger.log(...)` always writes to `LocalLogger` first.
|
| 88 |
+
- If you introduce a brand-new per-epoch key, also add that key to `LocalLogger.my_fantastic_logging`, otherwise the local assertion will fail.
|
nnUNet/documentation/explanation_normalization.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Intensity normalization in nnU-Net
|
| 2 |
+
|
| 3 |
+
The type of intensity normalization applied in nnU-Net can be controlled via the `channel_names` (former `modalities`)
|
| 4 |
+
entry in the dataset.json. Just like the old nnU-Net, per-channel z-scoring as well as dataset-wide z-scoring based on
|
| 5 |
+
foreground intensities are supported. However, there have been a few additions as well.
|
| 6 |
+
|
| 7 |
+
Reminder: The `channel_names` entry typically looks like this:
|
| 8 |
+
|
| 9 |
+
"channel_names": {
|
| 10 |
+
"0": "T2",
|
| 11 |
+
"1": "ADC"
|
| 12 |
+
},
|
| 13 |
+
|
| 14 |
+
It has as many entries as there are input channels for the given dataset.
|
| 15 |
+
|
| 16 |
+
To tell you a secret, nnU-Net does not really care what your channels are called. We just use this to determine what normalization
|
| 17 |
+
scheme will be used for the given dataset. nnU-Net requires you to specify a normalization strategy for each of your input channels!
|
| 18 |
+
If you enter a channel name that is not in the following list, the default (`zscore`) will be used.
|
| 19 |
+
|
| 20 |
+
Here is a list of currently available normalization schemes:
|
| 21 |
+
|
| 22 |
+
- `CT`: Perform CT normalization. Specifically, collect intensity values from the foreground classes (all but the
|
| 23 |
+
background and ignore) from all training cases, compute the mean, standard deviation as well as the 0.5 and
|
| 24 |
+
99.5 percentile of the values. Then clip to the percentiles, followed by subtraction of the mean and division with the
|
| 25 |
+
standard deviation. The normalization that is applied is the same for each training case (for this input channel).
|
| 26 |
+
The values used by nnU-Net for normalization are stored in the `foreground_intensity_properties_per_channel` entry in the
|
| 27 |
+
corresponding plans file. This normalization is suitable for modalities presenting physical quantities such as CT
|
| 28 |
+
images and ADC maps.
|
| 29 |
+
- `noNorm` : do not perform any normalization at all
|
| 30 |
+
- `rescale_to_0_1`: rescale the intensities to [0, 1]
|
| 31 |
+
- `rgb_to_0_1`: assumes uint8 inputs. Divides by 255 to rescale uint8 to [0, 1]
|
| 32 |
+
- `zscore`/anything else: perform z-scoring (subtract mean and standard deviation) separately for each train case
|
| 33 |
+
|
| 34 |
+
**Important:** The nnU-Net default is to perform 'CT' normalization for CT images and 'zscore' for everything else! If
|
| 35 |
+
you deviate from that path, make sure to benchmark whether that actually improves results!
|
| 36 |
+
|
| 37 |
+
# How to implement custom normalization strategies?
|
| 38 |
+
- Head over to nnunetv2/preprocessing/normalization
|
| 39 |
+
- implement a new image normalization class by deriving from ImageNormalization
|
| 40 |
+
- register it in nnunetv2/preprocessing/normalization/map_channel_name_to_normalization.py:channel_name_to_normalization_mapping.
|
| 41 |
+
This is where you specify a channel name that should be associated with it
|
| 42 |
+
- use it by specifying the correct channel_name
|
| 43 |
+
|
| 44 |
+
Normalization can only be applied to one channel at a time. There is currently no way of implementing a normalization scheme
|
| 45 |
+
that gets multiple channels as input to be used jointly!
|
nnUNet/documentation/explanation_plans_files.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modifying the nnU-Net Configurations
|
| 2 |
+
|
| 3 |
+
nnU-Net provides unprecedented out-of-the-box segmentation performance for essentially any dataset we have evaluated
|
| 4 |
+
it on. That said, there is always room for improvements. A fool-proof strategy for squeezing out the last bit of
|
| 5 |
+
performance is to start with the default nnU-Net, and then further tune it manually to a concrete dataset at hand.
|
| 6 |
+
**This guide is about changes to the nnU-Net configuration you can make via the plans files. It does not cover code
|
| 7 |
+
extensions of nnU-Net. For that, take a look [here](extending_nnunet.md)**
|
| 8 |
+
|
| 9 |
+
In nnU-Net V2, plans files are SO MUCH MORE powerful than they were in v1. There are a lot more knobs that you can
|
| 10 |
+
turn without resorting to hacky solutions or even having to touch the nnU-Net code at all! And as an added bonus:
|
| 11 |
+
plans files are now also .json files and no longer require users to fiddle with pickle. Just open them in your text
|
| 12 |
+
editor of choice!
|
| 13 |
+
|
| 14 |
+
If overwhelmed, look at our [Examples](#examples)!
|
| 15 |
+
|
| 16 |
+
# plans.json structure
|
| 17 |
+
|
| 18 |
+
Plans have global and local settings. Global settings are applied to all configurations in that plans file while
|
| 19 |
+
local settings are attached to a specific configuration.
|
| 20 |
+
|
| 21 |
+
## Global settings
|
| 22 |
+
|
| 23 |
+
- `foreground_intensity_properties_by_modality`: Intensity statistics of the foreground regions (all labels except
|
| 24 |
+
background and ignore label), computed over all training cases. Used by [CT normalization scheme](explanation_normalization.md).
|
| 25 |
+
- `image_reader_writer`: Name of the image reader/writer class that should be used with this dataset. You might want
|
| 26 |
+
to change this if, for example, you would like to run inference with files that have a different file format. The
|
| 27 |
+
class that is named here must be located in nnunetv2.imageio!
|
| 28 |
+
- `label_manager`: The name of the class that does label handling. Take a look at
|
| 29 |
+
nnunetv2.utilities.label_handling.LabelManager to see what it does. If you decide to change it, place your version
|
| 30 |
+
in nnunetv2.utilities.label_handling!
|
| 31 |
+
- `transpose_forward`: nnU-Net transposes the input data so that the axes with the highest resolution (lowest spacing)
|
| 32 |
+
come last. This is because the 2D U-Net operates on the trailing dimensions (more efficient slicing due to internal
|
| 33 |
+
memory layout of arrays). Future work might move this setting to affect only individual configurations.
|
| 34 |
+
- transpose_backward is what numpy.transpose gets as new axis ordering.
|
| 35 |
+
- `transpose_backward`: the axis ordering that inverts "transpose_forward"
|
| 36 |
+
- \[`original_median_shape_after_transp`\]: just here for your information
|
| 37 |
+
- \[`original_median_spacing_after_transp`\]: just here for your information
|
| 38 |
+
- \[`plans_name`\]: do not change. Used internally
|
| 39 |
+
- \[`experiment_planner_used`\]: just here as metadata so that we know what planner originally generated this file
|
| 40 |
+
- \[`dataset_name`\]: do not change. This is the dataset these plans are intended for
|
| 41 |
+
|
| 42 |
+
## Local settings
|
| 43 |
+
Plans also have a `configurations` key in which the actual configurations are stored. `configurations` are again a
|
| 44 |
+
dictionary, where the keys are the configuration names and the values are the local settings for each configuration.
|
| 45 |
+
|
| 46 |
+
To better understand the components describing the network topology in our plans files, please read section 6.2
|
| 47 |
+
in the [supplementary information](https://static-content.springer.com/esm/art%3A10.1038%2Fs41592-020-01008-z/MediaObjects/41592_2020_1008_MOESM1_ESM.pdf)
|
| 48 |
+
(page 13) of our paper!
|
| 49 |
+
|
| 50 |
+
Local settings:
|
| 51 |
+
- `spacing`: the target spacing used in this configuration
|
| 52 |
+
- `patch_size`: the patch size used for training this configuration
|
| 53 |
+
- `data_identifier`: the preprocessed data for this configuration will be saved in
|
| 54 |
+
nnUNet_preprocessed/DATASET_NAME/_data_identifier_. If you add a new configuration, remember to set a unique
|
| 55 |
+
data_identifier in order to not create conflicts with other configurations (unless you plan to reuse the data from
|
| 56 |
+
another configuration, for example as is done in the cascade)
|
| 57 |
+
- `batch_size`: batch size used for training
|
| 58 |
+
- `batch_dice`: whether to use batch dice (pretend all samples in the batch are one image, compute dice loss over that)
|
| 59 |
+
or not (each sample in the batch is a separate image, compute dice loss for each sample and average over samples)
|
| 60 |
+
- `preprocessor_name`: Name of the preprocessor class used for running preprocessing. Class must be located in
|
| 61 |
+
nnunetv2.preprocessing.preprocessors
|
| 62 |
+
- `use_mask_for_norm`: whether to use the nonzero mask for normalization or not (relevant for BraTS and the like,
|
| 63 |
+
probably False for all other datasets). Interacts with ImageNormalization class
|
| 64 |
+
- `normalization_schemes`: mapping of channel identifier to ImageNormalization class name. ImageNormalization
|
| 65 |
+
classes must be located in nnunetv2.preprocessing.normalization. Also see [here](explanation_normalization.md)
|
| 66 |
+
- `resampling_fn_data`: name of resampling function to be used for resizing image data. resampling function must be
|
| 67 |
+
callable(data, current_spacing, new_spacing, **kwargs). It must be located in nnunetv2.preprocessing.resampling
|
| 68 |
+
- `resampling_fn_data_kwargs`: kwargs for resampling_fn_data
|
| 69 |
+
- `resampling_fn_probabilities`: name of resampling function to be used for resizing predicted class probabilities/logits.
|
| 70 |
+
resampling function must be `callable(data: Union[np.ndarray, torch.Tensor], current_spacing, new_spacing, **kwargs)`. It must be located in
|
| 71 |
+
nnunetv2.preprocessing.resampling
|
| 72 |
+
- `resampling_fn_probabilities_kwargs`: kwargs for resampling_fn_probabilities
|
| 73 |
+
- `resampling_fn_seg`: name of resampling function to be used for resizing segmentation maps (integer: 0, 1, 2, 3, etc).
|
| 74 |
+
resampling function must be callable(data, current_spacing, new_spacing, **kwargs). It must be located in
|
| 75 |
+
nnunetv2.preprocessing.resampling
|
| 76 |
+
- `resampling_fn_seg_kwargs`: kwargs for resampling_fn_seg
|
| 77 |
+
- `network_arch_class_name`: UNet class name, can be used to integrate custom dynamic architectures
|
| 78 |
+
- `UNet_base_num_features`: The number of starting features for the UNet architecture. Default is 32. Default: Features
|
| 79 |
+
are doubled with each downsampling
|
| 80 |
+
- `unet_max_num_features`: Maximum number of features (default: capped at 320 for 3D and 512 for 2d). The purpose is to
|
| 81 |
+
prevent parameters from exploding too much.
|
| 82 |
+
- `conv_kernel_sizes`: the convolutional kernel sizes used by nnU-Net in each stage of the encoder. The decoder
|
| 83 |
+
mirrors the encoder and is therefore not explicitly listed here! The list is as long as `n_conv_per_stage_encoder` has
|
| 84 |
+
entries
|
| 85 |
+
- `n_conv_per_stage_encoder`: number of convolutions used per stage (=at a feature map resolution in the encoder) in the encoder.
|
| 86 |
+
Default is 2. The list has as many entries as the encoder has stages
|
| 87 |
+
- `n_conv_per_stage_decoder`: number of convolutions used per stage in the decoder. Also see `n_conv_per_stage_encoder`
|
| 88 |
+
- `num_pool_per_axis`: number of times each of the spatial axes is pooled in the network. Needed to know how to pad
|
| 89 |
+
image sizes during inference (num_pool = 5 means input must be divisible by 2**5=32)
|
| 90 |
+
- `pool_op_kernel_sizes`: the pooling kernel sizes (and at the same time strides) for each stage of the encoder
|
| 91 |
+
- \[`median_image_size_in_voxels`\]: the median size of the images of the training set at the current target spacing.
|
| 92 |
+
Do not modify this as this is not used. It is just here for your information.
|
| 93 |
+
|
| 94 |
+
Special local settings:
|
| 95 |
+
- `inherits_from`: configurations can inherit from each other. This makes it easy to add new configurations that only
|
| 96 |
+
differ in a few local settings from another. If using this, remember to set a new `data_identifier` (if needed)!
|
| 97 |
+
- `previous_stage`: if this configuration is part of a cascade, we need to know what the previous stage (for example
|
| 98 |
+
the low resolution configuration) was. This needs to be specified here.
|
| 99 |
+
- `next_stage`: if this configuration is part of a cascade, we need to know what possible subsequent stages are! This
|
| 100 |
+
is because we need to export predictions in the correct spacing when running the validation. `next_stage` can either
|
| 101 |
+
be a string or a list of strings
|
| 102 |
+
|
| 103 |
+
# Examples
|
| 104 |
+
|
| 105 |
+
## Increasing the batch size for large datasets
|
| 106 |
+
If your dataset is large the training can benefit from larger batch_sizes. To do this, simply create a new
|
| 107 |
+
configuration in the `configurations` dict
|
| 108 |
+
|
| 109 |
+
"configurations": {
|
| 110 |
+
"3d_fullres_bs40": {
|
| 111 |
+
"inherits_from": "3d_fullres",
|
| 112 |
+
"batch_size": 40
|
| 113 |
+
}
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
No need to change the data_identifier. `3d_fullres_bs40` will just use the preprocessed data from `3d_fullres`.
|
| 117 |
+
No need to rerun `nnUNetv2_preprocess` because we can use already existing data (if available) from `3d_fullres`.
|
| 118 |
+
|
| 119 |
+
## Using custom preprocessors
|
| 120 |
+
If you would like to use a different preprocessor class then this can be specified as follows:
|
| 121 |
+
|
| 122 |
+
"configurations": {
|
| 123 |
+
"3d_fullres_my_preprocesor": {
|
| 124 |
+
"inherits_from": "3d_fullres",
|
| 125 |
+
"preprocessor_name": MY_PREPROCESSOR,
|
| 126 |
+
"data_identifier": "3d_fullres_my_preprocesor"
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
You need to run preprocessing for this new configuration:
|
| 131 |
+
`nnUNetv2_preprocess -d DATASET_ID -c 3d_fullres_my_preprocesor` because it changes the preprocessing. Remember to
|
| 132 |
+
set a unique `data_identifier` whenever you make modifications to the preprocessed data!
|
| 133 |
+
|
| 134 |
+
## Change target spacing
|
| 135 |
+
|
| 136 |
+
"configurations": {
|
| 137 |
+
"3d_fullres_my_spacing": {
|
| 138 |
+
"inherits_from": "3d_fullres",
|
| 139 |
+
"spacing": [X, Y, Z],
|
| 140 |
+
"data_identifier": "3d_fullres_my_spacing"
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
You need to run preprocessing for this new configuration:
|
| 145 |
+
`nnUNetv2_preprocess -d DATASET_ID -c 3d_fullres_my_spacing` because it changes the preprocessing. Remember to
|
| 146 |
+
set a unique `data_identifier` whenever you make modifications to the preprocessed data!
|
| 147 |
+
|
| 148 |
+
## Adding a cascade to a dataset where it does not exist
|
| 149 |
+
Hippocampus is small. It doesn't have a cascade. It also doesn't really make sense to add a cascade here but hey for
|
| 150 |
+
the sake of demonstration we can do that.
|
| 151 |
+
We change the following things here:
|
| 152 |
+
|
| 153 |
+
- `spacing`: The lowres stage should operate at a lower resolution
|
| 154 |
+
- we modify the `median_image_size_in_voxels` entry as a guide for what original image sizes we deal with
|
| 155 |
+
- we set some patch size that is inspired by `median_image_size_in_voxels`
|
| 156 |
+
- we need to remember that the patch size must be divisible by 2**num_pool in each axis!
|
| 157 |
+
- network parameters such as kernel sizes, pooling operations are changed accordingly
|
| 158 |
+
- we need to specify the name of the next stage
|
| 159 |
+
- we need to add the highres stage
|
| 160 |
+
|
| 161 |
+
This is how this would look like (comparisons with 3d_fullres given as reference):
|
| 162 |
+
|
| 163 |
+
"configurations": {
|
| 164 |
+
"3d_lowres": {
|
| 165 |
+
"inherits_from": "3d_fullres",
|
| 166 |
+
"data_identifier": "3d_lowres"
|
| 167 |
+
"spacing": [2.0, 2.0, 2.0], # from [1.0, 1.0, 1.0] in 3d_fullres
|
| 168 |
+
"median_image_size_in_voxels": [18, 25, 18], # from [36, 50, 35]
|
| 169 |
+
"patch_size": [20, 28, 20], # from [40, 56, 40]
|
| 170 |
+
"n_conv_per_stage_encoder": [2, 2, 2], # one less entry than 3d_fullres ([2, 2, 2, 2])
|
| 171 |
+
"n_conv_per_stage_decoder": [2, 2], # one less entry than 3d_fullres
|
| 172 |
+
"num_pool_per_axis": [2, 2, 2], # one less pooling than 3d_fullres in each dimension (3d_fullres: [3, 3, 3])
|
| 173 |
+
"pool_op_kernel_sizes": [[1, 1, 1], [2, 2, 2], [2, 2, 2]], # one less [2, 2, 2]
|
| 174 |
+
"conv_kernel_sizes": [[3, 3, 3], [3, 3, 3], [3, 3, 3]], # one less [3, 3, 3]
|
| 175 |
+
"next_stage": "3d_cascade_fullres" # name of the next stage in the cascade
|
| 176 |
+
},
|
| 177 |
+
"3d_cascade_fullres": { # does not need a data_identifier because we can use the data of 3d_fullres
|
| 178 |
+
"inherits_from": "3d_fullres",
|
| 179 |
+
"previous_stage": "3d_lowres" # name of the previous stage
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
To better understand the components describing the network topology in our plans files, please read section 6.2
|
| 184 |
+
in the [supplementary information](https://static-content.springer.com/esm/art%3A10.1038%2Fs41592-020-01008-z/MediaObjects/41592_2020_1008_MOESM1_ESM.pdf)
|
| 185 |
+
(page 13) of our paper!
|
nnUNet/documentation/extending_nnunet.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Extending nnU-Net
|
| 2 |
+
We hope that the new structure of nnU-Net v2 makes it much more intuitive on how to modify it! We cannot give an
|
| 3 |
+
extensive tutorial on how each and every bit of it can be modified. It is better for you to search for the position
|
| 4 |
+
in the repository where the thing you intend to change is implemented and start working your way through the code from
|
| 5 |
+
there. Setting breakpoints and debugging into nnU-Net really helps in understanding it and thus will help you make the
|
| 6 |
+
necessary modifications!
|
| 7 |
+
|
| 8 |
+
Here are some things you might want to read before you start:
|
| 9 |
+
- Editing nnU-Net configurations through plans files is really powerful now and allows you to change a lot of things regarding
|
| 10 |
+
preprocessing, resampling, network topology etc. Read [this](explanation_plans_files.md)!
|
| 11 |
+
- [Image normalization](explanation_normalization.md) and [i/o formats](dataset_format.md#supported-file-formats) are easy to extend!
|
| 12 |
+
- Manual data splits can be defined as described [here](manual_data_splits.md)
|
| 13 |
+
- You can chain arbitrary configurations together into cascades, see [this again](explanation_plans_files.md)
|
| 14 |
+
- Read about our support for [region-based training](region_based_training.md)
|
| 15 |
+
- If you intend to modify the training procedure (loss, sampling, data augmentation, lr scheduler, etc) then you need
|
| 16 |
+
to implement your own trainer class. Best practice is to create a class that inherits from nnUNetTrainer and
|
| 17 |
+
implements the necessary changes. Head over to our [trainer classes folder](../nnunetv2/training/nnUNetTrainer) for
|
| 18 |
+
inspiration! There will be similar trainers for what you intend to change and you can take them as a guide. nnUNetTrainer
|
| 19 |
+
are structured similarly to PyTorch lightning trainers, this should also make things easier!
|
| 20 |
+
- Integrating new network architectures can be done in two ways:
|
| 21 |
+
- Quick and dirty: implement a new nnUNetTrainer class and overwrite its `build_network_architecture` function.
|
| 22 |
+
Make sure your architecture is compatible with deep supervision (if not, use `nnUNetTrainerNoDeepSupervision`
|
| 23 |
+
as basis!) and that it can handle the patch sizes that are thrown at it! Your architecture should NOT apply any
|
| 24 |
+
nonlinearities at the end (softmax, sigmoid etc). nnU-Net does that!
|
| 25 |
+
- The 'proper' (but difficult) way: Build a dynamically configurable architecture such as the `PlainConvUNet` class
|
| 26 |
+
used by default. It needs to have some sort of GPU memory estimation method that can be used to evaluate whether
|
| 27 |
+
certain patch sizes and
|
| 28 |
+
topologies fit into a specified GPU memory target. Build a new `ExperimentPlanner` that can configure your new
|
| 29 |
+
class and communicate with its memory budget estimation. Run `nnUNetv2_plan_and_preprocess` while specifying your
|
| 30 |
+
custom `ExperimentPlanner` and a custom `plans_name`. Implement a nnUNetTrainer that can use the plans generated by
|
| 31 |
+
your `ExperimentPlanner` to instantiate the network architecture. Specify your plans and trainer when running `nnUNetv2_train`.
|
| 32 |
+
It always pays off to first read and understand the corresponding nnU-Net code and use it as a template for your implementation!
|
| 33 |
+
- Remember that multi-GPU training, region-based training, ignore label and cascaded training are now simply integrated
|
| 34 |
+
into one unified nnUNetTrainer class. No separate classes needed (remember that when implementing your own trainer
|
| 35 |
+
classes and ensure support for all of these features! Or raise `NotImplementedError`)
|
| 36 |
+
|
| 37 |
+
[//]: # (- Read about our support for [ignore label](ignore_label.md) and [region-based training](region_based_training.md))
|
nnUNet/documentation/how_to_use_nnunet.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## **2024-04-18 UPDATE: New residual encoder UNet presets available!**
|
| 2 |
+
The recommended nnU-Net presets have changed! See [here](resenc_presets.md) how to unlock them!
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
## How to run nnU-Net on a new dataset
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
Given some dataset, nnU-Net fully automatically configures an entire segmentation pipeline that matches its properties.
|
| 9 |
+
nnU-Net covers the entire pipeline, from preprocessing to model configuration, model training, postprocessing
|
| 10 |
+
all the way to ensembling. After running nnU-Net, the trained model(s) can be applied to the test cases for inference.
|
| 11 |
+
|
| 12 |
+
### Dataset Format
|
| 13 |
+
nnU-Net expects datasets in a structured format. This format is inspired by the data structure of
|
| 14 |
+
the [Medical Segmentation Decthlon](http://medicaldecathlon.com/). Please read
|
| 15 |
+
[this](dataset_format.md) for information on how to set up datasets to be compatible with nnU-Net.
|
| 16 |
+
|
| 17 |
+
**Since version 2 we support multiple image file formats (.nii.gz, .png, .tif, ...)! Read the dataset_format
|
| 18 |
+
documentation to learn more!**
|
| 19 |
+
|
| 20 |
+
**Datasets from nnU-Net v1 can be converted to V2 by running `nnUNetv2_convert_old_nnUNet_dataset INPUT_FOLDER
|
| 21 |
+
OUTPUT_DATASET_NAME`.** Remember that v2 calls datasets DatasetXXX_Name (not Task) where XXX is a 3-digit number.
|
| 22 |
+
Please provide the **path** to the old task, not just the Task name. nnU-Net V2 doesn't know where v1 tasks were!
|
| 23 |
+
|
| 24 |
+
### Experiment planning and preprocessing
|
| 25 |
+
Given a new dataset, nnU-Net will extract a dataset fingerprint (a set of dataset-specific properties such as
|
| 26 |
+
image sizes, voxel spacings, intensity information etc). This information is used to design three U-Net configurations.
|
| 27 |
+
Each of these pipelines operates on its own preprocessed version of the dataset.
|
| 28 |
+
|
| 29 |
+
The easiest way to run fingerprint extraction, experiment planning and preprocessing is to use:
|
| 30 |
+
|
| 31 |
+
```bash
|
| 32 |
+
nnUNetv2_plan_and_preprocess -d DATASET_ID --verify_dataset_integrity
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Where `DATASET_ID` is the dataset id (duh). We recommend `--verify_dataset_integrity` whenever it's the first time
|
| 36 |
+
you run this command. This will check for some of the most common error sources! Fingerprint extraction and
|
| 37 |
+
preprocessing show a progress bar by default. Use `--no_pbar` if you want to suppress it, for example in
|
| 38 |
+
non-interactive cluster environments.
|
| 39 |
+
|
| 40 |
+
You can also process several datasets at once by giving `-d 1 2 3 [...]`. If you already know what U-Net configuration
|
| 41 |
+
you need you can also specify that with `-c 3d_fullres` (make sure to adapt -np in this case!). For more information
|
| 42 |
+
about all the options available to you please run `nnUNetv2_plan_and_preprocess -h`.
|
| 43 |
+
|
| 44 |
+
nnUNetv2_plan_and_preprocess will create a new subfolder in your nnUNet_preprocessed folder named after the dataset.
|
| 45 |
+
Once the command is completed there will be a dataset_fingerprint.json file as well as a nnUNetPlans.json file for you to look at
|
| 46 |
+
(in case you are interested!). There will also be subfolders containing the preprocessed data for your UNet configurations.
|
| 47 |
+
|
| 48 |
+
[Optional]
|
| 49 |
+
If you prefer to keep things separate, you can also use `nnUNetv2_extract_fingerprint`, `nnUNetv2_plan_experiment`
|
| 50 |
+
and `nnUNetv2_preprocess` (in that order). `nnUNetv2_extract_fingerprint` and `nnUNetv2_preprocess` also support
|
| 51 |
+
`--no_pbar`.
|
| 52 |
+
|
| 53 |
+
### Model training
|
| 54 |
+
#### Overview
|
| 55 |
+
You pick which configurations (2d, 3d_fullres, 3d_lowres, 3d_cascade_fullres) should be trained! If you have no idea
|
| 56 |
+
what performs best on your data, just run all of them and let nnU-Net identify the best one. It's up to you!
|
| 57 |
+
|
| 58 |
+
nnU-Net trains all configurations in a 5-fold cross-validation over the training cases. This is 1) needed so that
|
| 59 |
+
nnU-Net can estimate the performance of each configuration and tell you which one should be used for your
|
| 60 |
+
segmentation problem and 2) a natural way of obtaining a good model ensemble (average the output of these 5 models
|
| 61 |
+
for prediction) to boost performance.
|
| 62 |
+
|
| 63 |
+
You can influence the splits nnU-Net uses for 5-fold cross-validation (see [here](manual_data_splits.md)). If you
|
| 64 |
+
prefer to train a single model on all training cases, this is also possible (see below).
|
| 65 |
+
|
| 66 |
+
**Note that not all U-Net configurations are created for all datasets. In datasets with small image sizes, the U-Net
|
| 67 |
+
cascade (and with it the 3d_lowres configuration) is omitted because the patch size of the full resolution U-Net
|
| 68 |
+
already covers a large part of the input images.**
|
| 69 |
+
|
| 70 |
+
Training models is done with the `nnUNetv2_train` command. The general structure of the command is:
|
| 71 |
+
```bash
|
| 72 |
+
nnUNetv2_train DATASET_NAME_OR_ID UNET_CONFIGURATION FOLD [additional options, see -h]
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
UNET_CONFIGURATION is a string that identifies the requested U-Net configuration (defaults: 2d, 3d_fullres, 3d_lowres,
|
| 76 |
+
3d_cascade_lowres). DATASET_NAME_OR_ID specifies what dataset should be trained on and FOLD specifies which fold of
|
| 77 |
+
the 5-fold-cross-validation is trained.
|
| 78 |
+
|
| 79 |
+
nnU-Net stores a checkpoint every 50 epochs. If you need to continue a previous training, just add a `--c` to the
|
| 80 |
+
training command.
|
| 81 |
+
|
| 82 |
+
IMPORTANT: If you plan to use `nnUNetv2_find_best_configuration` (see below) add the `--npz` flag. This makes
|
| 83 |
+
nnU-Net save the softmax outputs during the final validation. They are needed for that. Exported softmax
|
| 84 |
+
predictions are very large and therefore can take up a lot of disk space, which is why this is not enabled by default.
|
| 85 |
+
If you ran initially without the `--npz` flag but now require the softmax predictions, simply rerun the validation with:
|
| 86 |
+
```bash
|
| 87 |
+
nnUNetv2_train DATASET_NAME_OR_ID UNET_CONFIGURATION FOLD --val --npz
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
You can specify the device nnU-net should use by using `-device DEVICE`. DEVICE can only be cpu, cuda or mps. If
|
| 91 |
+
you have multiple GPUs, please select the gpu id using `CUDA_VISIBLE_DEVICES=X nnUNetv2_train [...]` (requires device to be cuda).
|
| 92 |
+
|
| 93 |
+
See `nnUNetv2_train -h` for additional options.
|
| 94 |
+
|
| 95 |
+
### 2D U-Net
|
| 96 |
+
For FOLD in [0, 1, 2, 3, 4], run:
|
| 97 |
+
```bash
|
| 98 |
+
nnUNetv2_train DATASET_NAME_OR_ID 2d FOLD [--npz]
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### 3D full resolution U-Net
|
| 102 |
+
For FOLD in [0, 1, 2, 3, 4], run:
|
| 103 |
+
```bash
|
| 104 |
+
nnUNetv2_train DATASET_NAME_OR_ID 3d_fullres FOLD [--npz]
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
### 3D U-Net cascade
|
| 108 |
+
#### 3D low resolution U-Net
|
| 109 |
+
For FOLD in [0, 1, 2, 3, 4], run:
|
| 110 |
+
```bash
|
| 111 |
+
nnUNetv2_train DATASET_NAME_OR_ID 3d_lowres FOLD [--npz]
|
| 112 |
+
```
|
| 113 |
+
|
| 114 |
+
#### 3D full resolution U-Net
|
| 115 |
+
For FOLD in [0, 1, 2, 3, 4], run:
|
| 116 |
+
```bash
|
| 117 |
+
nnUNetv2_train DATASET_NAME_OR_ID 3d_cascade_fullres FOLD [--npz]
|
| 118 |
+
```
|
| 119 |
+
**Note that the 3D full resolution U-Net of the cascade requires the five folds of the low resolution U-Net to be
|
| 120 |
+
completed!**
|
| 121 |
+
|
| 122 |
+
The trained models will be written to the nnUNet_results folder. Each training obtains an automatically generated
|
| 123 |
+
output folder name:
|
| 124 |
+
|
| 125 |
+
nnUNet_results/DatasetXXX_MYNAME/TRAINER_CLASS_NAME__PLANS_NAME__CONFIGURATION/FOLD
|
| 126 |
+
|
| 127 |
+
For Dataset002_Heart (from the MSD), for example, this looks like this:
|
| 128 |
+
|
| 129 |
+
nnUNet_results/
|
| 130 |
+
βββ Dataset002_Heart
|
| 131 |
+
βββ nnUNetTrainer__nnUNetPlans__2d
|
| 132 |
+
β βββ fold_0
|
| 133 |
+
β βββ fold_1
|
| 134 |
+
β βββ fold_2
|
| 135 |
+
β βββ fold_3
|
| 136 |
+
β βββ fold_4
|
| 137 |
+
β βββ dataset.json
|
| 138 |
+
β βββ dataset_fingerprint.json
|
| 139 |
+
β βββ plans.json
|
| 140 |
+
βββ nnUNetTrainer__nnUNetPlans__3d_fullres
|
| 141 |
+
βββ fold_0
|
| 142 |
+
βββ fold_1
|
| 143 |
+
βββ fold_2
|
| 144 |
+
βββ fold_3
|
| 145 |
+
βββ fold_4
|
| 146 |
+
βββ dataset.json
|
| 147 |
+
βββ dataset_fingerprint.json
|
| 148 |
+
βββ plans.json
|
| 149 |
+
|
| 150 |
+
Note that 3d_lowres and 3d_cascade_fullres do not exist here because this dataset did not trigger the cascade. In each
|
| 151 |
+
model training output folder (each of the fold_x folder), the following files will be created:
|
| 152 |
+
- debug.json: Contains a summary of blueprint and inferred parameters used for training this model as well as a
|
| 153 |
+
bunch of additional stuff. Not easy to read, but very useful for debugging ;-)
|
| 154 |
+
- checkpoint_best.pth: checkpoint files of the best model identified during training. Not used right now unless you
|
| 155 |
+
explicitly tell nnU-Net to use it.
|
| 156 |
+
- checkpoint_final.pth: checkpoint file of the final model (after training has ended). This is what is used for both
|
| 157 |
+
validation and inference.
|
| 158 |
+
- network_architecture.pdf (only if hiddenlayer is installed!): a pdf document with a figure of the network architecture in it.
|
| 159 |
+
- progress.png: Shows losses, pseudo dice, learning rate and epoch times ofer the course of the training. At the top is
|
| 160 |
+
a plot of the training (blue) and validation (red) loss during training. Also shows an approximation of
|
| 161 |
+
the dice (green) as well as a moving average of it (dotted green line). This approximation is the average Dice score
|
| 162 |
+
of the foreground classes. **It needs to be taken with a big (!)
|
| 163 |
+
grain of salt** because it is computed on randomly drawn patches from the validation
|
| 164 |
+
data at the end of each epoch, and the aggregation of TP, FP and FN for the Dice computation treats the patches as if
|
| 165 |
+
they all originate from the same volume ('global Dice'; we do not compute a Dice for each validation case and then
|
| 166 |
+
average over all cases but pretend that there is only one validation case from which we sample patches). The reason for
|
| 167 |
+
this is that the 'global Dice' is easy to compute during training and is still quite useful to evaluate whether a model
|
| 168 |
+
is training at all or not. A proper validation takes way too long to be done each epoch. It is run at the end of the training.
|
| 169 |
+
- validation: in this folder are the predicted validation cases after the training has finished. The summary.json file in here
|
| 170 |
+
contains the validation metrics (a mean over all cases is provided at the start of the file). If `--npz` was set then
|
| 171 |
+
the compressed softmax outputs (saved as .npz files) are in here as well.
|
| 172 |
+
|
| 173 |
+
During training it is often useful to watch the progress. We therefore recommend that you have a look at the generated
|
| 174 |
+
progress.png when running the first training. It will be updated after each epoch.
|
| 175 |
+
|
| 176 |
+
Training times largely depend on the GPU. The smallest GPU we recommend for training is the Nvidia RTX 2080ti. With
|
| 177 |
+
that all network trainings take less than 2 days. Refer to our [benchmarks](benchmarking.md) to see if your system is
|
| 178 |
+
performing as expected.
|
| 179 |
+
|
| 180 |
+
### Using multiple GPUs for training
|
| 181 |
+
|
| 182 |
+
If multiple GPUs are at your disposal, the best way of using them is to train multiple nnU-Net trainings at once, one
|
| 183 |
+
on each GPU. This is because data parallelism never scales perfectly linearly, especially not with small networks such
|
| 184 |
+
as the ones used by nnU-Net.
|
| 185 |
+
|
| 186 |
+
Example:
|
| 187 |
+
|
| 188 |
+
```bash
|
| 189 |
+
CUDA_VISIBLE_DEVICES=0 nnUNetv2_train DATASET_NAME_OR_ID 2d 0 [--npz] & # train on GPU 0
|
| 190 |
+
CUDA_VISIBLE_DEVICES=1 nnUNetv2_train DATASET_NAME_OR_ID 2d 1 [--npz] & # train on GPU 1
|
| 191 |
+
CUDA_VISIBLE_DEVICES=2 nnUNetv2_train DATASET_NAME_OR_ID 2d 2 [--npz] & # train on GPU 2
|
| 192 |
+
CUDA_VISIBLE_DEVICES=3 nnUNetv2_train DATASET_NAME_OR_ID 2d 3 [--npz] & # train on GPU 3
|
| 193 |
+
CUDA_VISIBLE_DEVICES=4 nnUNetv2_train DATASET_NAME_OR_ID 2d 4 [--npz] & # train on GPU 4
|
| 194 |
+
...
|
| 195 |
+
wait
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
**Important: The first time a training is run nnU-Net will extract the preprocessed data into uncompressed numpy
|
| 199 |
+
arrays for speed reasons! This operation must be completed before starting more than one training of the same
|
| 200 |
+
configuration! Wait with starting subsequent folds until the first training is using the GPU! Depending on the
|
| 201 |
+
dataset size and your System this should only take a couple of minutes at most.**
|
| 202 |
+
|
| 203 |
+
If you insist on running DDP multi-GPU training, we got you covered:
|
| 204 |
+
|
| 205 |
+
`nnUNetv2_train DATASET_NAME_OR_ID 2d 0 [--npz] -num_gpus X`
|
| 206 |
+
|
| 207 |
+
Again, note that this will be slower than running separate training on separate GPUs. DDP only makes sense if you have
|
| 208 |
+
manually interfered with the nnU-Net configuration and are training larger models with larger patch and/or batch sizes!
|
| 209 |
+
|
| 210 |
+
Important when using `-num_gpus`:
|
| 211 |
+
1) If you train using, say, 2 GPUs but have more GPUs in the system you need to specify which GPUs should be used via
|
| 212 |
+
CUDA_VISIBLE_DEVICES=0,1 (or whatever your ids are).
|
| 213 |
+
2) You cannot specify more GPUs than you have samples in your minibatches. If the batch size is 2, 2 GPUs is the maximum!
|
| 214 |
+
3) Make sure your batch size is divisible by the numbers of GPUs you use or you will not make good use of your hardware.
|
| 215 |
+
|
| 216 |
+
In contrast to the old nnU-Net, DDP is now completely hassle free. Enjoy!
|
| 217 |
+
|
| 218 |
+
### Automatically determine the best configuration
|
| 219 |
+
Once the desired configurations were trained (full cross-validation) you can tell nnU-Net to automatically identify
|
| 220 |
+
the best combination for you:
|
| 221 |
+
|
| 222 |
+
```commandline
|
| 223 |
+
nnUNetv2_find_best_configuration DATASET_NAME_OR_ID -c CONFIGURATIONS
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
`CONFIGURATIONS` hereby is the list of configurations you would like to explore. Per default, ensembling is enabled
|
| 227 |
+
meaning that nnU-Net will generate all possible combinations of ensembles (2 configurations per ensemble). This requires
|
| 228 |
+
the .npz files containing the predicted probabilities of the validation set to be present (use `nnUNetv2_train` with
|
| 229 |
+
`--npz` flag, see above). You can disable ensembling by setting the `--disable_ensembling` flag.
|
| 230 |
+
|
| 231 |
+
See `nnUNetv2_find_best_configuration -h` for more options.
|
| 232 |
+
|
| 233 |
+
nnUNetv2_find_best_configuration will also automatically determine the postprocessing that should be used.
|
| 234 |
+
Postprocessing in nnU-Net only considers the removal of all but the largest component in the prediction (once for
|
| 235 |
+
foreground vs background and once for each label/region).
|
| 236 |
+
|
| 237 |
+
Once completed, the command will print to your console exactly what commands you need to run to make predictions. It
|
| 238 |
+
will also create two files in the `nnUNet_results/DATASET_NAME` folder for you to inspect:
|
| 239 |
+
- `inference_instructions.txt` again contains the exact commands you need to use for predictions
|
| 240 |
+
- `inference_information.json` can be inspected to see the performance of all configurations and ensembles, as well
|
| 241 |
+
as the effect of the postprocessing plus some debug information.
|
| 242 |
+
|
| 243 |
+
### Run inference
|
| 244 |
+
Remember that the data located in the input folder must have the file endings as the dataset you trained the model on
|
| 245 |
+
and must adhere to the nnU-Net naming scheme for image files (see [dataset format](dataset_format.md) and
|
| 246 |
+
[inference data format](dataset_format_inference.md)!)
|
| 247 |
+
|
| 248 |
+
`nnUNetv2_find_best_configuration` (see above) will print a string to the terminal with the inference commands you need to use.
|
| 249 |
+
The easiest way to run inference is to simply use these commands.
|
| 250 |
+
|
| 251 |
+
If you wish to manually specify the configuration(s) used for inference, use the following commands:
|
| 252 |
+
|
| 253 |
+
#### Run prediction
|
| 254 |
+
For each of the desired configurations, run:
|
| 255 |
+
```
|
| 256 |
+
nnUNetv2_predict -i INPUT_FOLDER -o OUTPUT_FOLDER -d DATASET_NAME_OR_ID -c CONFIGURATION --save_probabilities
|
| 257 |
+
```
|
| 258 |
+
|
| 259 |
+
Only specify `--save_probabilities` if you intend to use ensembling. `--save_probabilities` will make the command save the predicted
|
| 260 |
+
probabilities alongside of the predicted segmentation masks requiring a lot of disk space.
|
| 261 |
+
|
| 262 |
+
Please select a separate `OUTPUT_FOLDER` for each configuration!
|
| 263 |
+
|
| 264 |
+
Note that per default, inference will be done with all 5 folds from the cross-validation as an ensemble. We very
|
| 265 |
+
strongly recommend you use all 5 folds. Thus, all 5 folds must have been trained prior to running inference.
|
| 266 |
+
|
| 267 |
+
If you wish to make predictions with a single model, train the `all` fold and specify it in `nnUNetv2_predict`
|
| 268 |
+
with `-f all`
|
| 269 |
+
|
| 270 |
+
#### Ensembling multiple configurations
|
| 271 |
+
If you wish to ensemble multiple predictions (typically form different configurations), you can do so with the following command:
|
| 272 |
+
```bash
|
| 273 |
+
nnUNetv2_ensemble -i FOLDER1 FOLDER2 ... -o OUTPUT_FOLDER -np NUM_PROCESSES
|
| 274 |
+
```
|
| 275 |
+
|
| 276 |
+
You can specify an arbitrary number of folders, but remember that each folder needs to contain npz files that were
|
| 277 |
+
generated by `nnUNetv2_predict`. Again, `nnUNetv2_ensemble -h` will tell you more about additional options.
|
| 278 |
+
|
| 279 |
+
#### Apply postprocessing
|
| 280 |
+
Finally, apply the previously determined postprocessing to the (ensembled) predictions:
|
| 281 |
+
|
| 282 |
+
```commandline
|
| 283 |
+
nnUNetv2_apply_postprocessing -i FOLDER_WITH_PREDICTIONS -o OUTPUT_FOLDER --pp_pkl_file POSTPROCESSING_FILE -plans_json PLANS_FILE -dataset_json DATASET_JSON_FILE
|
| 284 |
+
```
|
| 285 |
+
|
| 286 |
+
`nnUNetv2_find_best_configuration` (or its generated `inference_instructions.txt` file) will tell you where to find
|
| 287 |
+
the postprocessing file. If not you can just look for it in your results folder (it's creatively named
|
| 288 |
+
`postprocessing.pkl`). If your source folder is from an ensemble, you also need to specify a `-plans_json` file and
|
| 289 |
+
a `-dataset_json` file that should be used (for single configuration predictions these are automatically copied
|
| 290 |
+
from the respective training). You can pick these files from any of the ensemble members.
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
## How to run inference with pretrained models
|
| 294 |
+
See [here](run_inference_with_pretrained_models.md)
|
| 295 |
+
|
| 296 |
+
## How to Deploy and Run Inference with YOUR Pretrained Models
|
| 297 |
+
To facilitate the use of pretrained models on a different computer for inference purposes, follow these streamlined steps:
|
| 298 |
+
1. Exporting the Model: Utilize the `nnUNetv2_export_model_to_zip` function to package your trained model into a .zip file. This file will contain all necessary model files.
|
| 299 |
+
2. Transferring the Model: Transfer the .zip file to the target computer where inference will be performed.
|
| 300 |
+
3. Importing the Model: On the new PC, use the `nnUNetv2_install_pretrained_model_from_zip` to load the pretrained model from the .zip file.
|
| 301 |
+
Please note that both computers must have nnU-Net installed along with all its dependencies to ensure compatibility and functionality of the model.
|
| 302 |
+
|
| 303 |
+
[//]: # (## Examples)
|
| 304 |
+
|
| 305 |
+
[//]: # ()
|
| 306 |
+
[//]: # (To get you started we compiled two simple to follow examples:)
|
| 307 |
+
|
| 308 |
+
[//]: # (- run a training with the 3d full resolution U-Net on the Hippocampus dataset. See [here](documentation/training_example_Hippocampus.md).)
|
| 309 |
+
|
| 310 |
+
[//]: # (- run inference with nnU-Net's pretrained models on the Prostate dataset. See [here](documentation/inference_example_Prostate.md).)
|
| 311 |
+
|
| 312 |
+
[//]: # ()
|
| 313 |
+
[//]: # (Usability not good enough? Let us know!)
|
nnUNet/documentation/ignore_label.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignore Label
|
| 2 |
+
|
| 3 |
+
The _ignore label_ can be used to mark regions that should be ignored by nnU-Net. This can be used to
|
| 4 |
+
learn from images where only sparse annotations are available, for example in the form of scribbles or a limited
|
| 5 |
+
amount of annotated slices. Internally, this is accomplished by using partial losses, i.e. losses that are only
|
| 6 |
+
computed on annotated pixels while ignoring the rest. Take a look at our
|
| 7 |
+
[`DC_and_BCE_loss` loss](../nnunetv2/training/loss/compound_losses.py) to see how this is done.
|
| 8 |
+
During inference (validation and prediction), nnU-Net will always predict dense segmentations. Metric computation in
|
| 9 |
+
validation is of course only done on annotated pixels.
|
| 10 |
+
|
| 11 |
+
Using sparse annotations can be used to train a model for application to new, unseen images or to autocomplete the
|
| 12 |
+
provided training cases given the sparse labels.
|
| 13 |
+
|
| 14 |
+
(See our [paper](https://arxiv.org/abs/2403.12834) for more information)
|
| 15 |
+
|
| 16 |
+
Typical use-cases for the ignore label are:
|
| 17 |
+
- Save annotation time through sparse annotation schemes
|
| 18 |
+
- Annotation of all or a subset of slices with scribbles (Scribble Supervision)
|
| 19 |
+
- Dense annotation of a subset of slices
|
| 20 |
+
- Dense annotation of chosen patches/cubes within an image
|
| 21 |
+
- Coarsly masking out faulty segmentations in the reference segmentations
|
| 22 |
+
- Masking areas for other reasons
|
| 23 |
+
|
| 24 |
+
If you are using nnU-Net's ignore label, please cite the following paper in addition to the original nnU-net paper:
|
| 25 |
+
|
| 26 |
+
```
|
| 27 |
+
Gotkowski, K., LΓΌth, C., JΓ€ger, P. F., Ziegler, S., KrΓ€mer, L., Denner, S., Xiao, S., Disch, N., H., K., & Isensee, F.
|
| 28 |
+
(2024). Embarrassingly Simple Scribble Supervision for 3D Medical Segmentation. ArXiv. /abs/2403.12834
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## Usecases
|
| 32 |
+
|
| 33 |
+
### Scribble Supervision
|
| 34 |
+
|
| 35 |
+
Scribbles are free-form drawings to coarsly annotate an image. As we have demonstrated in our recent [paper](https://arxiv.org/abs/2403.12834), nnU-Net's partial loss implementation enables state-of-the-art learning from partially annotated data and even surpasses many purpose-built methods for learning from scribbles. As a starting point, for each image slice and each class (including background), an interior and a border scribble should be generated:
|
| 36 |
+
|
| 37 |
+
- Interior Scribble: A scribble placed randomly within the class interior of a class instance
|
| 38 |
+
- Border Scribble: A scribble roughly delineating a small part of the class border of a class instance
|
| 39 |
+
|
| 40 |
+
An example of such scribble annotations is depicted in Figure 1 and an animation in Animation 1.
|
| 41 |
+
Depending on the availability of data and their variability it is also possible to only annotated a subset of selected slices.
|
| 42 |
+
|
| 43 |
+
<p align="center">
|
| 44 |
+
<img src="assets/scribble_example.png" width="1024px" />
|
| 45 |
+
<figcaption>Figure 1: Examples of segmentation types with (A) depicting a dense segmentation and (B) a scribble segmentation.</figcaption>
|
| 46 |
+
</figure>
|
| 47 |
+
</p>
|
| 48 |
+
|
| 49 |
+
<p align="center">
|
| 50 |
+
<img width="512px" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbmdndHQwMG96M3FqZWtwbHR2enUwZXhwNHVsbndzNmNpZnVlbHJ6OSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/KRJ48evmroDlIgcqcO/giphy.gif">
|
| 51 |
+
<img width="512px" src="https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExem10Z3ZqZHQ2MWNsMjdibG1zc3M2NzNqbG9mazdudG5raTk4d3h4MSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ifVxQQfco5ro1gH6bQ/giphy.gif">
|
| 52 |
+
<figcaption>Animation 1: Depiction of a dense segmentation and a scribble annotation. Background scribbles have been excluded for better visualization.</figcaption>
|
| 53 |
+
</p>
|
| 54 |
+
|
| 55 |
+
### Dense annotation of a subset of slices
|
| 56 |
+
|
| 57 |
+
Another form of sparse annotation is the dense annotation of a subset of slices. These slices should be selected by the user either randomly, based on visual class variation between slices or in an active learning setting. An example with only 10% of slices annotated is depicted in Figure 2.
|
| 58 |
+
|
| 59 |
+
<p align="center">
|
| 60 |
+
<img src="assets/amos2022_sparseseg10_2d.png" width="512px" />
|
| 61 |
+
<img src="assets/amos2022_sparseseg10.png" width="512px" />
|
| 62 |
+
<figcaption>Figure 2: Examples of a dense annotation of a subset of slices. The ignored areas are shown in red.</figcaption>
|
| 63 |
+
</figure>
|
| 64 |
+
</p>
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
## Usage within nnU-Net
|
| 68 |
+
|
| 69 |
+
Usage of the ignore label in nnU-Net is straightforward and only requires the definition of an _ignore_ label in the _dataset.json_.
|
| 70 |
+
This ignore label MUST be the highest integer label value in the segmentation. Exemplary, given the classes background and two foreground classes, then the ignore label must have the integer 3. The ignore label must be named _ignore_ in the _dataset.json_. Given the BraTS dataset as an example the labels dict of the _dataset.json_ must look like this:
|
| 71 |
+
|
| 72 |
+
```python
|
| 73 |
+
...
|
| 74 |
+
"labels": {
|
| 75 |
+
"background": 0,
|
| 76 |
+
"edema": 1,
|
| 77 |
+
"non_enhancing_and_necrosis": 2,
|
| 78 |
+
"enhancing_tumor": 3,
|
| 79 |
+
"ignore": 4
|
| 80 |
+
},
|
| 81 |
+
...
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Of course, the ignore label is compatible with [region-based training](region_based_training.md):
|
| 85 |
+
|
| 86 |
+
```python
|
| 87 |
+
...
|
| 88 |
+
"labels": {
|
| 89 |
+
"background": 0,
|
| 90 |
+
"whole_tumor": (1, 2, 3),
|
| 91 |
+
"tumor_core": (2, 3),
|
| 92 |
+
"enhancing_tumor": 3, # or (3, )
|
| 93 |
+
"ignore": 4
|
| 94 |
+
},
|
| 95 |
+
"regions_class_order": (1, 2, 3), # don't declare ignore label here! It is not predicted
|
| 96 |
+
...
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
Then use the dataset as you would any other.
|
| 100 |
+
|
| 101 |
+
Remember that nnU-Net runs a cross-validation. Thus, it will also evaluate on your partially annotated data. This
|
| 102 |
+
will of course work! If you wish to compare different sparse annotation strategies (through simulations for example),
|
| 103 |
+
we recommend evaluating on densely annotated images by running inference and then using `nnUNetv2_evaluate_folder` or
|
| 104 |
+
`nnUNetv2_evaluate_simple`.
|
nnUNet/documentation/installation_instructions.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# System requirements
|
| 2 |
+
|
| 3 |
+
## Operating System
|
| 4 |
+
nnU-Net has been tested on Linux (Ubuntu 18.04, 20.04, 22.04; centOS, RHEL), Windows and MacOS! It should work out of the box!
|
| 5 |
+
|
| 6 |
+
## Hardware requirements
|
| 7 |
+
We support GPU (recommended), CPU and Apple M1/M2 as devices (currently Apple mps does not implement 3D
|
| 8 |
+
convolutions, so you might have to use the CPU on those devices).
|
| 9 |
+
|
| 10 |
+
### Hardware requirements for Training
|
| 11 |
+
We recommend you use a GPU for training as this will take a really long time on CPU or MPS (Apple M1/M2).
|
| 12 |
+
For training a GPU with at least 10 GB (popular non-datacenter options are the RTX 2080ti, RTX 3080/3090 or RTX 4080/4090) is
|
| 13 |
+
required. We also recommend a strong CPU to go along with the GPU. 6 cores (12 threads)
|
| 14 |
+
are the bare minimum! CPU requirements are mostly related to data augmentation and scale with the number of
|
| 15 |
+
input channels and target structures. Plus, the faster the GPU, the better the CPU should be!
|
| 16 |
+
|
| 17 |
+
### Hardware Requirements for inference
|
| 18 |
+
Again we recommend a GPU to make predictions as this will be substantially faster than the other options. However,
|
| 19 |
+
inference times are typically still manageable on CPU and MPS (Apple M1/M2). If using a GPU, it should have at least
|
| 20 |
+
4 GB of available (unused) VRAM.
|
| 21 |
+
|
| 22 |
+
### Example hardware configurations
|
| 23 |
+
Example workstation configurations for training:
|
| 24 |
+
- CPU: Ryzen 5800X - 5900X or 7900X would be even better! We have not yet tested Intel Alder/Raptor lake but they will likely work as well.
|
| 25 |
+
- GPU: RTX 3090 or RTX 4090
|
| 26 |
+
- RAM: 64GB
|
| 27 |
+
- Storage: SSD (M.2 PCIe Gen 3 or better!)
|
| 28 |
+
|
| 29 |
+
Example Server configuration for training:
|
| 30 |
+
- CPU: 2x AMD EPYC7763 for a total of 128C/256T. 16C/GPU are highly recommended for fast GPUs such as the A100!
|
| 31 |
+
- GPU: 8xA100 PCIe (price/performance superior to SXM variant + they use less power)
|
| 32 |
+
- RAM: 1 TB
|
| 33 |
+
- Storage: local SSD storage (PCIe Gen 3 or better) or ultra fast network storage
|
| 34 |
+
|
| 35 |
+
(nnU-net by default uses one GPU per training. The server configuration can run up to 8 model trainings simultaneously)
|
| 36 |
+
|
| 37 |
+
### Setting the correct number of Workers for data augmentation (training only)
|
| 38 |
+
Note that you will need to manually set the number of processes nnU-Net uses for data augmentation according to your
|
| 39 |
+
CPU/GPU ratio. For the server above (256 threads for 8 GPUs), a good value would be 24-30. You can do this by
|
| 40 |
+
setting the `nnUNet_n_proc_DA` environment variable (`export nnUNet_n_proc_DA=XX`).
|
| 41 |
+
Recommended values (assuming a recent CPU with good IPC) are 10-12 for RTX 2080 ti, 12 for a RTX 3090, 16-18 for
|
| 42 |
+
RTX 4090, 28-32 for A100. Optimal values may vary depending on the number of input channels/modalities and number of classes.
|
| 43 |
+
|
| 44 |
+
# Installation instructions
|
| 45 |
+
We strongly recommend that you install nnU-Net in a virtual environment! Pip or anaconda are both fine. If you choose to
|
| 46 |
+
compile PyTorch from source (see below), you will need to use conda instead of pip.
|
| 47 |
+
|
| 48 |
+
Use a recent version of Python! 3.9 or newer is guaranteed to work!
|
| 49 |
+
|
| 50 |
+
**nnU-Net v2 can coexist with nnU-Net v1! Both can be installed at the same time.**
|
| 51 |
+
|
| 52 |
+
1) Install [PyTorch](https://pytorch.org/get-started/locally/) as described on their website (conda/pip). Please
|
| 53 |
+
install the latest version with support for your hardware (cuda, mps, cpu).
|
| 54 |
+
**DO NOT JUST `pip install nnunetv2` WITHOUT PROPERLY INSTALLING PYTORCH FIRST**. For maximum speed, consider
|
| 55 |
+
[compiling pytorch yourself](https://github.com/pytorch/pytorch#from-source) (experienced users only!).
|
| 56 |
+
2) Install nnU-Net depending on your use case:
|
| 57 |
+
1) For use as **standardized baseline**, **out-of-the-box segmentation algorithm** or for running
|
| 58 |
+
**inference with pretrained models**:
|
| 59 |
+
|
| 60 |
+
```pip install nnunetv2```
|
| 61 |
+
|
| 62 |
+
2) For use as integrative **framework** (this will create a copy of the nnU-Net code on your computer so that you
|
| 63 |
+
can modify it as needed):
|
| 64 |
+
```bash
|
| 65 |
+
git clone https://github.com/MIC-DKFZ/nnUNet.git
|
| 66 |
+
cd nnUNet
|
| 67 |
+
pip install -e .
|
| 68 |
+
```
|
| 69 |
+
3) nnU-Net needs to know where you intend to save raw data, preprocessed data and trained models. For this you need to
|
| 70 |
+
set a few environment variables. Please follow the instructions [here](setting_up_paths.md).
|
| 71 |
+
4) (OPTIONAL) Install [hiddenlayer](https://github.com/waleedka/hiddenlayer). hiddenlayer enables nnU-net to generate
|
| 72 |
+
plots of the network topologies it generates (see [Model training](how_to_use_nnunet.md#model-training)).
|
| 73 |
+
To install hiddenlayer,
|
| 74 |
+
run the following command:
|
| 75 |
+
```bash
|
| 76 |
+
pip install --upgrade git+https://github.com/FabianIsensee/hiddenlayer.git
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
Installing nnU-Net will add several new commands to your terminal. These commands are used to run the entire nnU-Net
|
| 80 |
+
pipeline. You can execute them from any location on your system. All nnU-Net commands have the prefix `nnUNetv2_` for
|
| 81 |
+
easy identification.
|
| 82 |
+
|
| 83 |
+
Note that these commands simply execute python scripts. If you installed nnU-Net in a virtual environment, this
|
| 84 |
+
environment must be activated when executing the commands. You can see what scripts/functions are executed by
|
| 85 |
+
checking the project.scripts in the [pyproject.toml](../pyproject.toml) file.
|
| 86 |
+
|
| 87 |
+
All nnU-Net commands have a `-h` option which gives information on how to use them.
|
nnUNet/documentation/manual_data_splits.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# How to generate custom splits in nnU-Net
|
| 2 |
+
|
| 3 |
+
Sometimes, the default 5-fold cross-validation split by nnU-Net does not fit a project. Maybe you want to run 3-fold
|
| 4 |
+
cross-validation instead? Or maybe your training cases cannot be split randomly and require careful stratification.
|
| 5 |
+
Fear not, for nnU-Net has got you covered (it really can do anything <3).
|
| 6 |
+
|
| 7 |
+
The splits nnU-Net uses are generated in the `do_split` function of nnUNetTrainer. This function will first look for
|
| 8 |
+
existing splits, stored as a file, and if no split exists it will create one. So if you wish to influence the split,
|
| 9 |
+
manually creating a split file that will then be recognized and used is the way to go!
|
| 10 |
+
|
| 11 |
+
The split file is located in the `nnUNet_preprocessed/DATASETXXX_NAME` folder. So it is best practice to first
|
| 12 |
+
populate this folder by running `nnUNetv2_plan_and_preproccess`.
|
| 13 |
+
|
| 14 |
+
Splits are stored as a .json file. They are a simple python list. The length of that list is the number of splits it
|
| 15 |
+
contains (so it's 5 in the default nnU-Net). Each list entry is a dictionary with keys 'train' and 'val'. Values are
|
| 16 |
+
again simply lists with the train identifiers in each set. To illustrate this, I am just messing with the Dataset002
|
| 17 |
+
file as an example:
|
| 18 |
+
|
| 19 |
+
```commandline
|
| 20 |
+
In [1]: from batchgenerators.utilities.file_and_folder_operations import load_json
|
| 21 |
+
|
| 22 |
+
In [2]: splits = load_json('splits_final.json')
|
| 23 |
+
|
| 24 |
+
In [3]: len(splits)
|
| 25 |
+
Out[3]: 5
|
| 26 |
+
|
| 27 |
+
In [4]: splits[0].keys()
|
| 28 |
+
Out[4]: dict_keys(['train', 'val'])
|
| 29 |
+
|
| 30 |
+
In [5]: len(splits[0]['train'])
|
| 31 |
+
Out[5]: 16
|
| 32 |
+
|
| 33 |
+
In [6]: len(splits[0]['val'])
|
| 34 |
+
Out[6]: 4
|
| 35 |
+
|
| 36 |
+
In [7]: print(splits[0])
|
| 37 |
+
{'train': ['la_003', 'la_004', 'la_005', 'la_009', 'la_010', 'la_011', 'la_014', 'la_017', 'la_018', 'la_019', 'la_020', 'la_022', 'la_023', 'la_026', 'la_029', 'la_030'],
|
| 38 |
+
'val': ['la_007', 'la_016', 'la_021', 'la_024']}
|
| 39 |
+
```
|
| 40 |
+
|
| 41 |
+
If you are still not sure what splits are supposed to look like, simply download some reference dataset from the
|
| 42 |
+
[Medical Decathlon](http://medicaldecathlon.com/), start some training (to generate the splits) and manually inspect
|
| 43 |
+
the .json file with your text editor of choice!
|
| 44 |
+
|
| 45 |
+
In order to generate your custom splits, all you need to do is reproduce the data structure explained above and save it as
|
| 46 |
+
`splits_final.json` in the `nnUNet_preprocessed/DATASETXXX_NAME` folder. Then use `nnUNetv2_train` etc. as usual.
|
nnUNet/documentation/pretraining_and_finetuning.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Pretraining with nnU-Net
|
| 2 |
+
|
| 3 |
+
## Intro
|
| 4 |
+
|
| 5 |
+
So far nnU-Net only supports supervised pre-training, meaning that you train a regular nnU-Net on some pretraining dataset
|
| 6 |
+
and then use the final network weights as initialization for your target dataset.
|
| 7 |
+
|
| 8 |
+
As a reminder, many training hyperparameters such as patch size and network topology differ between datasets as a
|
| 9 |
+
result of the automated dataset analysis and experiment planning nnU-Net is known for. So, out of the box, it is not
|
| 10 |
+
possible to simply take the network weights from some dataset and then reuse them for another.
|
| 11 |
+
|
| 12 |
+
Consequently, the plans need to be aligned between the two tasks. In this README we show how this can be achieved and
|
| 13 |
+
how the resulting weights can then be used for initialization.
|
| 14 |
+
|
| 15 |
+
### Terminology
|
| 16 |
+
|
| 17 |
+
Throughout this README we use the following terminology:
|
| 18 |
+
|
| 19 |
+
- `pretraining dataset` is the dataset you intend to run the pretraining on
|
| 20 |
+
- `finetuning dataset` is the dataset you are interested in; the one you wish to fine tune on
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
## Training on the pretraining dataset
|
| 24 |
+
|
| 25 |
+
In order to obtain matching network topologies we need to transfer the plans from one dataset to another. Since we are
|
| 26 |
+
only interested in the finetuning dataset, we first need to run experiment planning (and preprocessing) for it:
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
nnUNetv2_plan_and_preprocess -d FINETUNING_DATASET
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
Then we need to extract the dataset fingerprint of the pretraining dataset, if not yet available:
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
nnUNetv2_extract_fingerprint -d PRETRAINING_DATASET
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
Now we can take the plans from the finetuning dataset and transfer it to the pretraining dataset:
|
| 39 |
+
|
| 40 |
+
```bash
|
| 41 |
+
nnUNetv2_move_plans_between_datasets -s FINETUNING_DATASET -t PRETRAINING_DATASET -sp FINETUNING_PLANS_IDENTIFIER -tp PRETRAINING_PLANS_IDENTIFIER
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
`FINETUNING_PLANS_IDENTIFIER` is hereby probably nnUNetPlans unless you changed the experiment planner in
|
| 45 |
+
nnUNetv2_plan_and_preprocess. For `PRETRAINING_PLANS_IDENTIFIER` we recommend you set something custom in order to not
|
| 46 |
+
overwrite default plans.
|
| 47 |
+
|
| 48 |
+
Note that EVERYTHING is transferred between the datasets. Not just the network topology, batch size and patch size but
|
| 49 |
+
also the normalization scheme! Therefore, a transfer between datasets that use different normalization schemes may not
|
| 50 |
+
work well (but it could, depending on the schemes!).
|
| 51 |
+
|
| 52 |
+
Note on CT normalization: Yes, also the clip values, mean and std are transferred!
|
| 53 |
+
|
| 54 |
+
Now you can run the preprocessing on the pretraining dataset:
|
| 55 |
+
|
| 56 |
+
```bash
|
| 57 |
+
nnUNetv2_preprocess -d PRETRAINING_DATASET -plans_name PRETRAINING_PLANS_IDENTIFIER
|
| 58 |
+
```
|
| 59 |
+
|
| 60 |
+
And run the training as usual:
|
| 61 |
+
|
| 62 |
+
```bash
|
| 63 |
+
nnUNetv2_train PRETRAINING_DATASET CONFIG all -p PRETRAINING_PLANS_IDENTIFIER
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Note how we use the 'all' fold to train on all available data. For pretraining it does not make sense to split the data.
|
| 67 |
+
|
| 68 |
+
## Using pretrained weights
|
| 69 |
+
|
| 70 |
+
Once pretraining is completed (or you obtain compatible weights by other means) you can use them to initialize your model:
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
nnUNetv2_train FINETUNING_DATASET CONFIG FOLD -pretrained_weights PATH_TO_CHECKPOINT
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
Specify the checkpoint in PATH_TO_CHECKPOINT.
|
| 77 |
+
|
| 78 |
+
When loading pretrained weights, all layers except the segmentation layers will be used!
|
| 79 |
+
|
| 80 |
+
So far there are no specific nnUNet trainers for fine tuning, so the current recommendation is to just use
|
| 81 |
+
nnUNetTrainer. You can however easily write your own trainers with learning rate ramp up, fine-tuning of segmentation
|
| 82 |
+
heads or shorter training time.
|
nnUNet/documentation/region_based_training.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Region-based training
|
| 2 |
+
|
| 3 |
+
## What is this about?
|
| 4 |
+
In some segmentation tasks, most prominently the
|
| 5 |
+
[Brain Tumor Segmentation Challenge](http://braintumorsegmentation.org/), the target areas (based on which the metric
|
| 6 |
+
will be computed) are different from the labels provided in the training data. This is the case because for some
|
| 7 |
+
clinical applications, it is more relevant to detect the whole tumor, tumor core and enhancing tumor instead of the
|
| 8 |
+
individual labels (edema, necrosis and non-enhancing tumor, enhancing tumor).
|
| 9 |
+
|
| 10 |
+
<img src="assets/regions_vs_labels.png" width="768px" />
|
| 11 |
+
|
| 12 |
+
The figure shows an example BraTS case along with label-based representation of the task (top) and region-based
|
| 13 |
+
representation (bottom). The challenge evaluation is done on the regions. As we have shown in our
|
| 14 |
+
[BraTS 2018 contribution](https://arxiv.org/abs/1809.10483), directly optimizing those
|
| 15 |
+
overlapping areas over the individual labels yields better scoring models!
|
| 16 |
+
|
| 17 |
+
## What can nnU-Net do?
|
| 18 |
+
nnU-Net's region-based training allows you to learn areas that are constructed by merging individual labels. For
|
| 19 |
+
some segmentation tasks this provides a benefit, as this shifts the importance allocated to different labels during training.
|
| 20 |
+
Most prominently, this feature can be used to represent **hierarchical classes**, for example when organs +
|
| 21 |
+
substructures are to be segmented. Imagine a liver segmentation problem, where vessels and tumors are also to be
|
| 22 |
+
segmented. The first target region could thus be the entire liver (including the substructures), while the remaining
|
| 23 |
+
targets are the individual substructues.
|
| 24 |
+
|
| 25 |
+
Important: nnU-Net still requires integer label maps as input and will produce integer label maps as output!
|
| 26 |
+
Region-based training can be used to learn overlapping labels, but there must be a way to model these overlaps
|
| 27 |
+
for nnU-Net to work (see below how this is done).
|
| 28 |
+
|
| 29 |
+
## How do you use it?
|
| 30 |
+
|
| 31 |
+
When declaring the labels in the `dataset.json` file, BraTS would typically look like this:
|
| 32 |
+
|
| 33 |
+
```python
|
| 34 |
+
...
|
| 35 |
+
"labels": {
|
| 36 |
+
"background": 0,
|
| 37 |
+
"edema": 1,
|
| 38 |
+
"non_enhancing_and_necrosis": 2,
|
| 39 |
+
"enhancing_tumor": 3
|
| 40 |
+
},
|
| 41 |
+
...
|
| 42 |
+
```
|
| 43 |
+
(we use different int values than the challenge because nnU-Net needs consecutive integers!)
|
| 44 |
+
|
| 45 |
+
This representation corresponds to the upper row in the figure above.
|
| 46 |
+
|
| 47 |
+
For region-based training, the labels need to be changed to the following:
|
| 48 |
+
|
| 49 |
+
```python
|
| 50 |
+
...
|
| 51 |
+
"labels": {
|
| 52 |
+
"background": 0,
|
| 53 |
+
"whole_tumor": [1, 2, 3],
|
| 54 |
+
"tumor_core": [2, 3],
|
| 55 |
+
"enhancing_tumor": 3 # or [3]
|
| 56 |
+
},
|
| 57 |
+
"regions_class_order": [1, 2, 3],
|
| 58 |
+
...
|
| 59 |
+
```
|
| 60 |
+
This corresponds to the bottom row in the figure above. Note how an additional entry in the dataset.json is
|
| 61 |
+
required: `regions_class_order`. This tells nnU-Net how to convert the region representations back to an integer map.
|
| 62 |
+
It essentially just tells nnU-Net what labels to place for which region in what order. The length of the
|
| 63 |
+
list here needs to be the same as the number of regions (excl background). Each element in the list corresponds
|
| 64 |
+
to the label that is placed instead of the region into the final segmentation. Later entries will overwrite earlier ones!
|
| 65 |
+
Concretely, for the example given here, nnU-Net
|
| 66 |
+
will firstly place the label 1 (edema) where the 'whole_tumor' region was predicted, then place the label 2
|
| 67 |
+
(non-enhancing tumor and necrosis) where the "tumor_core" was predicted and finally place the label 3 in the
|
| 68 |
+
predicted 'enhancing_tumor' area. With each step, part of the previously set pixels
|
| 69 |
+
will be overwritten with the new label! So when setting your `regions_class_order`, place encompassing regions
|
| 70 |
+
(like whole tumor etc) first, followed by substructures.
|
| 71 |
+
|
| 72 |
+
**IMPORTANT** Because the conversion back to a segmentation map is sensitive to the order in which the regions are
|
| 73 |
+
declared ("place label X in the first region") you need to make sure that this order is not perturbed! When
|
| 74 |
+
automatically generating the dataset.json, make sure the dictionary keys do not get sorted alphabetically! Set
|
| 75 |
+
`sort_keys=False` in `json.dump()`!!!
|
| 76 |
+
|
| 77 |
+
nnU-Net will perform the evaluation + model selection also on the regions, not the individual labels!
|
| 78 |
+
|
| 79 |
+
That's all. Easy, huh?
|
nnUNet/documentation/resenc_presets.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Residual Encoder Presets in nnU-Net
|
| 2 |
+
|
| 3 |
+
When using these presets, please cite our recent paper on the need for rigorous validation in 3D medical image segmentation:
|
| 4 |
+
|
| 5 |
+
> Isensee, F.<sup>* </sup>, Wald, T.<sup>* </sup>, Ulrich, C.<sup>* </sup>, Baumgartner, M.<sup>* </sup>, Roy, S., Maier-Hein, K.<sup>β </sup>, Jaeger, P.<sup>β </sup> (2024). nnU-Net Revisited: A Call for Rigorous Validation in 3D Medical Image Segmentation. arXiv preprint arXiv:2404.09556.
|
| 6 |
+
|
| 7 |
+
*: shared first authors\
|
| 8 |
+
<sup>β </sup>: shared last authors
|
| 9 |
+
|
| 10 |
+
[PAPER LINK](https://arxiv.org/pdf/2404.09556.pdf)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
Residual Encoder UNets have been supported by nnU-Net since our participation in KiTS2019, but have flown under the radar.
|
| 14 |
+
This is bound to change with our new nnUNetResEncUNet presets :raised_hands:! Especially on large datasets such as KiTS2023 and AMOS2022
|
| 15 |
+
they offer improved segmentation performance!
|
| 16 |
+
|
| 17 |
+
| | BTCV | ACDC | LiTS | BraTS | KiTS | AMOS | VRAM | RT | Arch. | nnU |
|
| 18 |
+
|------------------------|-------|-------|-------|-------|-------|-------|-------|-----|-------|-----|
|
| 19 |
+
| | n=30 | n=200 | n=131 | n=1251| n=489 | n=360 | | | | |
|
| 20 |
+
| nnU-Net (org.) [1] | 83.08 | 91.54 | 80.09 | 91.24 | 86.04 | 88.64 | 7.70 | 9 | CNN | Yes |
|
| 21 |
+
| nnU-Net ResEnc M | 83.31 | 91.99 | 80.75 | 91.26 | 86.79 | 88.77 | 9.10 | 12 | CNN | Yes |
|
| 22 |
+
| nnU-Net ResEnc L | 83.35 | 91.69 | 81.60 | 91.13 | 88.17 | 89.41 | 22.70 | 35 | CNN | Yes |
|
| 23 |
+
| nnU-Net ResEnc XL | 83.28 | 91.48 | 81.19 | 91.18 | 88.67 | 89.68 | 36.60 | 66 | CNN | Yes |
|
| 24 |
+
| MedNeXt L k3 [2] | 84.70 | 92.65 | 82.14 | 91.35 | 88.25 | 89.62 | 17.30 | 68 | CNN | Yes |
|
| 25 |
+
| MedNeXt L k5 [2] | 85.04 | 92.62 | 82.34 | 91.50 | 87.74 | 89.73 | 18.00 | 233 | CNN | Yes |
|
| 26 |
+
| STU-Net S [3] | 82.92 | 91.04 | 78.50 | 90.55 | 84.93 | 88.08 | 5.20 | 10 | CNN | Yes |
|
| 27 |
+
| STU-Net B [3] | 83.05 | 91.30 | 79.19 | 90.85 | 86.32 | 88.46 | 8.80 | 15 | CNN | Yes |
|
| 28 |
+
| STU-Net L [3] | 83.36 | 91.31 | 80.31 | 91.26 | 85.84 | 89.34 | 26.50 | 51 | CNN | Yes |
|
| 29 |
+
| SwinUNETR [4] | 78.89 | 91.29 | 76.50 | 90.68 | 81.27 | 83.81 | 13.10 | 15 | TF | Yes |
|
| 30 |
+
| SwinUNETRV2 [5] | 80.85 | 92.01 | 77.85 | 90.74 | 84.14 | 86.24 | 13.40 | 15 | TF | Yes |
|
| 31 |
+
| nnFormer [6] | 80.86 | 92.40 | 77.40 | 90.22 | 75.85 | 81.55 | 5.70 | 8 | TF | Yes |
|
| 32 |
+
| CoTr [7] | 81.95 | 90.56 | 79.10 | 90.73 | 84.59 | 88.02 | 8.20 | 18 | TF | Yes |
|
| 33 |
+
| No-Mamba Base | 83.69 | 91.89 | 80.57 | 91.26 | 85.98 | 89.04 | 12.0 | 24 | CNN | Yes |
|
| 34 |
+
| U-Mamba Bot [8] | 83.51 | 91.79 | 80.40 | 91.26 | 86.22 | 89.13 | 12.40 | 24 | Mam | Yes |
|
| 35 |
+
| U-Mamba Enc [8] | 82.41 | 91.22 | 80.27 | 90.91 | 86.34 | 88.38 | 24.90 | 47 | Mam | Yes |
|
| 36 |
+
| A3DS SegResNet [9,11] | 80.69 | 90.69 | 79.28 | 90.79 | 81.11 | 87.27 | 20.00 | 22 | CNN | No |
|
| 37 |
+
| A3DS DiNTS [10, 11] | 78.18 | 82.97 | 69.05 | 87.75 | 65.28 | 82.35 | 29.20 | 16 | CNN | No |
|
| 38 |
+
| A3DS SwinUNETR [4, 11] | 76.54 | 82.68 | 68.59 | 89.90 | 52.82 | 85.05 | 34.50 | 9 | TF | No |
|
| 39 |
+
|
| 40 |
+
Results taken from our paper (see above), reported values are Dice scores computed over 5-fold cross-validation on each
|
| 41 |
+
dataset. All models trained from scratch.
|
| 42 |
+
|
| 43 |
+
RT: training run time (measured on 1x Nvidia A100 PCIe 40GB)\
|
| 44 |
+
VRAM: GPU VRAM used during training, as reported by nvidia-smi\
|
| 45 |
+
Arch.: CNN = convolutional neural network; TF = transformer; Mam = Mamba\
|
| 46 |
+
nnU: whether the architectrue was integrated and tested with the nnU-Net framework (either by us or the original authors)
|
| 47 |
+
|
| 48 |
+
## How to use the new presets
|
| 49 |
+
|
| 50 |
+
We offer three new presets, each targeted for a different GPU VRAM and compute budget:
|
| 51 |
+
- **nnU-Net ResEnc M**: similar GPU budget to the standard UNet configuration. Best suited for GPUs with 9-11GB VRAM. Training time: ~12h on A100
|
| 52 |
+
- **nnU-Net ResEnc L**: requires a GPU with 24GB VRAM. Training time: ~35h on A100
|
| 53 |
+
- **nnU-Net ResEnc XL**: requires a GPU with 40GB VRAM. Training time: ~66h on A100
|
| 54 |
+
|
| 55 |
+
### **:point_right: We recommend **nnU-Net ResEnc L** as the new default nnU-Net configuration! :point_left:**
|
| 56 |
+
|
| 57 |
+
The new presets are available as follows ((M/L/XL) = pick one!):
|
| 58 |
+
1. Specify the desired configuration when running experiment planning and preprocessing:
|
| 59 |
+
`nnUNetv2_plan_and_preprocess -d DATASET -pl nnUNetPlannerResEnc(M/L/XL)`. These planners use the same preprocessed
|
| 60 |
+
data folder as the standard 2d and 3d_fullres configurations since the preprocessed data is identical. Only the
|
| 61 |
+
3d_lowres differs and will be saved in a different folder to allow all configurations to coexist! If you are only
|
| 62 |
+
planning to run 3d_fullres/2d and you already have this data preprocessed, you can just run
|
| 63 |
+
`nnUNetv2_plan_experiment -d DATASET -pl nnUNetPlannerResEnc(M/L/XL)` to avoid preprocessing again!
|
| 64 |
+
2. Now, just specify the correct plans when running `nnUNetv2_train`, `nnUNetv2_predict` etc. The interface is
|
| 65 |
+
consistent across all nnU-Net commands: `-p nnUNetResEncUNet(M/L/XL)Plans`
|
| 66 |
+
|
| 67 |
+
Training results for the new presets will be stored in a dedicated folder and will not overwrite standard nnU-Net
|
| 68 |
+
results! So don't be afraid to give it a go!
|
| 69 |
+
|
| 70 |
+
## Scaling ResEnc nnU-Net beyond the Presets
|
| 71 |
+
The presets differ from `ResEncUNetPlanner` in two ways:
|
| 72 |
+
- They set new default values for `gpu_memory_target_in_gb` to target the respective VRAM consumptions
|
| 73 |
+
- They remove the batch size cap of 0.05 (= previously one batch could not cover mode pixels than 5% of the entire dataset, not it can be arbitrarily large)
|
| 74 |
+
|
| 75 |
+
The presets are merely there to make life easier, and to provide standardized configurations people can benchmark with.
|
| 76 |
+
You can easily adapt the GPU memory target to match your GPU, and to scale beyond 40GB of GPU memory.
|
| 77 |
+
|
| 78 |
+
Here is an example for how to scale to 80GB VRAM on Dataset003_Liver:
|
| 79 |
+
|
| 80 |
+
`nnUNetv2_plan_experiment -d 3 -pl nnUNetPlannerResEncM -gpu_memory_target 80 -overwrite_plans_name nnUNetResEncUNetPlans_80G`
|
| 81 |
+
|
| 82 |
+
Just use `-p nnUNetResEncUNetPlans_80G` moving forward as outlined above! Running the example above will yield a
|
| 83 |
+
warning ("You are running nnUNetPlannerM with a non-standard gpu_memory_target_in_gb"). This warning can be ignored here.
|
| 84 |
+
**Always change the plans identifier with `-overwrite_plans_name NEW_PLANS_NAME` when messing with the VRAM target in
|
| 85 |
+
order to not overwrite preset plans!**
|
| 86 |
+
|
| 87 |
+
Why not use `ResEncUNetPlanner` -> because that one still has the 5% cap in place!
|
| 88 |
+
|
| 89 |
+
### Scaling to multiple GPUs
|
| 90 |
+
When scaling to multiple GPUs, do not just specify the combined amount of VRAM to `nnUNetv2_plan_experiment` as this
|
| 91 |
+
may result in patch sizes that are too large to be processed by individual GPUs. It is best to let this command run for
|
| 92 |
+
the VRAM budget of one GPU, and then manually edit the plans file to increase the batch size. You can use [configuration inheritance](explanation_plans_files.md).
|
| 93 |
+
In the configurations dictionary of the generated plans JSON file, add the following entry:
|
| 94 |
+
|
| 95 |
+
```json
|
| 96 |
+
"3d_fullres_bsXX": {
|
| 97 |
+
"inherits_from": "3d_fullres",
|
| 98 |
+
"batch_size": XX
|
| 99 |
+
},
|
| 100 |
+
```
|
| 101 |
+
Where XX is the new batch size. If 3d_fullres has a batch size of 2 for one GPU and you are planning to scale to 8 GPUs, make the new batch size 2x8=16!
|
| 102 |
+
You can then train the new configuration using nnU-Net's multi-GPU settings:
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
nnUNetv2_train DATASETID 3d_fullres_bsXX FOLD -p nnUNetResEncUNetPlans_80G -num_gpus 8
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
## Proposing a new segmentation method? Benchmark the right way!
|
| 109 |
+
When benchmarking new segmentation methods against nnU-Net, we encourage to benchmark against the residual encoder
|
| 110 |
+
variants. For a fair comparison, pick the variant that most closely matches the GPU memory and compute
|
| 111 |
+
requirements of your method!
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
## References
|
| 115 |
+
[1] Isensee, Fabian, et al. "nnU-Net: a self-configuring method for deep learning-based biomedical image segmentation." Nature methods 18.2 (2021): 203-211.\
|
| 116 |
+
[2] Roy, Saikat, et al. "Mednext: transformer-driven scaling of convnets for medical image segmentation." International Conference on Medical Image Computing and Computer-Assisted Intervention. Cham: Springer Nature Switzerland, 2023.\
|
| 117 |
+
[3] Huang, Ziyan, et al. "Stu-net: Scalable and transferable medical image segmentation models empowered by large-scale supervised pre-training." arXiv preprint arXiv:2304.06716 (2023).\
|
| 118 |
+
[4] Hatamizadeh, Ali, et al. "Swin unetr: Swin transformers for semantic segmentation of brain tumors in mri images." International MICCAI Brainlesion Workshop. Cham: Springer International Publishing, 2021.\
|
| 119 |
+
[5] He, Yufan, et al. "Swinunetr-v2: Stronger swin transformers with stagewise convolutions for 3d medical image segmentation." International Conference on Medical Image Computing and Computer-Assisted Intervention. Cham: Springer Nature Switzerland, 2023.\
|
| 120 |
+
[6] Zhou, Hong-Yu, et al. "nnformer: Interleaved transformer for volumetric segmentation." arXiv preprint arXiv:2109.03201 (2021).\
|
| 121 |
+
[7] Xie, Yutong, et al. "Cotr: Efficiently bridging cnn and transformer for 3d medical image segmentation." Medical Image Computing and Computer Assisted InterventionβMICCAI 2021: 24th International Conference, Strasbourg, France, September 27βOctober 1, 2021, Proceedings, Part III 24. Springer International Publishing, 2021.\
|
| 122 |
+
[8] Ma, Jun, Feifei Li, and Bo Wang. "U-mamba: Enhancing long-range dependency for biomedical image segmentation." arXiv preprint arXiv:2401.04722 (2024).\
|
| 123 |
+
[9] Myronenko, Andriy. "3D MRI brain tumor segmentation using autoencoder regularization." Brainlesion: Glioma, Multiple Sclerosis, Stroke and Traumatic Brain Injuries: 4th International Workshop, BrainLes 2018, Held in Conjunction with MICCAI 2018, Granada, Spain, September 16, 2018, Revised Selected Papers, Part II 4. Springer International Publishing, 2019.\
|
| 124 |
+
[10] He, Yufan, et al. "Dints: Differentiable neural network topology search for 3d medical image segmentation." Proceedings of the IEEE/CVF conference on computer vision and pattern recognition. 2021.\
|
| 125 |
+
[11] Auto3DSeg, MONAI 1.3.0, [LINK](https://github.com/Project-MONAI/tutorials/tree/ed8854fa19faa49083f48abf25a2c30ab9ac1c6b/auto3dseg)
|
| 126 |
+
|
nnUNet/documentation/run_inference_with_pretrained_models.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# How to run inference with pretrained models
|
| 2 |
+
**Important:** Pretrained weights from nnU-Net v1 are NOT compatible with V2. You will need to retrain with the new
|
| 3 |
+
version. But honestly, you already have a fully trained model with which you can run inference (in v1), so
|
| 4 |
+
just continue using that!
|
| 5 |
+
|
| 6 |
+
Not yet available for V2 :-(
|
| 7 |
+
If you wish to run inference with pretrained models, check out the old nnU-Net for now. We are working on this full steam!
|
nnUNet/documentation/set_environment_variables.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# How to set environment variables
|
| 2 |
+
|
| 3 |
+
nnU-Net requires some environment variables so that it always knows where the raw data, preprocessed data and trained
|
| 4 |
+
models are. Depending on the operating system, these environment variables need to be set in different ways.
|
| 5 |
+
|
| 6 |
+
Variables can either be set permanently (recommended!) or you can decide to set them every time you call nnU-Net.
|
| 7 |
+
|
| 8 |
+
# Linux & MacOS
|
| 9 |
+
|
| 10 |
+
## Permanent
|
| 11 |
+
Locate the `.bashrc` file in your home folder and add the following lines to the bottom:
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
export nnUNet_raw="/media/fabian/nnUNet_raw"
|
| 15 |
+
export nnUNet_preprocessed="/media/fabian/nnUNet_preprocessed"
|
| 16 |
+
export nnUNet_results="/media/fabian/nnUNet_results"
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
(Of course you need to adapt the paths to the actual folders you intend to use).
|
| 20 |
+
If you are using a different shell, such as zsh, you will need to find the correct script for it. For zsh this is `.zshrc`.
|
| 21 |
+
|
| 22 |
+
## Temporary
|
| 23 |
+
Just execute the following lines whenever you run nnU-Net:
|
| 24 |
+
```bash
|
| 25 |
+
export nnUNet_raw="/media/fabian/nnUNet_raw"
|
| 26 |
+
export nnUNet_preprocessed="/media/fabian/nnUNet_preprocessed"
|
| 27 |
+
export nnUNet_results="/media/fabian/nnUNet_results"
|
| 28 |
+
```
|
| 29 |
+
(Of course you need to adapt the paths to the actual folders you intend to use).
|
| 30 |
+
|
| 31 |
+
Important: These variables will be deleted if you close your terminal! They will also only apply to the current
|
| 32 |
+
terminal window and DO NOT transfer to other terminals!
|
| 33 |
+
|
| 34 |
+
Alternatively you can also just prefix them to your nnU-Net commands:
|
| 35 |
+
|
| 36 |
+
`nnUNet_results="/media/fabian/nnUNet_results" nnUNet_preprocessed="/media/fabian/nnUNet_preprocessed" nnUNetv2_train[...]`
|
| 37 |
+
|
| 38 |
+
## Verify that environment parameters are set
|
| 39 |
+
You can always execute `echo ${nnUNet_raw}` etc to print the environment variables. This will return an empty string if
|
| 40 |
+
they were not set.
|
| 41 |
+
|
| 42 |
+
# Windows
|
| 43 |
+
Useful links:
|
| 44 |
+
- [https://www3.ntu.edu.sg](https://www3.ntu.edu.sg/home/ehchua/programming/howto/Environment_Variables.html#:~:text=To%20set%20(or%20change)%20a,it%20to%20an%20empty%20string.)
|
| 45 |
+
- [https://phoenixnap.com](https://phoenixnap.com/kb/windows-set-environment-variable)
|
| 46 |
+
|
| 47 |
+
## Permanent
|
| 48 |
+
See `Set Environment Variable in Windows via GUI` [here](https://phoenixnap.com/kb/windows-set-environment-variable).
|
| 49 |
+
Or read about setx (command prompt).
|
| 50 |
+
|
| 51 |
+
## Temporary
|
| 52 |
+
Just execute the following before you run nnU-Net:
|
| 53 |
+
|
| 54 |
+
(PowerShell)
|
| 55 |
+
```PowerShell
|
| 56 |
+
$Env:nnUNet_raw = "C:/Users/fabian/nnUNet_raw"
|
| 57 |
+
$Env:nnUNet_preprocessed = "C:/Users/fabian/nnUNet_preprocessed"
|
| 58 |
+
$Env:nnUNet_results = "C:/Users/fabian/nnUNet_results"
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
(Command Prompt)
|
| 62 |
+
```Command Prompt
|
| 63 |
+
set nnUNet_raw=C:/Users/fabian/nnUNet_raw
|
| 64 |
+
set nnUNet_preprocessed=C:/Users/fabian/nnUNet_preprocessed
|
| 65 |
+
set nnUNet_results=C:/Users/fabian/fabian/nnUNet_results
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
(Of course you need to adapt the paths to the actual folders you intend to use).
|
| 69 |
+
|
| 70 |
+
Important: These variables will be deleted if you close your session! They will also only apply to the current
|
| 71 |
+
window and DO NOT transfer to other sessions!
|
| 72 |
+
|
| 73 |
+
## Verify that environment parameters are set
|
| 74 |
+
Printing in Windows works differently depending on the environment you are in:
|
| 75 |
+
|
| 76 |
+
PowerShell: `echo $Env:[variable_name]`
|
| 77 |
+
|
| 78 |
+
Command Prompt: `echo %variable_name%`
|
nnUNet/documentation/setting_up_paths.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Setting up Paths
|
| 2 |
+
|
| 3 |
+
nnU-Net relies on environment variables to know where raw data, preprocessed data and trained model weights are stored.
|
| 4 |
+
To use the full functionality of nnU-Net, the following three environment variables must be set:
|
| 5 |
+
|
| 6 |
+
1) `nnUNet_raw`: This is where you place the raw datasets. This folder will have one subfolder for each dataset names
|
| 7 |
+
DatasetXXX_YYY where XXX is a 3-digit identifier (such as 001, 002, 043, 999, ...) and YYY is the (unique)
|
| 8 |
+
dataset name. The datasets must be in nnU-Net format, see [here](dataset_format.md).
|
| 9 |
+
|
| 10 |
+
Example tree structure:
|
| 11 |
+
```
|
| 12 |
+
nnUNet_raw/Dataset001_NAME1
|
| 13 |
+
βββ dataset.json
|
| 14 |
+
βββ imagesTr
|
| 15 |
+
βΒ Β βββ ...
|
| 16 |
+
βββ imagesTs
|
| 17 |
+
βΒ Β βββ ...
|
| 18 |
+
βββ labelsTr
|
| 19 |
+
βββ ...
|
| 20 |
+
nnUNet_raw/Dataset002_NAME2
|
| 21 |
+
βββ dataset.json
|
| 22 |
+
βββ imagesTr
|
| 23 |
+
βΒ Β βββ ...
|
| 24 |
+
βββ imagesTs
|
| 25 |
+
βΒ Β βββ ...
|
| 26 |
+
βββ labelsTr
|
| 27 |
+
βββ ...
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
2) `nnUNet_preprocessed`: This is the folder where the preprocessed data will be saved. The data will also be read from
|
| 31 |
+
this folder during training. It is important that this folder is located on a drive with low access latency and high
|
| 32 |
+
throughput (such as a nvme SSD (PCIe gen 3 is sufficient)).
|
| 33 |
+
|
| 34 |
+
3) `nnUNet_results`: This specifies where nnU-Net will save the model weights. If pretrained models are downloaded, this
|
| 35 |
+
is where it will save them.
|
| 36 |
+
|
| 37 |
+
### How to set environment variables
|
| 38 |
+
|
| 39 |
+
See [here](set_environment_variables.md).
|
nnUNet/documentation/tldr_migration_guide_from_v1.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# TLDR Migration Guide from nnU-Net V1
|
| 2 |
+
|
| 3 |
+
- nnU-Net V2 can be installed simultaneously with V1. They won't get in each other's way
|
| 4 |
+
- The environment variables needed for V2 have slightly different names. Read [this](setting_up_paths.md).
|
| 5 |
+
- nnU-Net V2 datasets are called DatasetXXX_NAME. Not Task.
|
| 6 |
+
- Datasets have the same structure (imagesTr, labelsTr, dataset.json) but we now support more
|
| 7 |
+
[file types](dataset_format.md#supported-file-formats). The dataset.json is simplified. Use `generate_dataset_json`
|
| 8 |
+
from nnunetv2.dataset_conversion.generate_dataset_json.py.
|
| 9 |
+
- Careful: labels are now no longer declared as value:name but name:value. This has to do with [hierarchical labels](region_based_training.md).
|
| 10 |
+
- nnU-Net v2 commands start with `nnUNetv2...`. They work mostly (but not entirely) the same. Just use the `-h` option.
|
| 11 |
+
- You can transfer your V1 raw datasets to V2 with `nnUNetv2_convert_old_nnUNet_dataset`. You cannot transfer trained
|
| 12 |
+
models. Continue to use the old nnU-Net Version for making inference with those.
|
| 13 |
+
- These are the commands you are most likely to be using (in that order)
|
| 14 |
+
- `nnUNetv2_plan_and_preprocess`. Example: `nnUNetv2_plan_and_preprocess -d 2`
|
| 15 |
+
- `nnUNetv2_train`. Example: `nnUNetv2_train 2 3d_fullres 0`
|
| 16 |
+
- `nnUNetv2_find_best_configuration`. Example: `nnUNetv2_find_best_configuration 2 -c 2d 3d_fullres`. This command
|
| 17 |
+
will now create a `inference_instructions.txt` file in your `nnUNet_preprocessed/DatasetXXX_NAME/` folder which
|
| 18 |
+
tells you exactly how to do inference.
|
| 19 |
+
- `nnUNetv2_predict`. Example: `nnUNetv2_predict -i INPUT_FOLDER -o OUTPUT_FOLDER -c 3d_fullres -d 2`
|
| 20 |
+
- `nnUNetv2_apply_postprocessing` (see inference_instructions.txt)
|