MONAI提供了非常多的Dataset, 包括Dataset⭐️, IterableDataset, PersistentDataset, CacheDataset⭐️, SmartCacheDataset, ZipDataset, ArrayDataset⭐️, ImageDataset,在官网Data — MONAI 0 Documentation,data栏中查看文档
参数简介
data: 将image 和 label 的地址或值存为字典。label可以是分割的图像,也可以是分类的值(如0, 1, 2…)
transform: 根据分类或分割任务来定义
from glob import glob # 注意这是两个glob
import monai
from monai.data import Dataset
imglist = sorted(glob('/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/imagesTr/*.gz'))[:11]
labellist = sorted(glob('/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/labelsTr/*.gz'))[:11]
data_dict = [{'image': image, 'label': label} for image, label in zip(imglist, labellist)]
print(data_dict)
输出:
[{'image': '/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/imagesTr/002chenxiaojiao_t2_0000.nii.gz',
'label': '/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/labelsTr/002chenxiaojiao_t2.nii.gz'},
{'image': '/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/imagesTr/002liushide_t2_0000.nii.gz',
'label': '/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/labelsTr/002liushide_t2.nii.gz'},
……]
得到的是字典类型,一个字典项中对应图片与标签的地址,这就把image和label搞好了,接下来就是把配对好的数据组装成dataset和dataloder即可。
如果我想添加一个通道维,并且放到首位应该怎么处理?
from monai.transforms import LoadImaged,EnsureChannelFirstd
# 先创建一个加载nii数据的loader
loader = LoadImaged(keys=("image", "label"))
# 读取刚构建字典中的第一个数据
data_dict = loader(data_dict[0])
#打印信息
print(f"image shape: {data_dict['image'].shape}")
print(f"label shape: {data_dict['label'].shape}")
#添加维度
add_channel_first = EnsureChannelFirstd(keys=("image", "label"))
data_dict_addc = add_channel_first(data_dict)
#输出添加通道维后的结果
print(f"AddChanneld after image shape: {data_dict_addc['image'].shape}")
我的数据集是3D数据,所以,resize需要三个数,monai在Version0.8后,AddChannel被弃用,之后使用EnsureChannelFirst同时添加一个通道维度,并且将通道维置于首位
输出:
image shape: (640, 640, 24) label shape: (640, 640, 24) AddChanneld after image shape: (1, 640, 640, 24)
其中参数keys就是设置 作用的对象,你可以仅仅对图像做处理,也可以对图像和标签都做处理,后续介绍见transform处
如果我想对数据Resize怎么做呢?同理:
from monai.transforms import Resized
#创建一个resize的方法
resize = Resized(keys=['image', 'label'], spatial_size=(640, 640, 32))
# 对刚才添加维度后的数据进行resize操作
data_dict_resize = resize(data_dict_addc)
print(f"image shape: {data_dict_resize['image'].shape}")
print(f"label shape: {data_dict_resize['label'].shape}")
输出:
image shape: (1, 640, 640, 32) label shape: (1, 640, 640, 32)
如果有很多处理分开写太过麻烦,所以用到了transform,可以整合到一起,方便看和使用,此处简单演示,下一篇详细介绍
from monai.transforms import Compose, LoadImaged,EnsureChannelFirstd,ToMetaTensord, ToTensord, Resized
transform = Compose(
[
LoadImaged(keys=['image','label']), # 做分类,这里image需要加载
# AddChanneld(keys=['image','label']), # 给加载进来的图像加个通道,只针对图像。
EnsureChannelFirstd(keys=['image','label']),
Resized(keys=['image','label'], spatial_size=(640,640,32)),
ToMetaTensord(keys=['image','label']), # 把图像转成tensor格式
]
train_ds = Dataset(data=data_dict[:7], transform=transform)
val_ds = Dataset(data=data_dict[-4:], transform=transform)
train_loader = monai.data.DataLoader(
train_ds, batch_size=4, shuffle=True, num_workers=2)
test_loader = monai.data.DataLoader(
val_ds, batch_size=2, shuffle=True, num_workers=2
)
对处理后的数据进行展示
import matplotlib.pyplot as plt
img = train_ds[0]["image"]
labels = train_ds[0]["label"]
img_shape = img.shape
label_shape = labels.shape
print(f"image shape: {img_shape}, label shape: {label_shape}")
plt.figure("image", (18, 6))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(img[0, :, :, 20].detach().cpu(), cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(labels[0, :, :,20].detach().cpu())
plt.show()
具有缓存机制的数据集,即在第一个epoch前,把所有训练的数据都加载进缓存,这样可以加快网络提取数据的速度。第一个是,它缓存的是确定性变换(或者说是非随机变换)后的数据,而不是原始数据。比如在transform中,使用的变换,其中,LoadImaged, EnsureChannelFirstd, Spacingd, Orientationd,SacleIntensityRanged都是确定性变化,于是缓存的是经过这些变换之后的数据。而剩余的变换包括(RandCropByPosNegLabels, ToTensord)是每次迭代的时候进行的。
总 结
CacheDataset缓存确定性变换后的结果,其余的随机变换在每次迭代的时候进行。根据这个规则,我们应该把尽可能多的确定性变换放在随机变换之前。而一般来讲,ToTensord()是确定性的,但通常应该是最后一个变换。
在使用带缓存机制的dataset的时候,会在训练前处理数据,因此加载数据的过程会很慢,但是训练的时候会比普通的快。一般也是用CacheDataset比较多。
参数介绍
data和transform同前面的Dataset.
cache_num: 要缓存的项目数(int)。默认值为sys.maxsize
cache_rate: 缓存数据占总数的百分比。默认为1(即全部缓存)
num_workers: 要使用的工作进程数目
前面两种Dataset都是基于字典的,如果你不喜欢用字典模式,就可以考虑使用这个数组模式。
普通变换和字典变换的联系与区别
普通变换又可以说是基于数组的变换:image和label是以数组形式给到Dataset。字典变换是基于字典的变换(image和label是一个字典对)。
普通变换和字典变换的功能是一样的,只是字典变换在每个transform后面都加了一个"d", 也可以写成”D“。如LoadImage/LoadImaged, Resize/Resized, 使用字典变换时,必须指明该变换是对image做,还是label做。如,LoadImaged(keys=‘image’),表明只加载image
不管是分类还是分割,都可以使用该数组模式。在该模式下,可以确保为图像,分割,分割标签和分类标签用相同的随机数种子。
注意注意
使用ArrayDataset加载数据时,必须设定image_only=True,只加载图像值而不加载元数据(下一节transform中数据load中会说明什么是元数据,有哪些类型),否则会报错。
参数介绍
img: 图像地址
img_transform: 用于图像的transform
seg: 如果是分割,为图像地址,如果是分类,不填即可
seg_transform: 如果是分割,填写对mask的变换
label: 如果是分类, 为数组
label_transform: 如果是分类,对label做的变换,一般不做变换
from glob import glob # 注意这是两个glob
import monai
from monai.data import ArrayDataset
from monai.transforms import Compose, LoadImage,EnsureChannelFirst, ToTensor, Resize
imglist = sorted(glob('/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/imagesTr/*.gz'))[:11]
labellist = sorted(glob('/home/qiaoqiang/nnUNetFrame/DATASET/nnUNet_raw/nnUNet_raw_data/Task012_Naoxue_t2/labelsTr/*.gz'))[:11]
由于加载的是数组,这里所有的transform都不加‘d’。
transform = Compose(
[
LoadImage(image_only=True), # # 使用数组加载数据时,必须设定image_only=True,只加载图像值而不加载元数
EnsureChannelFirst(),
Resize(spatial_size=(640,640,32)),
ToTensor(),
]
)
train_ds = ArrayDataset(img=imglist, img_transform=transform,seg=labellist,seg_transform=transform)
train_loader = monai.data.DataLoader(
train_ds, batch_size=4, shuffle=True, num_workers=2)
for image, label in train_loader:
print(image.shape)
print(label.shape)
break
输出结果: