基于Unet的BraTS 3d 脑肿瘤医学图像分割,从nii.gz文件中切分出2D图片数据

1、前言

3D图像分割一直是医疗领域的难题,在这方面nnunet已经成为了标杆,不过nnunet教程较少,本人之前跑了好久,一直目录报错、格式报错,反正哪里都是报错等等。并且,nnunet对于硬件的要求很高,一般的电脑配置或者低配置的服务器完全带不起来

或者定义conv.3D的unet网络模型,但对显卡的要求也很高...

之前实现了unet的自适应多类别分割任务,博文如下

Unet 实战分割项目、多尺度训练、多类别分割_unet进行多类分割-CSDN博客

代码根据数据集的mask,可以自动计算出mask前景的类别,这样就能为unet的输出自动调整,不需要更改别的操作。

而3d的图像其实就是2d拼接起来的,或许可以将nii格式的3d图片切分,这样根据上文的代码就可以实现医疗图像3d的分割

提示:这里切分的2d分割,效果肯定不如3d图像的分割

就比如线性回归对图像的分类,忽略了像素点的空间信息。那么3D切割出2D,其实也是忽略了图像的空间信息,效果肯定不如3d的直接分割

2、 nii 文件的切分

复制代码
import SimpleITK as sitk 

这里用itk 对3d数据进行读取

2.1 数据集

这里的3d数据是 BRATS 脑肿瘤分割数据(brain tumor segmentation challenge,BraTS Chanllenge),这里只对训练集进行操作

需要注意的是,一般的nii图像都是3D的,这里数据是4D的,好像是每个3D图像的模态,类似于官方的增强?

T1 成像,利于观察解剖结构,病灶显示不够清晰

T1gd 在受试者做磁共振之前向血液内注射造影剂,使成像中血流活跃的区域更加明显,是增强肿瘤的重要判据

T2 成像,病灶显示较为清晰,判断整颗肿瘤

FLAIR(抑制脑脊液的高信号),含水量大则更亮眼,可以判断瘤周水肿区域

mask模板是四分类的:

2.2 slice 切片代码

代码放在这里:

这里参考之前的博文:nii 文件的相关操作(SimpleITK)_如何使用nii文件做深度学习-CSDN博客

python 复制代码
import SimpleITK as sitk
import numpy as np
import os
from tqdm import tqdm
import shutil
import cv2


# 新建目录
def mkdir(rt):
    ret_path = rt + '_ret2D'
    if os.path.exists(ret_path):        # 删除之前的切片目录
        shutil.rmtree(ret_path)

    os.mkdir(ret_path)
    os.mkdir(os.path.join(ret_path,'images'))
    os.mkdir(os.path.join(ret_path,'labels'))


def get_image_from_nii(x,y,name,thre):  # 传入nii文件,对nii进行切片
    img = sitk.ReadImage(x)
    img_array = sitk.GetArrayFromImage(img)  # nii-->array

    label = sitk.ReadImage(y)
    label_array = sitk.GetArrayFromImage(label)  # nii-->array

    for index,i in enumerate(range(img_array.shape[1])):    # TODO 需要根据img维度更改,4D设定为1,3D设置为0
        img_select = img_array[0,i, :, :]       # TODO 需要根据img维度更改,从x轴切分,[:,i,:]从y轴切分
        label_select = label_array[i, :, :]

        # 图片保存目录
        img_save_name = os.path.join(root+'_ret2D','images',name+'_'+str(index)+'.png')
        label_save_name = os.path.join(root+'_ret2D','labels',name+'_'+str(index)+'.png')

        h,w = label_select.shape
        total_pixel = h*w           # 总的像素点个数

        if label_select.max() == 0:     # 没有前景的像素点不保存
            continue

        else:
            # 归一化
            img_select = (img_select - img_select.min()) / (img_select.max() - img_select.min())*255

            img_select = img_select.astype(np.uint8)
            label_select = label_select.astype(np.uint8)

            if (np.sum(label_select !=0 ) / total_pixel) > thre:
                cv2.imwrite(img_save_name,img_select)
                cv2.imwrite(label_save_name,label_select)


# 切片函数
def sliceMain(rt,imgf,labf,thre):
    # 删除之前的切片目录,建立新的目录
    mkdir(rt)

    nii_list = [i for i in os.listdir(os.path.join(rt,imgf))]

    for image_nii in tqdm(nii_list):      # 遍历所有的nii文件
        name = image_nii.split('.nii.gz')[0]

        image_nii = os.path.join(rt,imgf,image_nii)
        label_nii = image_nii.replace(imgf,labf)       # 自动获取nii 的标签

        get_image_from_nii(image_nii,label_nii,name,thre)


if __name__ == '__main__':

    root = 'BRATS'          # 待切分nii文件的父目录
    images_folder = 'imagesTr'      # 3d nii的数据
    labels_folder = 'labelsTr'       # 3d nii 的标签数据
    threshold = 0.03               # 分割的比例不超过阈值的数据删除

    # 切片函数
    sliceMain(
        rt=root,
        imgf=images_folder,
        labf=labels_folder,
        thre=threshold
    )

这里简单介绍一下:目录结构如下

具体数据的名称和后缀要严格对应!!!

threshold 是阈值处理,如果mask前景的像素点个数没有达到整个图片像素点的阈值,就不会被保存。这里默认是0.03

切分的时候,因为这里是4D的,所以img_array是四维的,我们默认取第一个维度的3D图像

同时,3D图像可以用x,y,z三个坐标表示,这里的shape1就是沿着x轴进行2D的切分

因为医学图像的灰度动态范围很多,可能到上千,因此这里将灰度值重新映射,变成np的uint8格式,再用cv保存

2.3 保存格式

图像的保存,这里搞了好久,要么格式问题,要么灰度有问题。这里做下总结

首先,png格式可以完整的保存2D切分的信息,而不会因为图像压缩导致mask灰度值改变。说人话就是,这里切分的2d像素值只有0 100 255,如果保存为其他格式,可能读取的时候,会产生0 1 2 3....等等灰度图像,而分割的mask是阈值图像!!

其次,plt保存的时候,会将图像重新映射,我们只想要0 1 2这种格式,但是他可能会把0变成0,1变成128.2变成255这样。虽说,这样看mask确实方便,不至于变成全黑的,但是本人测试的时候,总会莫名多出一个灰度。说人话就是,本来这里是四分类的,plt保存的时候,np.unique读取的时候,会变成5个类别

这里搞了半天,本人电脑太差,测试半天,只有这个代码是符合的。至于问题到底是不是我说的那样,可以自己测试

代码如下:

python 复制代码
import os
from tqdm import tqdm
import numpy as np
import cv2


root = './BRATS_ret2D/labels'             # 训练 mask的路径
masks_path = [os.path.join(root ,i) for i in os.listdir(root)]
gray = []           # 前景像素点
for i in tqdm(masks_path,desc="gray compute"):
    img = cv2.imread(i,0)
    img_uni = np.unique(img)        # 获取mask的灰度值

    for j in img_uni:
        if j not in gray:
            gray.append(j)
print(gray)

2.4 切分好的数据

上述代码,切分后会生成root的返回目录

这里的mask并不是全黑的,只是0 1 2 3这样导致很黑而已。这里的目录名称按照切分索引,而没有从0开始,这样就能看出来BRATS_001 里面,49之前的要么没有mask前景,要么前景的区域不足我们设定的阈值!

3、划分数据集

参考之前的代码:关于图像分割任务中按照比例将数据集随机划分成训练集和测试集_图像分割数据集怎么划分-CSDN博客

这里可以可视化一下:关于图像分割项目的可视化脚本-CSDN博客

4、训练

unet训练如下:

训练时间太长了, 这里只简单训练了10个epoch用作测试,结果如下:

代码是这篇的代码:Unet 实战分割项目、多尺度训练、多类别分割_unet进行多类分割-CSDN博客

训练日志里面,有每个类别的指标:

推理结果:

4、项目总结

1、准备好3D的nii.gz数据,然后根据本章第二节摆放好数据切分。根据项目的实际要求设定好阈值或者沿着哪个轴切分

2、划分数据很简单

3、训练的 train 脚本

4、推理的时候,把待推理的数据放在inference目录下即可

5、说点废话

对于项目的改进的思考,项目下载:

深度学习Unet实战分割项目:BraTS3d脑肿瘤图像切分的2D图片分割项目(4分类)资源-CSDN文库

因为医学图像的灰度值都很低,往往图像会很暗,这样图像的梯度信息啊、边缘信息啊都很模糊,效果不太好,可以利用医学图像常用的windowing方法,其实就是对比度拉伸

医学图像处理的windowing 方法_医学图像常用windowing和histogram equalization-CSDN博客

而且,不同于正常的分类图像,这里的normalize可能直接 - 0.5 在除以 2效果不太好,这可以手动计算好图像的mean和std,可以有效提升网络的性能

怎么计算数据的均值和方差_计算数据集均值和方差-CSDN博客

相关推荐
学习前端的小z16 分钟前
【AIGC】如何通过ChatGPT轻松制作个性化GPTs应用
人工智能·chatgpt·aigc
埃菲尔铁塔_CV算法44 分钟前
人工智能图像算法:开启视觉新时代的钥匙
人工智能·算法
EasyCVR1 小时前
EHOME视频平台EasyCVR视频融合平台使用OBS进行RTMP推流,WebRTC播放出现抖动、卡顿如何解决?
人工智能·算法·ffmpeg·音视频·webrtc·监控视频接入
打羽毛球吗️1 小时前
机器学习中的两种主要思路:数据驱动与模型驱动
人工智能·机器学习
蒙娜丽宁1 小时前
《Python OpenCV从菜鸟到高手》——零基础进阶,开启图像处理与计算机视觉的大门!
python·opencv·计算机视觉
好喜欢吃红柚子1 小时前
万字长文解读空间、通道注意力机制机制和超详细代码逐行分析(SE,CBAM,SGE,CA,ECA,TA)
人工智能·pytorch·python·计算机视觉·cnn
小馒头学python1 小时前
机器学习是什么?AIGC又是什么?机器学习与AIGC未来科技的双引擎
人工智能·python·机器学习
神奇夜光杯1 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
正义的彬彬侠1 小时前
《XGBoost算法的原理推导》12-14决策树复杂度的正则化项 公式解析
人工智能·决策树·机器学习·集成学习·boosting·xgboost
Debroon2 小时前
RuleAlign 规则对齐框架:将医生的诊断规则形式化并注入模型,无需额外人工标注的自动对齐方法
人工智能