基于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博客

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-->arraylabel = sitk.ReadImage(y)label_array = sitk.GetArrayFromImage(label)  # nii-->arrayfor index,i in enumerate(range(img_array.shape[1])):    # TODO 需要根据img维度更改,4D设定为1,3D设置为0img_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.shapetotal_pixel = h*w           # 总的像素点个数if label_select.max() == 0:     # 没有前景的像素点不保存continueelse:# 归一化img_select = (img_select - img_select.min()) / (img_select.max() - img_select.min())*255img_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个类别

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

代码如下:

import os
from tqdm import tqdm
import numpy as np
import cv2root = './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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/595906.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

dcm文件数据学习

simpleITK读取数据 import SimpleITK as sitk import numpy as np import matplotlib.pyplot as plt base_path "/Users/yxk/Desktop/test/" image sitk.ReadImage(base_path"000000.dcm") # type(image) <class SimpleITK.SimpleITK.Image> imag…

leetcode代码记录(两个数组的交集

目录 1. 题目&#xff1a;2. 我的代码&#xff1a;小结&#xff1a; 1. 题目&#xff1a; 给定两个数组 nums1 和 nums2 &#xff0c;返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1&#xff1a; 输入&#xff1a;nums1 […

手工瑞士卷的自然美味,口感松软滋味甜蜜

我很喜欢吃各种甜点&#xff0c;最近我尝试了JIAN趣品牌的手工瑞士卷。很多朋友为了获得更新鲜的点心&#xff0c;一般都会选择线下店&#xff0c;因为JIAN趣支持顺丰保温箱发货&#xff0c;保证了瑞士卷在运输过程中的新鲜度和口感&#xff0c;所以入手会更加方便一些&#xf…

windows 系统下 mysql 数据库的下载与安装(包括升级安装)

windows 系统下 mysql 数据库的下载与安装&#xff08;包括升级安装&#xff09; 一、mysql 介绍&#xff1a; MySQL 是一个关系型数据库管理系统&#xff0c;由瑞典 MySQL AB 公司开发&#xff0c;属于 Oracle 旗下产品。 MySQL 是最流行的关系型数据库管理系统之一&#xf…

Kubernetes学习笔记7

使用kubeadm部署Kubernetes集群方法 使用kubernetes部署单节点Master节点K8s集群。 在实际生产环境中&#xff0c;是不允许单master节点的&#xff0c;如果单master节点不可用的话&#xff0c;当导致我们的K8s集群无法访问。 可以使用kubeadm将单master节点升级为多master节点…

java网络编程——网络编程概述及UDP/TCP通信编程的实现

前言&#xff1a; 学习到通信了&#xff0c;整理下相关知识点。打好基础&#xff0c;daydayup!!! 网络编程 网络编程指可以让设备中的程序与网络上其他设备中的程序进行数据交互。 基本的通信架构 基本的通信架构有两种形式&#xff1a;CS架构&#xff08;Client客户端/Server服…

LabVIEW深度学习

目录 一、配置环境1.1、显卡选择1.2、下载显卡驱动1.3、下载并安装Anaconda1.4、配置Anaconda软件包下载服务器1.5、配置虚拟环境tf_gpu1.6、安装vscode1.7、安装tensorflow1.8、下载安装Git1.9、安装TensorFlow Object Detection API框架1.10、安装依赖的python软件包1.11、配…

【C++航海王:追寻罗杰的编程之路】C++的类型转换

目录 1 -> C语言中的类型转换 2 -> 为什么C需要四种类型转换 3 -> C强制类型转换 3.1 -> static_cast 3.2 -> reinterpret_cast 3.3 -> const_cast 3.4 -> dynamic_cast 4 -> RTTI 1 -> C语言中的类型转换 在C语言中&#xff0c;如果赋值运…

Java | Leetcode Java题解之第9题回文数

题目&#xff1a; 题解&#xff1a; class Solution {public boolean isPalindrome(int x) {// 特殊情况&#xff1a;// 如上所述&#xff0c;当 x < 0 时&#xff0c;x 不是回文数。// 同样地&#xff0c;如果数字的最后一位是 0&#xff0c;为了使该数字为回文&#xff0…

一些Java面试题

1、 Java语言有哪些特点 1、简单易学、有丰富的类库 2、面向对象&#xff08;Java最重要的特性&#xff0c;让程序耦合度更低&#xff0c;内聚性更高&#xff09; 3、与平台无关性&#xff08;JVM是Java跨平台使用的根本&#xff09; 4、可靠安全 5、支持多线程 2、面向对象和…

错误:找不到或无法加载主类(vscode的解决方法)

项目场景&#xff1a; 某天&#xff0c;喵某人在敲代码的过程中&#xff0c;点击运行代码&#xff0c;突然显示找不到或无法加载主类。之前创建的java文件都可以正常运行。但新建的java文件无论是什么&#xff0c;点击运行都会显示“错误&#xff1a;找不到或无法加载主类”。 …

初学python记录:力扣1483. 树节点的第 K 个祖先

题目&#xff1a; 给你一棵树&#xff0c;树上有 n 个节点&#xff0c;按从 0 到 n-1 编号。树以父节点数组的形式给出&#xff0c;其中 parent[i] 是节点 i 的父节点。树的根节点是编号为 0 的节点。 树节点的第 k 个祖先节点是从该节点到根节点路径上的第 k 个节点。 实现…