Skip to content
This repository was archived by the owner on May 15, 2023. It is now read-only.

Commit 9e15ad8

Browse files
committed
update monai version
1 parent da4337f commit 9e15ad8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+1790
-75
lines changed

README.md

+25-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
![Salmon-logo-1](images/salmon.JPG)
22
# SALMON v.2: Segmentation deep learning ALgorithm based on MONai toolbox
33
- SALMON is a computational toolbox for segmentation using neural networks (3D patches-based segmentation)
4-
- SALMON is based on NN-UNET and MONAI: PyTorch-based, open-source frameworks for deep learning in healthcare imaging.
4+
- SALMON is based on MONAI 0.7.0 : PyTorch-based, open-source frameworks for deep learning in healthcare imaging.
55
(https://github.com/Project-MONAI/MONAI)
66
(https://github.com/MIC-DKFZ/nnUNet)
7+
(https://arxiv.org/abs/2103.10504)
78

8-
This is my "open-box" version if I want to modify the parameters for some particular task, while the two above are hard-coded.
9+
This is my "open-box" version if I want to modify the parameters for some particular task, while the two above are hard-coded. The monai 0.5.0 folder contains the previous versions based on the old monai version.
910

1011
*******************************************************************************
1112
## Requirements
@@ -20,7 +21,7 @@ Labels are resampled and resized to the corresponding image, to avoid array size
2021

2122
- check_loader_patches: Shows example of patches fed to the network during the training.
2223

23-
- networks.py: The architecture available for segmentation is a nn-Unet.
24+
- networks.py: The architectures available for segmentation are nn-Unet and UneTR (based on Visual transformers)
2425

2526
- train.py: Runs the training
2627

@@ -93,6 +94,7 @@ with same size and resolution of the source image.
9394
### Tips:
9495
- Use and modify "check_loader_patches.py" to check the patches fed during training.
9596
- The "networks.py" calls the nn-Unet, which adapts itself to the input data (resolution and patches size). The script also saves the graph of you network, so you can visualize it.
97+
- "networks.py" includes also UneTR (based on Visual transformers). This is experimental. For more info check (https://arxiv.org/abs/2103.10504) and https://github.com/Project-MONAI/tutorials/blob/master/3d_segmentation/unetr_btcv_segmentation_3d.ipynb
9698
- Is it possible to add other networks, but for segmentation the U-net architecture is the state of the art.
9799

98100
### Sample script inference
@@ -108,9 +110,27 @@ The example segment the prostate (1 channel input) in the transition zone and pe
108110
The gif files with some example images are shown above.
109111

110112
Some note:
111-
- You must add an additional channel for the background. Example: 0 background, 1 prostate, 2 prostate tumor = 3 out channels in total.
112113
- Tensorboard can show you all segmented channels, but for now the metric is the Mean-Dice (of all channels). If you want to evaluate the Dice score for each channel you
113114
have to modify a bit the plot_dice function. I will do it...one day...who knows...maybe not
114115
- The loss is the DiceLoss + CrossEntropy. You can modify it if you want to try others (https://docs.monai.io/en/latest/losses.html#diceloss)
115116

116-
Check more examples at https://github.com/Project-MONAI/tutorials/blob/master/3d_segmentation/spleen_segmentation_3d.ipynb.
117+
Check more examples at https://github.com/Project-MONAI/tutorials/blob/master/3d_segmentation/.
118+
119+
UneTR Notes from the authors:
120+
121+
Feature_size and pos_embed are the parameters that need to changed to adopt it for your application of interest. Other parameters that are mentioned come from Vision Transformer (ViT) default hyper-parameters (original architecture). In addition, the new revision of UNETR paper with more descriptions is now publicly available. Please check for more details:
122+
https://arxiv.org/pdf/2103.10504.pdf
123+
124+
Now let's look at each of these hyper-parameters in the order of importance:
125+
126+
feature_size : In UNETR, we multiply the size of the CNN-based features in the decoder by a factor of 2 at every resolution ( just like the original UNet paper). By default, we set this value to 16 ( to make the entire network lighter). However using larger values such as 32 can improve the segmentation performance if GPU memory is not an issue. Figure2 of the paper also shows this in details.
127+
128+
pos_embed: this determines how the image is divided into non-overlapping patches. Essentially, there are 2 ways to achieve this ( by setting it to conv or perceptron). Let's further dive into it for more information:
129+
First is by directly applying a convolutional layer with the same stride and kernel size of the patch size and with feature size of the hidden size in the ViT model. Second is by first breaking the image into patches by properly resizing the tensor ( for which we use einops) and then feed it into a perceptron (linear) layer with a hidden size of the ViT model. Our experiments show that for certain applications such as brain segmentation with multiple modalities (e.g. 4 modes such as T1,T2 etc.), using the convolutional layer works better as it takes into account all modes concurrently. For CT images ( e.g. BTCV multi-organ segmentation), we did not see any difference in terms of performance between these two approaches.
130+
131+
hidden_size : this is the size of the hidden layers in the ViT encoder. We follow the original ViT model and set this value to 768. In addition, the hidden size should be divisible by the number of attention heads in the ViT model.
132+
133+
num_heads : in the multi-headed self-attention block, this is the number of attention heads. Following the ViT architecture, we set it to 12.
134+
135+
mlp_dim : this is the dimension of the multi-layer perceptrons (MLP) in the transformer encoder. Again, we follow the ViT model and set this to 3072 as default value to be consistent with their architecture.
136+

check_loader_patches.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def plot3d(image):
7575

7676
LoadImaged(keys=['image', 'label']),
7777
AddChanneld(keys=['image', 'label']),
78-
Orientationd(keys=["image", "label"], axcodes="RAS"),
78+
# Orientationd(keys=["image", "label"], axcodes="RAS"),
7979
# ThresholdIntensityd(keys=['image'], threshold=-135, above=True, cval=-135),
8080
# ThresholdIntensityd(keys=['image'], threshold=215, above=False, cval=215),
8181
CropForegroundd(keys=['image', 'label'], source_key='image', start_coord_key='foreground_start_coord',

init.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@ def initialize(self, parser):
1717
parser.add_argument('--labels_folder', type=str, default='./Data_folder/labels')
1818
parser.add_argument('--increase_factor_data', default=1, help='Increase data number per epoch')
1919
parser.add_argument('--preload', type=str, default=None)
20-
parser.add_argument('--gpu_ids', type=str, default='0,1', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
20+
parser.add_argument('--gpu_ids', type=str, default='2,3', help='gpu ids: e.g. 0 0,1,2, 0,2. use -1 for CPU')
2121
parser.add_argument('--workers', default=8, type=int, help='number of data loading workers')
2222

2323
# dataset parameters
24-
parser.add_argument('--patch_size', default=(160, 160, 32), help='Size of the patches extracted from the image')
25-
parser.add_argument('--spacing', default=[2.25, 2.25, 3], help='Original Resolution')
24+
parser.add_argument('--network', default='unetr', help='nnunet, unetr')
25+
parser.add_argument('--patch_size', default=(256, 256, 16), help='Size of the patches extracted from the image')
26+
parser.add_argument('--spacing', default=[0.7, 0.7, 3], help='Original Resolution')
2627
parser.add_argument('--resolution', default=None, help='New Resolution, if you want to resample the data in training. I suggest to resample in organize_folder_structure.py, otherwise in train resampling is slower')
2728
parser.add_argument('--batch_size', type=int, default=4, help='batch size, depends on your machine')
2829
parser.add_argument('--in_channels', default=1, type=int, help='Channels of the input')
@@ -31,6 +32,7 @@ def initialize(self, parser):
3132
# training parameters
3233
parser.add_argument('--epochs', default=1000, help='Number of epochs')
3334
parser.add_argument('--lr', default=0.01, help='Learning rate')
35+
parser.add_argument('--benchmark', default=True)
3436

3537
self.initialized = True
3638
return parser

installation_commands_.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
2) install pytorch conda install pytorch==1.5.0 torchvision==0.6.0 cudatoolkit=10.1 -c pytorch # this for cuda (check your cuda version)
66

77
3) conda install git pip
8-
4) pip install git+git://github.com/davidiommi/MONAI # dowload libraries
8+
4) pip install git+https://github.com/davidiommi/MONAI_0_7_0.git # dowload libraries
99
5) pip install -r requirements.txt # dowload libraries
1010

1111

File renamed without changes.

monai 0.5.0/README.md

+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
![Salmon-logo-1](images/salmon.JPG)
2+
# SALMON v.2: Segmentation deep learning ALgorithm based on MONai toolbox
3+
- SALMON is a computational toolbox for segmentation using neural networks (3D patches-based segmentation)
4+
- SALMON is based on NN-UNET and MONAI: PyTorch-based, open-source frameworks for deep learning in healthcare imaging.
5+
(https://github.com/Project-MONAI/MONAI)
6+
(https://github.com/MIC-DKFZ/nnUNet)
7+
8+
This is my "open-box" version if I want to modify the parameters for some particular task, while the two above are hard-coded.
9+
10+
*******************************************************************************
11+
## Requirements
12+
Follow the steps in "installation_commands.txt". Installation via Anaconda and creation of a virtual env to download the python libraries and pytorch/cuda.
13+
*******************************************************************************
14+
## Python scripts and their function
15+
16+
- organize_folder_structure.py: Organize the data in the folder structure (training,validation,testing) for the network.
17+
Labels are resampled and resized to the corresponding image, to avoid array size conflicts. You can set here a new image resolution for the dataset.
18+
19+
- init.py: List of options used to train the network.
20+
21+
- check_loader_patches: Shows example of patches fed to the network during the training.
22+
23+
- networks.py: The architecture available for segmentation is a nn-Unet.
24+
25+
- train.py: Runs the training
26+
27+
- predict_single_image.py: It launches the inference on a single input image chosen by the user.
28+
*******************************************************************************
29+
## Usage
30+
### Folders structure:
31+
32+
Use first "organize_folder_structure.py" to create organize the data.
33+
Modify the input parameters to select the two folders: images and labels folders with the dataset. Set the resolution of the images here before training.
34+
35+
.
36+
├── Data_folder
37+
| ├── CT
38+
| | ├── 1.nii
39+
| | ├── 2.nii
40+
| | └── 3.nii
41+
| ├── CT_labels
42+
| | ├── 1.nii
43+
| | ├── 2.nii
44+
| | └── 3.nii
45+
46+
Data structure after running it:
47+
48+
.
49+
├── Data_folder
50+
| ├── CT
51+
| ├── CT_labels
52+
| ├── images
53+
| | ├── train
54+
| | | ├── image1.nii
55+
| | | └── image2.nii
56+
| | └── val
57+
| | | ├── image3.nii
58+
| | | └── image4.nii
59+
| | └── test
60+
| | | ├── image5.nii
61+
| | | └── image6.nii
62+
| ├── labels
63+
| | ├── train
64+
| | | ├── label1.nii
65+
| | | └── label2.nii
66+
| | └── val
67+
| | | ├── label3.nii
68+
| | | └── label4.nii
69+
| | └── test
70+
| | | ├── label5.nii
71+
| | | └── label6.nii
72+
73+
*******************************************************************************
74+
### Training:
75+
- Modify the "init.py" to set the parameters and start the training/testing on the data. Read the descriptions for each parameter.
76+
- Afterwards launch the "train.py" for training. Tensorboard is available to monitor the training ("runs" folder created)
77+
- Check and modify the train_transforms applied to the images in "train.py" for your specific case. (e.g. In the last update there is a HU windowing for CT images)
78+
79+
Sample images: the following images show the segmentation of carotid artery from MRI sequence
80+
81+
![Image](images/image.gif)![result](images/result.gif)
82+
83+
Sample images: the following images show the multi-label segmentation of prostate transition zone and peripheral zone from MRI sequence
84+
85+
![Image1](images/prostate.gif)![result1](images/prostate_inf.gif)!
86+
87+
*******************************************************************************
88+
### Inference:
89+
- Launch "predict_single_image.py" to test the network. Modify the parameters in the parse section to select the path of the weights, images to infer and result.
90+
- You can test the model on a new image, with different size and resolution from the training. The script will resample it before the inference and give you a mask
91+
with same size and resolution of the source image.
92+
*******************************************************************************
93+
### Tips:
94+
- Use and modify "check_loader_patches.py" to check the patches fed during training.
95+
- The "networks.py" calls the nn-Unet, which adapts itself to the input data (resolution and patches size). The script also saves the graph of you network, so you can visualize it.
96+
- Is it possible to add other networks, but for segmentation the U-net architecture is the state of the art.
97+
98+
### Sample script inference
99+
- The label can be omitted (None) if you segment an unknown image. You have to add the --resolution if you resampled the data during training (look at the argsparse in the code).
100+
```console
101+
python predict_single_image.py --image './Data_folder/image.nii' --label './Data_folder/label.nii' --result './Data_folder/prova.nii' --weights './best_metric_model.pth'
102+
```
103+
*******************************************************************************
104+
### Multi-channel segmentation:
105+
106+
The subfolder "multi_label_segmentation_example" include the modified code for multi_labels scenario.
107+
The example segment the prostate (1 channel input) in the transition zone and peripheral zone (2 channels output).
108+
The gif files with some example images are shown above.
109+
110+
Some note:
111+
- You must add an additional channel for the background. Example: 0 background, 1 prostate, 2 prostate tumor = 3 out channels in total.
112+
- Tensorboard can show you all segmented channels, but for now the metric is the Mean-Dice (of all channels). If you want to evaluate the Dice score for each channel you
113+
have to modify a bit the plot_dice function. I will do it...one day...who knows...maybe not
114+
- The loss is the DiceLoss + CrossEntropy. You can modify it if you want to try others (https://docs.monai.io/en/latest/losses.html#diceloss)
115+
116+
Check more examples at https://github.com/Project-MONAI/tutorials/blob/master/3d_segmentation/spleen_segmentation_3d.ipynb.

monai 0.5.0/check_loader_patches.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# Copyright 2020 MONAI Consortium
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
# Unless required by applicable law or agreed to in writing, software
7+
# distributed under the License is distributed on an "AS IS" BASIS,
8+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9+
# See the License for the specific language governing permissions and
10+
# limitations under the License.
11+
12+
import os
13+
import sys
14+
from glob import glob
15+
import tempfile
16+
import numpy as np
17+
import matplotlib.pyplot as plt
18+
import nibabel as nib
19+
import torch
20+
from torch.utils.data import DataLoader
21+
from init import Options
22+
import monai
23+
from monai.data import ArrayDataset, GridPatchDataset, create_test_image_3d
24+
from monai.transforms import (Compose, LoadImaged, AddChanneld, Transpose, Resized, CropForegroundd, CastToTyped,RandGaussianSmoothd,
25+
ScaleIntensityd, ToTensord, RandSpatialCropd, Rand3DElasticd, RandAffined, SpatialPadd,
26+
Spacingd, Orientationd, RandZoomd, ThresholdIntensityd, RandShiftIntensityd, RandGaussianNoised, BorderPadd,RandAdjustContrastd, NormalizeIntensityd,RandFlipd, ScaleIntensityRanged)
27+
28+
29+
class IndexTracker(object):
30+
def __init__(self, ax, X):
31+
self.ax = ax
32+
ax.set_title('use scroll wheel to navigate images')
33+
34+
self.X = X
35+
rows, cols, self.slices = X.shape
36+
self.ind = self.slices//2
37+
38+
self.im = ax.imshow(self.X[:, :, self.ind],cmap= 'gray')
39+
self.update()
40+
41+
def onscroll(self, event):
42+
print("%s %s" % (event.button, event.step))
43+
if event.button == 'up':
44+
self.ind = (self.ind + 1) % self.slices
45+
else:
46+
self.ind = (self.ind - 1) % self.slices
47+
self.update()
48+
49+
def update(self):
50+
self.im.set_data(self.X[:, :, self.ind])
51+
self.ax.set_ylabel('slice %s' % self.ind)
52+
self.im.axes.figure.canvas.draw()
53+
54+
55+
def plot3d(image):
56+
original=image
57+
original = np.rot90(original, k=-1)
58+
fig, ax = plt.subplots(1, 1)
59+
tracker = IndexTracker(ax, original)
60+
fig.canvas.mpl_connect('scroll_event', tracker.onscroll)
61+
plt.show()
62+
63+
64+
if __name__ == "__main__":
65+
66+
opt = Options().parse()
67+
68+
train_images = sorted(glob(os.path.join(opt.images_folder, 'train', 'image*.nii')))
69+
train_segs = sorted(glob(os.path.join(opt.labels_folder, 'train', 'label*.nii')))
70+
71+
data_dicts = [{'image': image_name, 'label': label_name}
72+
for image_name, label_name in zip(train_images, train_segs)]
73+
74+
monai_transforms = [
75+
76+
LoadImaged(keys=['image', 'label']),
77+
AddChanneld(keys=['image', 'label']),
78+
Orientationd(keys=["image", "label"], axcodes="RAS"),
79+
# ThresholdIntensityd(keys=['image'], threshold=-135, above=True, cval=-135),
80+
# ThresholdIntensityd(keys=['image'], threshold=215, above=False, cval=215),
81+
CropForegroundd(keys=['image', 'label'], source_key='image', start_coord_key='foreground_start_coord',
82+
end_coord_key='foreground_end_coord', ), # crop CropForeground
83+
NormalizeIntensityd(keys=['image']),
84+
ScaleIntensityd(keys=['image']),
85+
# Spacingd(keys=['image', 'label'], pixdim=opt.resolution, mode=('bilinear', 'nearest')),
86+
87+
SpatialPadd(keys=['image', 'label'], spatial_size=opt.patch_size, method= 'end'),
88+
RandSpatialCropd(keys=['image', 'label'], roi_size=opt.patch_size, random_size=False),
89+
ToTensord(keys=['image', 'label','foreground_start_coord', 'foreground_end_coord'],)
90+
]
91+
92+
transform = Compose(monai_transforms)
93+
check_ds = monai.data.Dataset(data=data_dicts, transform=transform)
94+
loader = DataLoader(check_ds, batch_size=1, shuffle=True, num_workers=0, pin_memory=torch.cuda.is_available())
95+
check_data = monai.utils.misc.first(loader)
96+
im, seg, coord1, coord2 = (check_data['image'][0], check_data['label'][0],check_data['foreground_start_coord'][0],
97+
check_data['foreground_end_coord'][0])
98+
99+
print(im.shape, seg.shape, coord1, coord2)
100+
101+
vol = im[0].numpy()
102+
mask = seg[0].numpy()
103+
104+
print(vol.shape, mask.shape)
105+
plot3d(vol)
106+
plot3d(mask)

0 commit comments

Comments
 (0)