赞
踩
笔者正在 Datawhale 举办的 AI 夏令营中参与 CV 方向的学习,研究课题为发布在讯飞 AI 开发者大赛上的脑PET图像分析和疾病预测挑战赛,需要对 nii 格式的医学影像文件进行学习,并进行疾病预测
第一轮学习中,Datawhale 团队给出了基于logistic回归进行预测的baseline,但其中并没有对 nii 文件进行详细介绍;且为了适配 logistic 回归模型,并缩小数据规模,其数据处理过程(个人认为)产生了不可忽视的信息丢失,直接导致预测的准确度仅略高于0.5——这跟瞎猜也没啥区别了吧……

鉴于此,笔者在探索中,总结出一篇对nii文件进行数据处理,并采用学习能力更强的简单CNN模型进行训练的教程
nii 文件是医学图像处理中经常使用的一种 NIFTI 格式图像,关于 nii 文件具体的底层原理,这里不做详细介绍,有兴趣的同学可以参见:Brainder.org的讲解。我们今天主要聚焦于,如何合理地处理 nii 文件中的数据,以便模型训练
数据集中的nii文件对一个人脑进行了三维建模。python 的 nibabel 库专门用于处理 nii 文件,我们可以通过 load() 函数读取 nii 文件,获取其信息:
- #1.py
- import nibabel as nib
- import numpy as np
- im=nib.load('脑PET图像分析和疾病预测挑战赛数据集/Train/NC/1.nii')
- im_data=np.array(im.get_fdata()) #获取文件中的所有数值,并转换为numpy数组便于后续处理
- print(im_data.shape) #查看数据的形状
-
- #返回:(128, 128, 63, 1)
可以看到,数据集中的 nii 文件包含四维数据:其中前三维确定某一个像素点的空间位置,其中左右和前后方向各分128层,上下方向分63层;第四维记录了该点的状态值(类似图片的灰度)
在整份 nii 数据中,所有的数值均为正整数,随便挑出一个值看一看:
- #1.py
- print(im_data[64,64,55,0])
-
- #返回:10550.0
如此多这么大的数值,如果直接将其投入模型进行训练,效果会很差,必须对数据进行预处理,减小其规模。前面提到的 baseline 中的示例代码给出的方法是人工进行特征提取:
- #1.py
- feat = [(im_data != 0).sum(), # 非零像素的数量
- (im_data == 0).sum(), # 零像素的数量
- im_data.mean(), # 平均值
- im_data.std(), # 标准差
- len(np.where(im_data.mean(0))[0]), # 在列方向上平均值不为零的数量
- len(np.where(im_data.mean(1))[0]), # 在行方向上平均值不为零的数量
- im_data.mean(0).max(), # 列方向上的最大平均值
- im_data.mean(1).max() # 行方向上的最大平均值
- ]
这种人工特征提取的方法,优势在于极大地减小了数据规模:每份文件从128*128*63=1032192个数值,减小到8个数值,大幅提升了训练速度。但或许其劣势也在于此:如此大规模的删减,势必造成严重的信息丢失
通常情况下,我们不对数据进行大量的丢弃与删除,而是对所有数据进行“归一化”,使得它们均值为0,标准差为1。具体操作为:求所有数据的平均值与标准差;随后对每个数据,减去平均值,再除以标准差:
- #1.py
- def norm(image):
- m=image.mean()
- s=image.std()
- return (image-m)/s
- im_norm=norm(im_data)
- #看看刚才的10550变成了多少
- print(im_norm[64,64,54,0])
-
- #返回:3.955
理论上来说,似此般对所有的 nii 文件做读取和归一化操作,就可以喂给模型做训练了。但是在尝试中我发现,数据集中的文件并非全部是 (128, 128, 63, 1) 的形状!少部分数据的前三个值出现了变化,比如256, 256, 47!这些数据可能受到了损坏,或者它本来就那样……通过一段代码,我们可以看到数据集中的 nii 都有哪些尺寸:
- #2.py
- import nibabel as nib
- import numpy as np
- import glob
-
- path=glob.glob('./脑PET图像分析和疾病预测挑战赛数据集/Train/*/*')
- path2=glob.glob('./脑PET图像分析和疾病预测挑战赛数据集/Test/*')
- sizeSet=set()
-
- for i in path:
- im=nib.load(i)
- im_data=np.array(im.get_fdata())
- sizeSet.add(im_data.shape)
-
- for i in path2:
- im=nib.load(i)
- im_data=np.array(im.get_fdata())
- sizeSet.add(im_data.shape)
-
- print(sizeSet)
-
- #返回:{
- # (256, 256, 207, 1), (128, 128, 768, 1), (128, 128, 47, 1),
- # (256, 256, 47, 1), (168, 168, 82, 1), (168, 168, 81, 1),
- # (400, 400, 109, 1), (128, 128, 89, 1), (128, 128, 540, 1),
- # (128, 128, 63, 1), (256, 256, 81, 1), (128, 128, 88, 1)
- # }

我初步的解决方法简单粗暴:在处理数据时,只要不是 (128, 128, 63, 1) 尺寸的数据,就直接丢弃它们!
- #1.py
- def process(path):
- im=nib.load(path)
- im_data=norm(np.array(im.get_fdata()))
- if im_data.shape==(128, 128, 63, 1): #判断数据形状是否异常
- if 'NC' in path:
- return True,im_data.reshape(-1),0 #表示正常(NC)
- else:
- return True,im_data.reshape(-1),1 #表示异常(MCI)
- else:
- return False,None,None #对于异常数据,直接不返回它们
不幸的是,测试集中的数据也有类似的情况(100个数据里有31个尺寸不正确)。对于这些数据,我只能暂时给它们随机分配“NC”和“MCI”标签来完成预测。这样造成的损失完全不亚于我所吐槽的示例代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。