针对在使用传统图像处理工具时可能遇到的困难,深度学习已成为医疗保健领域的主要解决方案。
因为医学图像比标准图像更难处理(高对比度、人体的广泛变化……)深度学习用于分类、对象检测,尤其是分割任务。
在分割方面,深度学习用于分割人体器官,如肝脏、肺和……或分割来自身体不同部位的肿瘤。
医学图像有很多不同的类型,例如 MRI(主要用于脑肿瘤分割)、CT 扫描、PET 扫描
等。
本文将重点介绍 CT 扫描
,但同样的操作也适用于其他类型。
所以我们知道执行深度学习任务需要许多步骤,其中一个是数据预处理,这是我们在开始训练之前必须做的第一件事。这是本文的主题;我们将讨论可用于执行此预处理的工具。
准备数据因任务而异;例如,分类是最简单的,因为我们只需要准备图像,而对象检测和分割则需要我们准备图像以及标签(用于分割的边界框或掩码)。
在本文中,我们将以分割为例,它可以用于肿瘤或器官的分割。
在医学成像中,我们可以处理 2D 图像,可以是 dicom、JPG 或 PNG
,也可以是 3D 卷(volumes)
,它们是一组切片,每个切片是一个 2D 文件(大多数时候是 dicom
),而这个组是一个nifti
文件,代表整个患者或仅代表他身体的一部分。
为了完成这个任务,我们将使用一个名为 monai
的开源框架,它基于我在实习期间使用的 PyTorch,发现它非常有用。
如果您想阅读其文档,请参阅此链接。
现在将开始编写我们的函数来执行 CT 扫描中肿瘤分割的预处理。第一步是使用 pip 或 conda 安装库,具体取决于您使用的环境。
pip install monai
我强烈建议你为你的项目设置一个虚拟环境,因为这个库直接安装在系统中时并不总是有效。
然后你需要安装 PyTorch
和一些 monai
的依赖。
pip install torch
pip install torch-vision
pip install "monai-weekly[gdwon, nibabel,tqdm]"
然后你需要包含你需要的库。
1- 第一种是单独加载图像和掩码(如果您想进行图像分类,可以使用这种方式,但它也适用于分割)。
2- 第二种方法是创建一个带有两列的Python字典,一列用于图像路径,一列用于标签路径。然后在每一行输入具有相应掩码的图像的路径。
就个人而言,我更喜欢第二种方法,因为当我们应用变换时,我们将能够只选择图像或标签或两者的关键工作,而不是为图像创建变换并为标签创建变换(掩码)。
您现在必须创建一个包含整个数据集路径的变量。就我而言,我有四个文件夹:TrainData、TrainLabels 训练图像和掩码和ValData、ValLabels 验证图像和掩码。
要将多个变换应用于同一患者,我们将使用 monai
的compose
功能,它允许您组合所需的任何变换(在 monai
文档中定义的变换)。
在应用任何转换时,您应该注意一些事情:一些步骤是必需的,而另一些是可选的。
使用 monai
时,主要的转换是“Load image”
以加载nifty
的文件,以及“ToTensor”
将转换后的数据转换为torch
张量,以便我们可以将其用于训练。
现在我们已经介绍了基本的转换,我们将继续讨论其他转换。我将讨论在实习期间特别重要的五个转换。
AddChanneld
:这个函数会为我们的图像和标签添加一个通道(当我说图像时,我是指多个切片的volume),因为我们在做肿瘤分割时,需要一个通道来扮演背景或肿瘤的角色。
Spacingd
:这个函数将帮助我们改变体素维度,因为我们不知道医学图像数据集是通过相同扫描还是不同扫描获得的,因此它们可能具有不同的体素维度(宽度、高度、深度),所以我们需要将它们推广到相同的维度。
ScalIntensityRanged
:这个函数将帮助我们同时执行两个任务:第一个是将密集视觉的对比度改变为更明显的东西,第二个是将体素值归一化并将它们置于 0 和 1 之间这样训练会更快。
CropForegroundd
:这个功能将帮助我们裁剪掉我们不需要的图像的空白区域,只留下感兴趣的区域。
Resized
:最后,这个函数是可选的,但在我看来,如果你使用了cropforeground
函数,它是必需的,因为将进行裁剪的函数将根据每个患者的不同得到随机尺寸的输出,所以如果我们不添加为所有患者提供相同尺寸的操作,我们的模型将不起作用。
如您所见,我们使用的每个函数的末尾都有一个“d”
,这表示使用字典(如果您不使用字典,则需要将其删除)。我们为每个操作添加了参数“keys”
,以指定我们是否希望将该变换应用于图像、标签或两者。您可以看到我们几乎将所有函数都应用于标签和图像,但在 ScaleIntensityRange
中我们只将其应用于图像,因为我们不需要更改标签的强度或标准化标签的值。
现在,与任何深度学习代码一样,我们必须先加载数据及其转换,然后才能开始训练;为此,必须使用两个基本功能。
Dataset:此函数将定义数据及其转换,因此如果您有训练集和验证集,则需要创建两个“数据集”函数,一个用于将训练数据与其转换组合,另一个用于组合验证数据及其转换。当然,如果对训练集和验证集应用相同的变换,则必须在函数dataset
的参数transform
中使用相同的变换。
Dataloader:这个函数将以特定的批处理大小将数据加载到RAM中。 将有两个数据加载器,一个用于训练,一个用于验证。
应用转换后,您可以绘制一些切片以查看使用和不使用转换的数据有何不同。
您可以使用 monai
函数“first”
从数据加载器中获取第一个项目,这将是第一个患者。
以下是一些代码行,可帮助您绘制示例。
现在让我们看看有和没有转换的输出:
左边是没有变换的切片,中间是有变换的切片,右边是对应的标签。
这就是您使用 monai
准备进行 3D volumes
分割的方式。如果你想看更多的变换,去monai
的网站。但是,一些转换是为数据增强而不是预处理而设计的。
import os
from glob import glob
import torch
from monai.transforms import (
Compose,
LoadImaged,
ToTensord,
AddChanneld,
Spacingd,
ScaleIntensityRanged,
CropForegroundd,
Resized,
)
from monai.data import Dataset, DataLoader
from monai.utils import first
import matplotlib.pyplot as plt
data_dir = 'D:/3_Stage/ALL_THE_DATA/fixed_data_03_august/all_together'
train_images = sorted(glob(os.path.join(data_dir, 'TrainData', '*.nii.gz')))
train_labels = sorted(glob(os.path.join(data_dir, 'TrainLabels', '*.nii.gz')))
val_images = sorted(glob(os.path.join(data_dir, 'ValData', '*.nii.gz')))
val_labels = sorted(glob(os.path.join(data_dir, 'ValLabels', '*.nii.gz')))
train_files = [{"image": image_name, 'label': label_name} for image_name, label_name in zip(train_images, train_labels)]
val_files = [{"image": image_name, 'label': label_name} for image_name, label_name in zip(val_images, val_labels)]
# load the images
# do any transforms
# need to convert them into torch tensors
orig_transforms = Compose(
[
LoadImaged(keys=['image', 'label']),
AddChanneld(keys=['image', 'label']),
ToTensord(keys=['image', 'label'])
]
)
train_transforms = Compose(
[
LoadImaged(keys=['image', 'label']),
AddChanneld(keys=['image', 'label']),
Spacingd(keys=['image', 'label'], pixdim=(1.5, 1.5, 2)),
ScaleIntensityRanged(keys='image', a_min=-200, a_max=200, b_min=0.0, b_max=1.0, clip=True),
CropForegroundd(keys=['image', 'label'], source_key='image'),
Resized(keys=['image', 'label'], spatial_size=[128,128,128]),
ToTensord(keys=['image', 'label'])
]
)
val_transforms = Compose(
[
LoadImaged(keys=['image', 'label']),
AddChanneld(keys=['image', 'label']),
Spacingd(keys=['image', 'label'], pixdim=(1.5, 1.5, 2)),
ScaleIntensityRanged(keys='image', a_min=-200, a_max=200, b_min=0.0, b_max=1.0, clip=True),
ToTensord(keys=['image', 'label'])
]
)
orig_ds = Dataset(data=train_files, transform=orig_transforms)
orig_loader = DataLoader(orig_ds, batch_size=1)
train_ds = Dataset(data=train_files, transform=train_transforms)
train_loader = DataLoader(train_ds, batch_size=1)
val_ds = Dataset(data=val_files, transform=val_transforms)
val_loader = DataLoader(val_ds, batch_size=1)
test_patient = first(train_loader)
orig_patient = first(orig_loader)
print(torch.min(test_patient['image']))
print(torch.max(test_patient['image']))
# tensor(-3024.)
# tensor(1911.7750)
plt.figure('test', (12, 6))
plt.subplot(1, 3, 1)
plt.title('Orig patient')
plt.imshow(orig_patient['image'][0, 0, : ,: ,30], cmap= "gray")
plt.subplot(1, 3, 2)
plt.title('Slice of a patient')
plt.imshow(test_patient['image'][0, 0, : ,: ,30], cmap= "gray")
plt.subplot(1,3,3)
plt.title('Label of a patient')
plt.imshow(test_patient['label'][0, 0, : ,: ,30])
plt.show()
https://pycad.co/preprocessing-3d-volumes-for-tumor-segmentation-using-monai-and-pytorch/