目录
前言
nii.gz格式是保存医学图像非常重要一种格式,下面来介绍一下如何使用
SimpleITK
这个包来处理
nii
文件。我们首先会介绍最简单的读取、保存、以及如何转为numpy数组;然后再来介绍一些高级操作,什么是
Direction
、
Origin
、
Spacing
、
重采样
。
nii格式
首先nii格式就是后缀名为
.nii
或
.nii.gz
的文件,该格式又叫
NIfTI-1
。
MRI
图像或者
CT
图像通常会以这种格式保存。
至于这种格式的作用,简单来理解就是将
索引坐标
映射到
体素坐标
。我们知道计算机中的数据都是离散的,比如一个数组可以按照索引取对应的值,但是在显示生活中,距离都是连续的,那如何给计算机中离散的点(索引坐标)赋予一个连续的坐标(即体素坐标)呢?
在nii格式中,为了将
索引坐标(数组下标)
映射到
体素坐标(空间坐标)
,除了保存图像的数据外,即那一个个离散的像素,还保存了一些额外信息,比如每个
像素间的距离
,
原点坐标
,
方向
等等,这样根据像素间的距离就可以计算某一像素在空间中真正的坐标了。
更为详细的解释在这里可以看到。
那我们做深度学习需要空间坐标干嘛呢,确实,在训练过程中我们并不关心
空间坐标
,只需要把nii文件中保存的像素值拿出来转化为
numpy
或
tensor
输入网络就可以了,空间距离对我们也没什么用。但还是想写给大家,因为当我们做对比实验时,你会发现将不同网络输出的预测结果保存为nii文件,然后用3DSlicer打开后,二者的预测结果可能完全不一样,这是因为
起始坐标
和
原始label
是对不上的,所以需要设置成一样的,这个问题我们后面再说。
读取nii成numpy格式
这个非常容易,只需要使用SimpleITK先读取图像,然后从图像中获取数组就可以了。
最后image就是一个numpy数组,但是需要注意的是nii文件默认保存数据的顺序是
[x, y, z]
,但是numpy数组保存数据的顺序是
[z, y, x]
,刚好是反过来的.。因为在训练时一般是
[x, y, z]
,所以我们在dataset中需要将图像的坐标轴转换一下,即做一个
transpose(2, 1, 0)
操作。
import SimpleITK as sitk
image_path =''
image = sitk.ReadImage(image_path)
image = sitk.GetArrayFromImage(image)
将numpy格式保存成nii
代码也是非常简单,仍然需要注意的是,可能从网络中输出预测结果的坐标顺序为
[x, y, z]
,但是保存时numpy数组轴的顺序一定要是
[z, y, x ]
,做一下
transpose(2, 1, 0)
或
permute(2, 1, 0)
操作,这样才是正确的。当然,这一步操作取决于你的
dataset类
是否交换了维度,如果交换了维度,网络输出后自然需要交换回来。不知道我讲清楚了没有
# image是一个三维numpy数组,image_path是要保存的路径
sitk.WriteImage(image, image_path)
什么是origin、Direction、Spacing,以及如何设置它们
origin就是原点的坐标,direction我还不太清楚,大概是轴的方向吧,spacing就是每个像素间所代表的真实世界的距离,下面的这个图比较容易理解
对这些有一个大概的认识后,实际使用中,我们只需要
以原图为基准
,将不同网络输出预测结果的
元数据
属性设置成和原图一样就可以了,这样用3D Slicer打开时位置就能对应起来~,代码如下:
需要注意的是保存前的numpy轴的顺序必须得是
[z, y, x]
,否则需要先交换再设置
元数据属性
,这样才是正确的!!!
# 读取原图像
origin_path =''
origin = sitk.ReadImage(origin_path)# 读取预测图像
pred_path =''
pred = sitk.ReadImage(pred_path)# 将预测结果的元数据属性设置成和原图像一样
pred.SetDirection(origin.GetDirection())
pred.SetOrigin(origin.GetOrigin())
pred.SetSpacing(origin.GetSpacing())# 保存处理后的
sitk.WriteImage(pred, pred_path)
示例
把自己的代码保存在这里吧,我做了四个对比试验,nnunet,nnformer,unetr,our,根据nii的ID来依次处理,将他们的元数据都设成和原始image的元数据一样,应该比较好理解吧~
import SimpleITK as sitk
defsetMetaMessage(target, origin):
target.SetDirection(origin.GetDirection())
target.SetOrigin(origin.GetOrigin())
target.SetSpacing(origin.GetSpacing())return target
defprocess(id):
image_path ='C:/Users/hejianfei/Desktop/vessel result/result'
image = sitk.ReadImage(os.path.join(image_path,str(id)+'_image.nii.gz'))print('image size:', image.GetSize())
sitk.WriteImage(image, os.path.join('C:/Users/hejianfei/Desktop/vessel result/process result',str(id)+'_image.nii.gz'))
label = sitk.ReadImage(os.path.join(image_path,str(id)+'_label.nii.gz'))print('label size:', label.GetSize())
label = setMetaMessage(label, image)
sitk.WriteImage(label, os.path.join('C:/Users/hejianfei/Desktop/vessel result/process result',str(id)+'_label.nii.gz'))
nnunet = sitk.ReadImage(os.path.join(image_path,str(id)+'_nnunet.nii.gz'))print('nnunet size:', nnunet.GetSize())
nnunet = setMetaMessage(nnunet, image)
sitk.WriteImage(nnunet, os.path.join('C:/Users/hejianfei/Desktop/vessel result/process result',str(id)+'_nnunet.nii.gz'))
nnformer = sitk.ReadImage(os.path.join(image_path,str(id)+'_nnformer.nii.gz'))print('nnformer size:', nnformer.GetSize())
nnformer = setMetaMessage(nnformer, image)
sitk.WriteImage(nnformer, os.path.join('C:/Users/hejianfei/Desktop/vessel result/process result',str(id)+'_nnformer.nii.gz'))
unetr = sitk.ReadImage(os.path.join(image_path,str(id)+'_unetr.nii.gz'))
unetr = sitk.GetArrayFromImage(unetr)
unetr = unetr.transpose(2,1,0)# 换轴
unetr = sitk.GetImageFromArray(unetr)print('unetr size:', unetr.GetSize())
unetr = setMetaMessage(unetr, image)
sitk.WriteImage(unetr, os.path.join('C:/Users/hejianfei/Desktop/vessel result/process result',str(id)+'_unetr.nii.gz'))
our = sitk.ReadImage(os.path.join(image_path,str(id)+'_our.nii.gz'))
our = sitk.GetArrayFromImage(our)
our = our.transpose(2,1,0)# 换轴
our = sitk.GetImageFromArray(our)print('our size:', our.GetSize())
our = setMetaMessage(our, image)
sitk.WriteImage(our, os.path.join('C:/Users/hejianfei/Desktop/vessel result/process result',str(id)+'_our.nii.gz'))if __name__ =='__main__':
process(447)
重采样
重采样可以这样理解,现在我们有一个
2m*2m*2m
的正方体,它的像素分辨率为
100*100*100
,即每个方向都存了
100
个离散的像素点,现在我们保持正方体的尺寸不变,还是
2m*2m*2m
,但是像素分辨率插值变为
150*150*150
,这样就缩小了每个像素间的
space
。所以这个函数也是需要我们传入一个目标
space
。大家先简单理解一下,
相当于体积不变,密度增大了
,类比二维的
resize
操作,等用到的时候去查一下文档~
这里说一下我对重采样的简单理解,重采样改变的是每个像素间所代表的物理距离,在重采样代码中,我们是重采样前像素个数为
x
,每个像素间的距离(即Spacing)为
y
;重采样后的像素个数为
z
,每个像素间的距离为
m
;我们为了保持物理体积不变(即
x*y = z*m
),所以当改变
m
(即Spacing)后,相应的像素个数
z
也会通过插值改变。
在神经网络中,我们除了通过重采样改变像素个数外,还可以通过
Resize
操作来改变像素点。只不过为了最终可视化和原
label
大小一样,最好先把两者的
Spacing
调整为一样的。
否则会出现如下情况,可以看到长宽比发生了变形:
原图
预测label:
重采样代码
defresampleVolume(outspacing, vol,type):"""
将体数据重采样的指定的spacing大小\n
paras:
outpacing:指定的spacing,例如[1,1,1]
vol:sitk读取的image信息,这里是体数据
type:指定插值方法,一般对image采取线性插值,对label采用最近邻插值
return:重采样后的数据
"""
outsize =[0,0,0]# 读取文件的size和spacing信息
inputsize = vol.GetSize()
inputspacing = vol.GetSpacing()
transform = sitk.Transform()
transform.SetIdentity()# 计算改变spacing后的size,用物理尺寸/体素的大小
outsize[0]=round(inputsize[0]* inputspacing[0]/ outspacing[0])
outsize[1]=round(inputsize[1]* inputspacing[1]/ outspacing[1])
outsize[2]=round(inputsize[2]* inputspacing[2]/ outspacing[2])# 设定重采样的一些参数
resampler = sitk.ResampleImageFilter()
resampler.SetTransform(transform)# 图像使用线性插值,标签使用最近邻插值iftype=='linear':
resampler.SetInterpolator(sitk.sitkLinear)
resampler.SetOutputPixelType(sitk.sitkFloat32)# image用float32存else:
resampler.SetInterpolator(sitk.sitkNearestNeighbor)
resampler.SetOutputPixelType(sitk.sitkUInt8)# 标签用int8存储
resampler.SetOutputOrigin(vol.GetOrigin())
resampler.SetOutputSpacing(outspacing)
resampler.SetOutputDirection(vol.GetDirection())
resampler.SetSize(outsize)
newvol = resampler.Execute(vol)return newvol
参考链接
https://blog.csdn.net/qq_39482438/article/details/106711272
重采样代码
版权归原作者 遇到坎就得迈过去 所有, 如有侵权,请联系我们删除。