使用MONAI深度学习框架进行3D图像空间变换

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()

在这里插入图片描述同样的,因为是随机变换,所以每运行一次结果都是不一样的。


  • 13
    点赞
  • 73
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值