目录
nii.gz格式是保存医学图像非常重要一种格式,下面来介绍一下如何使用SimpleITK这个包来处理nii文件。我们首先会介绍最简单的读取、保存、以及如何转为numpy数组;然后再来介绍一些高级操作,什么是Direction、Origin、Spacing、重采样。
首先nii格式就是后缀名为.nii或.nii.gz的文件,该格式又叫NIfTI-1。MRI图像或者CT图像通常会以这种格式保存。
至于这种格式的作用,简单来理解就是将索引坐标映射到体素坐标。我们知道计算机中的数据都是离散的,比如一个数组可以按照索引取对应的值,但是在显示生活中,距离都是连续的,那如何给计算机中离散的点(索引坐标)赋予一个连续的坐标(即体素坐标)呢?
在nii格式中,为了将索引坐标(数组下标)映射到体素坐标(空间坐标),除了保存图像的数据外,即那一个个离散的像素,还保存了一些额外信息,比如每个像素间的距离,原点坐标,方向等等,这样根据像素间的距离就可以计算某一像素在空间中真正的坐标了。
更为详细的解释在这里可以看到。
那我们做深度学习需要空间坐标干嘛呢,确实,在训练过程中我们并不关心空间坐标,只需要把nii文件中保存的像素值拿出来转化为numpy或tensor输入网络就可以了,空间距离对我们也没什么用。但还是想写给大家,因为当我们做对比实验时,你会发现将不同网络输出的预测结果保存为nii文件,然后用3DSlicer打开后,二者的预测结果可能完全不一样,这是因为起始坐标和原始label是对不上的,所以需要设置成一样的,这个问题我们后面再说。
这个非常容易,只需要使用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)
代码也是非常简单,仍然需要注意的是,可能从网络中输出预测结果的坐标顺序为[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就是每个像素间所代表的真实世界的距离,下面的这个图比较容易理解

对这些有一个大概的认识后,实际使用中,我们只需要以原图为基准,将不同网络输出预测结果的元数据属性设置成和原图一样就可以了,这样用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
def setMetaMessage(target, origin):
target.SetDirection(origin.GetDirection())
target.SetOrigin(origin.GetOrigin())
target.SetSpacing(origin.GetSpacing())
return target
def process(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:

def resampleVolume(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)
# 图像使用线性插值,标签使用最近邻插值
if type == '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
重采样代码
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时
我的目标是转换表单输入,例如“100兆字节”或“1GB”,并将其转换为我可以存储在数据库中的文件大小(以千字节为单位)。目前,我有这个:defquota_convert@regex=/([0-9]+)(.*)s/@sizes=%w{kilobytemegabytegigabyte}m=self.quota.match(@regex)if@sizes.include?m[2]eval("self.quota=#{m[1]}.#{m[2]}")endend这有效,但前提是输入是倍数(“gigabytes”,而不是“gigabyte”)并且由于使用了eval看起来疯狂不安全。所以,功能正常,
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题