MONAI 是一个基于 PyTorch的开源框架,用于深度学习在医学图像领域的研究。提供了许多便捷的可移植的API接口,以创建和评估深度学习模型。
这篇博客作为学习MONAI官方教程—3d_image_transforms的笔记,记录自己的学习过程。
本篇文章使用的MONAI版本是0.30,数据集是医学分割十项全能的Task02_Heart(并没有使用官方中的Tesk09_Spleen)。
import glob
import os
import shutil
import tempfile
import matplotlib.pyplot as plt
import numpy as np
from monai.transforms import (
AddChanneld,
LoadNifti,
LoadNiftid,
Orientationd,
Rand3DElasticd,
RandAffined,
Spacingd,
)
AddChanneld:用于添加图像尺寸中的通道数(并不是增加通道数)
LoadNifti:用于加载Nifti格式的文件
LoadNiftid:也是用于加载Nifti格式的文件,但是是以字典的形式进行包装保存 调用
Orientationd:重定向轴方向
Rand3DElasticd:随机3D弹性变形
RandAffined:随机仿射变换
Spacingd:图像重采样
root_dir = r'D:\Jupyter\3d_image_transforms'
data_dir = os.path.join(root_dir, "Task02_Heart")
train_images = sorted(glob.glob(os.path.join(data_dir, "imagesTr", "*.nii.gz")))
train_labels = sorted(glob.glob(os.path.join(data_dir, "labelsTr", "*.nii.gz")))
data_dicts = [
{"image": image_name, "label": label_name}
for image_name, label_name in zip(train_images, train_labels)
]
train_data_dicts, val_data_dicts = data_dicts[:12], data_dicts[12:]
设置一个字典data_dicts存image和label两个key值,将前12个数据划分为训练集,之后的都是验证集。
loader = LoadNifti(dtype=np.float32)
image, metadata = loader(train_data_dicts[0]["image"])
print(f"input: {train_data_dicts[0]['image']}")
print(f"image shape: {image.shape}")
print(f"image affine:\n{metadata['affine']}")
print(f"image pixdim:\n{metadata['pixdim']}")
'''
input: D:\Jupyter\3d_image_transforms\Task02_Heart\imagesTr\la_003.nii.gz
image shape: (320, 320, 130)
image affine:
[[1.25 0. 0. 0. ]
[0. 1.25 0. 0. ]
[0. 0. 1.37 0. ]
[0. 0. 0. 1. ]]
image pixdim:
[1. 1.25 1.25 1.37 0. 0. 0. 0. ]
'''
loader = LoadNiftid(keys=("image", "label"))
data_dict = loader(train_data_dicts[0])
print(f"input:, {train_data_dicts[0]}")
print(f"image shape: {data_dict['image'].shape}")
print(f"label shape: {data_dict['label'].shape}")
print(f"image pixdim:\n{data_dict['image_meta_dict']['pixdim']}")
'''
input:, {'image': 'D:\\Jupyter\\3d_image_transforms\\Task02_Heart\\imagesTr\\la_003.nii.gz', 'label': 'D:\\Jupyter\\3d_image_transforms\\Task02_Heart\\labelsTr\\la_003.nii.gz'}
image shape: (320, 320, 130)
label shape: (320, 320, 130)
image pixdim:
[1. 1.25 1.25 1.37 0. 0. 0. 0. ]
'''
给出了两种读取Nifti格式文件的方法,一种是直接读取文件的形式,另一种是通过创建好的字典中的键值进行读取。一般训练有监督的网络都是需要一对数据,即图像和标签作为训练样本,调用LoadNiftid接口更方便一点。
image, label = data_dict["image"], data_dict["label"]
plt.figure("visualize", (8, 4))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(image[:, :, 75], cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(label[:, :, 75])
plt.show()
add_channel = AddChanneld(keys=["image", "label"])
datac_dict = add_channel(data_dict)
print(f"image shape: {datac_dict['image'].shape}")
MONAI的大多数图像转换都需要图像尺寸格式[num_channels, spatial_dim_1, spatial_dim_2, … ,spatial_dim_n],第一位需要有通道数,所以这块代码作用是添加通道数,并且添加到首位。现在的图像格式为:image shape: (1, 320, 320, 130)
1.图像重采样(Spacingd)
spacing = Spacingd(keys=["image", "label"], pixdim=(1.5, 1.5, 5.0), mode=("bilinear", "nearest"))
data_dict = spacing(datac_dict)
print(f"image shape: {data_dict['image'].shape}")
print(f"label shape: {data_dict['label'].shape}")
print(f"image affine after Spacing:\n{data_dict['image_meta_dict']['affine']}")
print(f"label affine after Spacing:\n{data_dict['label_meta_dict']['affine']}")
'''
image shape: (1, 267, 267, 36)
label shape: (1, 267, 267, 36)
image affine after Spacing:
[[1.5 0. 0. 0. ]
[0. 1.5 0. 0. ]
[0. 0. 5. 0. ]
[0. 0. 0. 1. ]]
label affine after Spacing:
[[1.5 0. 0. 0. ]
[0. 1.5 0. 0. ]
[0. 0. 5. 0. ]
[0. 0. 0. 1. ]]
'''
这里我理解的pixdim是像素间距的意思,原图像的shape(1, 320, 320, 130) pixdim(1,1.25,1.25,1.37),重采样后shape(1, 267, 267, 36)pixdim(1,1.5, 1.5, 5.0),像素间距扩大相应倍数后,尺寸缩小相应倍数。
image, label = data_dict["image"], data_dict["label"]
plt.figure("visualise", (8, 4))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(image[0, :, :, 21], cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(label[0, :, :, 21])
plt.show()
计算:1.37/5*75=21 这是重采样后原切片深度对应的深度,可以看到尺寸发生了改变,其他没有变化。
2.重定向轴方向(Reorientation to a designated axes codes)
orientation = Orientationd(keys=["image", "label"], axcodes="PLI")
data_dict = orientation(data_dict)
print(f"image shape: {data_dict['image'].shape}")
print(f"label shape: {data_dict['label'].shape}")
print(f"image affine after Spacing:\n{data_dict['image_meta_dict']['affine']}")
print(f"label affine after Spacing:\n{data_dict['label_meta_dict']['affine']}")
'''
image shape: (1, 267, 267, 36)
label shape: (1, 267, 267, 36)
image affine after Spacing:
[[ 0. -1.5 0. 399. ]
[ -1.5 0. 0. 399. ]
[ 0. 0. -5. 175. ]
[ 0. 0. 0. 1. ]]
label affine after Spacing:
[[ 0. -1.5 0. 399. ]
[ -1.5 0. 0. 399. ]
[ 0. 0. -5. 175. ]
[ 0. 0. 0. 1. ]]
'''
默认轴标签为左(L)、右(R)、后(P)、前(A)、下(I)、上(S)。
创建以下变换以将体积重新定向为“后、左、下”(PLI)方向。
image, label = data_dict["image"], data_dict["label"]
plt.figure("visualise", (8, 4))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(image[0, :, :, 15], cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(label[0, :, :, 15])
plt.show()
轴方向改变,读取切片的深度也相应改变,总depth是36,所以我设置depth为15(误打误撞竟然真的对了,可能是顺序由从前向后变成了从后向前?20对称过去就是15,这个地方我比较疑惑…)。
3.随机仿射变换(Random affine transformation)
rand_affine = RandAffined(
keys=["image", "label"],
mode=("bilinear", "nearest"),
prob=1.0,
spatial_size=(267, 267, 36),
translate_range=(40, 40, 2),
rotate_range=(np.pi / 36, np.pi / 36, np.pi / 4),
scale_range=(0.15, 0.15, 0.15),
padding_mode="border",
)
RandAffined中的参数表示可以参考MONAI官方文档:https://docs.monai.io/en/latest/transforms.html#randaffined
affined_data_dict = rand_affine(data_dict)
print(f"image shape: {affined_data_dict['image'].shape}")
# image shape: torch.Size([1, 267, 267, 36])
image, label = affined_data_dict["image"][0], affined_data_dict["label"][0]
plt.figure("visualise", (8, 4))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(image[:, :, 18], cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(label[:, :, 18])
plt.show()
因为是随机变换,所以每运行一次结果都是不一样的。
4.随机弹性变换(Rand3DElasticd)
rand_elastic = Rand3DElasticd(
keys=["image", "label"],
mode=("bilinear", "nearest"),
prob=1.0,
sigma_range=(5, 8),
magnitude_range=(100, 200),
spatial_size=(267, 267, 36),
translate_range=(50, 50, 2),
rotate_range=(np.pi / 36, np.pi / 36, np.pi),
scale_range=(0.15, 0.15, 0.15),
padding_mode="border",
)
Rand3DElasticd中的参数表示可以参考MONAI官方文档:https://docs.monai.io/en/latest/transforms.html#rand3delasticd
deformed_data_dict = rand_elastic(data_dict)
print(f"image shape: {deformed_data_dict['image'].shape}")
# image shape: (1, 267, 267, 36)
image, label = deformed_data_dict["image"][0], deformed_data_dict["label"][0]
plt.figure("visualise", (8, 4))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(image[:, :, 5], cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(label[:, :, 5])
plt.show()