当前位置:   article > 正文

MONAI(3)—一文看懂各种Transform用法(上)_monai讲解

monai讲解

在上一次分享中,我们在Dataset方法里,已经使用了transform函数,这节课对transform做一个详细的介绍。

上一次视频连接:MONAI中,一定要学会的三种Dataset

transform大致可以分为以下几个类别

想要什么样类别的变换,就在该类别下去找。

目录

普通变换和字典变换的联系与区别

1.数据准备

2. 加载NIfTI 格式的文件【 LoadImage/ LoadImaged】

 3. 添加通道 [AddChanneld]

4. 强度变换 [NormalizeIntensityd / ScaleIntensityRanged]

5 空间变换 [Rotate90d / Resized]


普通变换和字典变换的联系与区别

  1. 普通变换又可以说是基于数组的变换:image和label是以数组形式给到Dataset。字典变换是基于字典的变换(image和label是一个字典对)。
  2. 普通变换和字典变换的功能是一样的,只是字典变换在每个transform后面都加了一个"d", 也可以写成”D“。如LoadImage/LoadImaged, Resize/Resized
  3. 使用字典变换时,必须指明该变换是对image做,还是label做。如,LoadImaged(keys='image'),表明只加载image

接下来在实战中介绍部分常用的transform,其余的类似。以下演示基于字典变换。

 

1.数据准备

这次使用的数据是医学图像十项全能挑战赛里面的CT数据(Task09_Spleen)。数据来源:http://medicaldecathlon.com/

  • 使用monai下载方法
  1. resource = "https://msd-for-monai.s3-us-west-2.amazonaws.com/Task09_Spleen.tar"
  2. md5 = "410d4a301da4e5b2f6f86ec3ddba524e"
  3. compressed_file = os.path.join(root_dir, "Task09_Spleen.tar")
  4. data_dir = os.path.join(root_dir, "Task09_Spleen")
  5. if not os.path.exists(data_dir):
  6. download_and_extract(resource, compressed_file, root_dir, md5)
  7. # root_dir: 存储地址,如‘.data/

使用该方法考验网络的速度,毕竟是国外的网址。

数据是三维的CT腹部图像,任务是脾脏的分割。

下载好数据后,先把image和label的地址加载到data_dict中,便于后续做变换。

  1. import glob
  2. data_dir = '/Volumes/Backup Plus/data/Task09_Spleen/'
  3. train_images = sorted(glob.glob(os.path.join(data_dir, "imagesTr", "*.nii.gz")))
  4. train_labels = sorted(glob.glob(os.path.join(data_dir, "labelsTr", "*.nii.gz")))
  5. data_dicts = [
  6. {"image": image_name, "label": label_name}
  7. for image_name, label_name in zip(train_images, train_labels)
  8. ]
  9. train_data_dicts, val_data_dicts = data_dicts[:-9], data_dicts[-9:]

 

2. 加载NIfTI 格式的文件【 LoadImage/ LoadImaged】

MONAI的一个设计选择是,它不仅提供高级工作流组件,而且以最小的功能形式提供相对较低级别的api。

例如,LoadImage类是底层Nibabel映像加载器的简单可调用包装器。在使用一些必要的系统参数构造加载程序之后,使用NIfTI文件名调用加载程序实例将返回图像数据数组以及元数据,例如仿射信息和体素大小。简单说就是,如果是nii.gz格式的文件,调用LoadImage,它会自动调用Nibabel来打开数据。在python中,nii.gz一般都是通过Nibabel来打开的。

LoadImage/ LoadImaged在使用时会有细小的差别,通过举例来说明。

注意注意

如果这些变换不是在Compose里面进行组合使用,单独调用时都是需要先实例化的。

  • LoadImage
  1. loader = LoadImage(dtype=np.float32) # 实例化
  2. image, metadata = loader(train_data_dicts[0]["image"]) # 把image的地址传给loader
  3. print(f"input: {train_data_dicts[0]['image']}")
  4. print(f"image shape: {image.shape}")
  5. print(f"image affine:\n{metadata['affine']}")
  6. print(f"image pixdim:\n{metadata['pixdim']}")

如上图所示,LoadImage是会加载图像的值和元数据的。元数据里面包含分辨率,放射值,具体包含什么可以通过获取键来查询(metadata.keys())

其中,最后一个”filename_or_obj“也是一个重要的信息,它是image的地址。

在实际应用中,我们不需要metadata的话,可以这样加载

  1. loader = LoadImage(image_only=True, dtype=np.float32) # 表示只需要图像值
  2. image = loader(train_data_dicts[0]["image"])
  • LoadImaged
  1. loader = LoadImaged(keys=("image", "label"))
  2. data_dict = loader(train_data_dicts[0])
  3. print(f"input:, {train_data_dicts[0]}")
  4. print(f"image shape: {data_dict['image'].shape}")
  5. print(f"label shape: {data_dict['label'].shape}")
  6. print(f"image pixdim:\n{data_dict['image_meta_dict']['pixdim']}")

这里的参数”keys“是你在data_dicts中设置的keys。表示要对image做变换还是label做变换。如果都做,就都写。比如keys=["image", "label"],  keys = 'image', keys = 'label'

我们可以通过可视化方法,查看加载进来的数据。使用LoadImage/LoadImaged加载进来的数据是numpy格式

  1. image, label = data_dict["image"], data_dict["label"]
  2. plt.figure("visualize", (8, 4))
  3. plt.subplot(1, 2, 1)
  4. plt.title("image")
  5. plt.imshow(image[:, :, 30], cmap="gray")
  6. plt.subplot(1, 2, 2)
  7. plt.title("label")
  8. plt.imshow(label[:, :, 30])
  9. plt.show()

 

 3. 添加通道 [AddChanneld]

使用monai的模型时,默认是通道优先的格式。要求数据尺寸为:[num_channels, spatial_dim_1, spatial_dim_2, ... ,spatial_dim_n]。如上,我们的数据尺寸为[512, 512, 55], 需要在前面一维通道。变成[1,512,  512,  55],使用AddChanneld就可以帮我们轻松搞定。

  1. add_channel = AddChanneld(keys=["image", "label"])
  2. datac_dict = add_channel(data_dict)
  3. print(f"image shape: {datac_dict['image'].shape}")

改变换应该用在加载数据LoadImaged后面。

4. 强度变换 [NormalizeIntensityd / ScaleIntensityRanged]

这两种都是对图像值强度进行变换的,像CT和MRI的值都是从-1000—+3000多的不等,通常需要进行归一化。

  • NormalizeIntensityd

查看源码发现,该函数使用的归一化方法是  img-subtrahend/divisor

参 数 介 绍

subtrahend:被减数, 可以自己指定,默认为整个图像的均值。

divisor: 除数, 可以自己指定,默认为整个图像的方差。

nonzero: 布尔值。等于True,表示只对图像的非0区域做归一化。

channel_wise: 布尔值。当不指定subtrahend和divisor,为True, 表示在每个通道上进行计算均值和方差,为False,则在整个图像上计算均值和方差

  1. from monai.transforms import NormalizeIntensityd, ScaleIntensityRanged
  2. norm = NormalizeIntensityd(keys='image', nonzero=False, dtype=np.float32)
  3. datac_dict_norm = norm(datac_dict)

  • ScaleIntensityRanged

ScaleIntensityRanged和NormalizeIntensityd不同之处在于, ScaleIntensityRanged可以指定把哪些范围值缩放到那个区间。

比如对脾脏的分割中,我们只在于脾脏的CT值范围(假设在-300-- +300之间),而骨头等高强度的信号(大于2000)我们不需要。如果直接将这个强度进行归一化,脾脏内部的值范围就很小。我们就可以直接把脾脏的CT值范围 [-300,+300] 进行归一化到 [0, 1], 而不在[-300,+300] 这中间的值都为0。事实上,很多论文都是这样做的。

参 数 介 绍

a_min:float,强度原始范围最小值。可以理解为需要被归一化的最小值,如我们这个例子中的-300(需要写成小数,-300.0)

a_max: float, 强度原始范围最大值。可以理解为需要被归一化的最大值,如我们这个例子中的300(需要写成小数,300.0)

b_min: float,  强度目标范围最小值。可以理解为归一化后的最小值,通常设置为0.0

b_max: float,  强度目标范围最大值。可以理解为归一化后的最大值,通常设置为1.0

clip: 布尔值。设置为True, 才会把[-300,+300]之外的值都设置为0.通常为True

  1. scale = ScaleIntensityRanged(keys='image', a_min=-300.0, a_max=300.0, b_min=0.0, b_max=1.0, clip=True)
  2. # 由于上面我们把datac_dict归一化了,因此,我们要重新制造一个datac_dict
  3. data_dict = loader(train_data_dicts[2])
  4. datac_dict = add_channel(data_dict)
  5. data_scale = scale(datac_dict)
  6. print('original max value', datac_dict['image'].max())
  7. print('original min value', datac_dict['image'].min())
  8. print('target max value', data_scale['image'].max())
  9. print('target min value', data_scale['image'].min())

5 空间变换 [Rotate90d / Resized]

  • Rotate90d:输入数组在由space_axes指定的平面中旋转90度

参 数 介 绍

prob:  float, 旋转的概率,默认为0.1, 也就是10%的概率被旋转

max_k: int, 旋转90度的次数,默认为3

spatial_axes: 元祖(int,int)。指定围绕哪个面旋转,默认为(0,1),即按前两个轴旋转。

  1. from monai.transforms import RandRotate90d, Resized
  2. rotate = RandRotate90d(keys=['image', 'label'], prob=0.5, max_k=1, spatial_axes=(2,1))
  3. data_rotate = rotate(data_scale)
  4. print(data_rotate['image'].shape)
  5. print(data_rotate['label'].shape)

注意注意

使用旋转,尤其是随机旋转的时候一定要注意,如果是分割,image和label应当一同旋转,并且旋转方式要一模一样。​​​​​​

  • Resized

参 数 介 绍

spatial_size: 序列[int, int,], 期大小调整操作之后的空间尺寸的形状

mode: resize使用的插值方式,默认为”area, “可以选择"nearest""linear""bilinear""bicubic""trilinear"

  1. resize = Resized(keys=['image', 'label'], spatial_size=(256,256,256))
  2. data_resize = resize(data_rotate)
  3. print(data_resize['image'].shape)
  4. # (1, 256, 256, 256)

 由于篇幅过长,剩下的下一篇进行分享。

 

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号