最近在学习深度学习,了解了monai框架,随着学习的不断深入,发现Transform这个模块很重要在做医学图像处理的时候需要用到图像增强,就需要用到个Transform中的各种API进行增强变换
所以在这里记录下,以便大家参考
具体API函数请参考文档:https://docs.monai.io/en/latest/transforms.html
普通变换又可以说是基于数组的变换:image和label是以数组形式给到Dataset。字典变换是基于字典的变换(image和label是一个字典对)。
普通变换和字典变换的功能是一样的,只是字典变换在每个transform后面都加了一个"d", 也可以写成”D“。如LoadImage/LoadImaged, Resize/Resized
使用字典变换时,必须指明该变换是对image做,还是label做。如,LoadImaged(keys='image'),表明只加载image
from monai.transforms import (
AsDiscrete,
EnsureChannelFirstd,
Compose,
LoadImaged,
ScaleIntensityRanged,
Spacingd,
Orientationd,
CropForegroundd,
RandCropByPosNegLabeld,
RandAffined,
RandRotated,
EnsureType,
EnsureTyped,
)
train_transforms = Compose(
[
LoadImaged(keys=["image", "label"]),
EnsureChannelFirstd(keys=["image", "label"]),
Spacingd(keys=["image", "label"], pixdim=(
1.5, 1.5, 2.0), mode=("bilinear", "nearest")),
Orientationd(keys=["image", "label"], axcodes="RAS"),
ScaleIntensityRanged(
keys=["image"], a_min=-57, a_max=164,
b_min=0.0, b_max=1.0, clip=True,
),
CropForegroundd(keys=["image", "label"], source_key="image"),
RandCropByPosNegLabeld(
keys=["image", "label"],
label_key="label",
spatial_size=(96, 96, 96),
pos=1,
neg=1,
num_samples=4,
image_key="image",
image_threshold=0,
),
RandAffined(
keys=['image', 'label'],
mode=('bilinear', 'nearest'),
prob=0.5,
spatial_size=(96, 96, 96),
rotate_range=(np.pi/18, np.pi/18, np.pi/5),
scale_range=(0.05, 0.05, 0.05)
),
EnsureTyped(keys=["image", "label"]),
]
)
val_transforms = Compose(
[
LoadImaged(keys=["image", "label"]),
EnsureChannelFirstd(keys=["image", "label"]),
Spacingd(keys=["image", "label"], pixdim=(
1.5, 1.5, 2.0), mode=("bilinear", "nearest")),
Orientationd(keys=["image", "label"], axcodes="RAS"),
ScaleIntensityRanged(
keys=["image"], a_min=-57, a_max=164,
b_min=0.0, b_max=1.0, clip=True,
),
RandRotated(
keys=['image', 'label'],
mode=('bilinear', 'nearest'),
range_x=np.pi/18,
range_y=np.pi/18,
range_z=np.pi/5,
prob=1.0,
padding_mode=('reflection', 'reflection'),
),
CropForegroundd(keys=["image", "label"], source_key="image"),
EnsureTyped(keys=["image", "label"]),
]
)
接下来会演示部分常用的transform,其余的类似。以下演示基于字典变换。
这次使用的数据是医学图像十项全能挑战赛里面的CT数据(Task09_Spleen)。数据来源:http://medicaldecathlon.com/
数据比较大,可以使用迅雷下载
"https://msd-for-monai.s3-us-west-2.amazonaws.com/Task09_Spleen.tar"
如果网速不好,也可以使用其他可用数据,用于测试了,无所谓了,能跑就行
我把数据解压好放在了特定的目录,你也可以参考MONAI加载数据方式自己组织
directory = os.environ.get("MONAI_DATA_DIRECTORY")
root_dir = tempfile.mkdtemp() if directory is None else directory
print(f"root dir is: {root_dir}")
data_dir = os.path.join(root_dir,"Task09_Spleen")
print(f"data dir is:{data_dir}")
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[:4], train_labels[:4])
]
train_data_dicts, val_data_dicts = data_dicts[:2], data_dicts[-2:]
MONAI的一个设计选择是,它不仅提供高级工作流组件,而且以最小的功能形式提供相对较低级别的api。
LoadImage类是底层Nibabel映像加载器的简单可调用包装器在使用一些必要的系统参数构造加载程序之后,使用NIfTI文件名调用加载程序实例将返回图像数据数组以及元数据,
例如仿射信息和体素大小。简单说就是,如果是nii.gz格式的文件,调用LoadImage,它会自动调用Nibabel来打开数据。在python中,nii.gz一般都是通过Nibabel来打开的。
LoadImage/ LoadImaged在使用时会有细小的差别,通过举例来说明。
注意注意
如果这些变换不是在Compose里面进行组合使用,单独调用时都是需要先实例化的
loader = LoadImaged(keys=("image", "label"))
data_dict = loader(train_data_dicts[0])
print(f"image shape: {data_dict['image'].shape}")
print(f"label shape: {data_dict['label'].shape}")
print("max pixel val:{}".format(data_dict["image"].max()))
print("min pixel val:{}".format(data_dict["image"].min()))
print(f"image pixdim:\n{data_dict['image_meta_dict']['pixdim'][2]}")
print(f"image dim:\n{data_dict['image_meta_dict']['dim']}")
print(f"data_dict keys:{data_dict.keys()}")
print(f"data_dict img_meta_dict :{data_dict['image_meta_dict'].keys()}\n")
print(f"data_dict img_meta_dict :{data_dict['image_meta_dict'].values()}\n")
如上图LoadImaged是会加载图像数据和元数据的,我们打印data_dict.keys() =dict_keys(['image', 'label', 'image_meta_dict', 'label_meta_dict']) 里面包含了image和label的图像数据索引key,元数据索引key
元数据的key默认保存在{key_meta_dict}中,例如上图中的image的元数据的key 是 image_meta_dict,label的元数据key是label_meta_dict
我们可以根据data_dict['image_meta_dict']中的key来获取元数据信息
例如data_dict['image_meta_dict']['filename_or_obj'] 中保存了Image的url
data_dict['image_meta_dict']['pixdim'] 保存了图像的体素维度信息,等等
image, label = data_dict["image"], data_dict["label"]
plt.figure("visualize", (8, 4))
plt.subplot(1, 2, 1)
plt.title("image")
plt.imshow(image[:,:,24], cmap="gray")
plt.subplot(1, 2, 2)
plt.title("label")
plt.imshow(label[:,:,24])
plt.show()
对图像进行一个方向变换,图像默认的方向是RAS坐标表示,如上图,进行了一个LPS的转换
axcodes 指定一个一个轴向默认是"RAS"
as_closest_canonical 如果为 True,则加载最接近规范轴格式的图像。 并忽略axcodes的参数
4.分辨率变换 [Spacingd]
spacing = Spacingd(keys=["image","label"],
pixdim=(1.5, 1.5, 5.0), mode=("bilinear", "nearest"))
print(f"label affine before Spacingd:\n{data_dict['label_meta_dict']['affine']}")
data_dict_spa = spacing(data_dict)
print(f"image affine after Spacingd:\n{data_dict_spa['image_meta_dict']['affine']}")
1.AddChanneld
向输入图像添加一个长度为 1 的通道维度。
大多数图像变换假设输入图像采用通道优先格式,其形状 为 (num_channels, spatial_dim_1[, spatial_dim_2, …]).monai.transforms
2. EnsureChannelFirstd
自动添加一个通道的维度确保通道在shape中的第一位置
3.AsChannelFirstd
将图像的通道维度更改为第一维度。
4. AsChannelLastd
将图像的通道维度更改为最后一个维度。 参数channel_dim指定哪个维度改为最后一个维度,默认第一个维度(channel_dim=0)
print(f"AddChanneld before image shape: {data_dict['image'].shape}")
add_channel = AddChanneld(keys=["image", "label"])
data_dict_addc = add_channel(data_dict)
print(f"AddChanneld after image shape: {data_dict_addc['image'].shape}")
运行结果
常用的有[NormalizeIntensityd / ScaleIntensityRanged/ScaleIntensityd]
NormalizeIntensityd
查看源码发现,该函数使用的归一化方法是 image-subtrahend/divisor
参 数 介 绍
subtrahend:被减数, 可以自己指定,默认为整个图像的均值。
divisor: 除数, 可以自己指定,默认为整个图像的标准差。
nonzero(bool): 等于True,表示只对图像的非0区域做归一化。
channel_wise(bool): 当不指定subtrahend和divisor,为True, 表示在每个通道上进行计算均值和标准差,为False,则在整个图像上计算均值和标准差
ScaleIntensityd
1 将输入图像的强度缩放到给定的值范围 (minv, maxv)。如果未提供 minv 和 maxv,则使用因子按 v = v * (1 + 因子) 缩放图像。
minv 默认为 0
maxv 默认为 1
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
使用旋转,尤其是随机旋转的时候一定要注意,如果是分割,image和label应当一同旋转,并且旋转方式要一模一样。
Rotate90d
参 数 介 绍
spatial_size: 序列[int, int,], 期大小调整操作之后的空间尺寸的形状
mode: resize使用的插值方式,
'默认为”area, 选 择"nearest", "linear", "bilinear", "bicubic", "trilinear"
MONAI API:https://docs.monai.io/en/latest/api.html
MONAI tutorials:https://github.com/Project-MONAI/tutorials
csdn博客:https://blog.csdn.net/u014264373/article/details/113742194
csdn博客:https://blog.csdn.net/u014264373/article/details/113752226