MONAI Transform 分析和使用

最近在学习深度学习,了解了monai框架,随着学习的不断深入,发现Transform这个模块很重要在做医学图像处理的时候需要用到图像增强,就需要用到个Transform中的各种API进行增强变换

所以在这里记录下,以便大家参考

一.Transform 的分类

 具体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

三.Transform组合使用案列

1.导入transforms模块

from monai.transforms import (
    AsDiscrete,
    EnsureChannelFirstd,
    Compose,
    LoadImaged,
    ScaleIntensityRanged,
    Spacingd,
    Orientationd,
    CropForegroundd,
    RandCropByPosNegLabeld,
    RandAffined,
    RandRotated,
    EnsureType,
    EnsureTyped,
)

 2.使用Compose组合各种变换

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"]),
    ]
)

四 测试使用transforms API

接下来会演示部分常用的transform,其余的类似。以下演示基于字典变换。

1.数据准备

这次使用的数据是医学图像十项全能挑战赛里面的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:]

 如下图可以看到,我活得4组数据的路径地址

 2.加载NIfTI 格式的文件[LoadImage/ LoadImaged]

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'] 保存了图像的体素维度信息,等等

2.1 加载显示

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

 3.方向变换(Orientationd)

 

对图像进行一个方向变换,图像默认的方向是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']}")

 4.添加和变换通道[AddChanneld/EnsureChannelFirstd/AsChannelFirstd/AsChannelLastd]

 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}")

运行结果

 

 5.强度变换 

常用的有[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

 6.空间变换[Rotate90d / Resized]

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

Rotate90d 

 

 Resized

 

参 数 介 绍

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

  • 10
    点赞
  • 52
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
阿里的DataV和京东的莫奈都是优秀的大数据可视化平台,但是在以下几个方面存在一些差异: 1. 技术架构:DataV采用了基于Kubernetes的容器化架构,可以快速扩展、自动化部署和管理,同时支持多云部署;莫奈则采用了基于Spark的分布式计算架构,支持GPU加速,能够快速处理大规模数据。 2. 用户群体:DataV更注重企业级用户,提供了丰富的企业级功能和服务,支持多种数据源接入,适用于金融、电商、物流等行业的大屏展示和实时监控;而莫奈则更注重学术机构、政府部门等研究型用户,提供了多种数据挖掘和预测分析算法和模型。 3. 功能模块:DataV提供了多种实时数据接入和展示方式,支持多种数据可视化图表和报表,同时提供了数据分析和预测分析算法和模型;而莫奈则更注重数据挖掘和预测分析功能,提供了更加多样化的算法和模型库,同时支持地图可视化和物联网数据接入。 4. 扩展性:DataV提供了插件式开发和二次开发的方式,可以灵活地进行定制和扩展,支持多种开发语言和框架;而莫奈则更注重算法和模型的扩展和优化,提供了多种扩展方式和可配置参数。 综上所述,阿里的DataV和京东的莫奈都是优秀的大数据可视化平台,但是在技术架构、用户群体、功能模块和扩展性方面存在一些差异。用户可以根据自身需求选择不同的平台,或者结合两个平台的优势进行定制开发。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值