SimpleITK是专门处理医学影像的软件,是ITK的简化接口,使用起来非常便捷,SimpleITK支持8种编程语言,包括c++、Python、R、Java、c#、Lua、Ruby和TCL。本文主要以python为例进行讲述。
在SimpleITK中,图像的概念与我们在计算机视觉中常用的RGB图像差异很大,后者只是一个多维矩阵,是一个数学上的概念,而在SimpleITK中图像是一个物理实体,图像中的每一个像素都是物理空间中的一个点,不光有着像素值,还有坐标、间距、方向等概念。
SimpleITK的项目地址github:https://github.com/SimpleITK/SimpleITK
说明文档:SimpleITK Sphinx Documentation — SimpleITK 2.0rc2 documentation
参考SimpleITK文档:Fundamental Concepts — SimpleITK 2.0rc2 documentation
SimpleITK假设世界坐标系下的长度单位为mm。
世界坐标系:用于医学成像仪器的坐标系。坐标用元素是连续值的向量表示。
图像坐标系:用于表示像素点的坐标系。坐标用元素是离散值的向量表示。
同自然图像格式中的通道概念。医学通道中也有通道的概念。比如使用的CT图像是单通道图像,可视化后可用灰度值表示。
但是也有一些成像仪器生成的图像具有多个通道,例如RGB色彩格式,能够展现更丰富的特征,相应的占用空间也会3。
(比如说,LUNA16中单个CT图像文件的大小约200MB。其纵向切片分辨率为512512,切片数为120 ~ 700,通道数为1。可以看到低分辨率下单张图像的数据量就已经很夸张了。)
SimpleITK 支持两种类型的空间转换,一种具有全局(无界)空间域,可以使用各种全局2D 和3D 转换(平移、旋转、刚性、相似性、仿射… …)。另一种具有有界空间域。SimpleITK 中的点通过转换使用 TransformPoint 方法进行映射。其中包括 B 样条变形变换,通常称为自由变形,以及位移场变换。
重采样是对图像进行采样的动作,而图像本身就是对原始连续信号的采样。可以通过重采样将改变体素的大小。
虽然 SimpleITK 提供了大量的插值方法,但是最常用的两种方法是** sitkLine 和 sitkNearestNeighbor.。前者用于大多数插补任务,是精度和计算效率之间的折衷。后者用于插值表示分割的标记图像**。这是唯一的插值方法,不会引入新的标签到结果。
SimpleITK包含了一些图像配准的功能.
**医学图像配准(Medical Image Registration)是指将不同的医学图像(根据定义,计算机内从像素到meta信息有任意不同之处的两张医学图像都是不同的。在现实中的定义则是两次不同成像过程产生的图像。)进行对齐和匹配。**医学图像配准的目标是将两个或多个医学图像进行对齐,使它们共享相同的解剖结构、几何形状和空间定位信息,从而实现它们之间的定量比较、分析和整合。常用的医学图像配准方法包括基于特征的方法、基于强度的方法、基于形变场的方法等。
简单地说,来自于同一个病人的两张图像可能各自含有一些对方没有的重要信息,图像配准技术的目标是能够同时利用这两张图象的信息。如果两张图象来自于不同原理的成像仪器(例如CT和MRI),这种图像配准技术被称作跨模态图像配准(Cross-Modality Image Registration)/多模态图像配准(Multimodal Image Registration)。
跨模态指的是A->B或B->A,多模态则是指任何涉及到多个模态的问题环境。可能是因为原始图像采样过程中丢失了精度,或者数学上两张不同的医学图像本身就不可能完全重叠?在图像配准任务中,实现的过程用估计(estimate)来表述。
图像配准最基本的过程包含了坐标轴位移、旋转、拉伸等操作。
用SimpleITK完成图像配准工作需要Transformation、Similarity metric、Optimizer、Interpolator这四个工具共同实现。
配准更具体细节参考:Registration Overview — SimpleITK 2.0rc2 documentation
在这里,使用python对SimpleITK库中常用的函数进行举例说明:
在使用SimpleITK库之前,需要将SimpleITK库导入进来,如下:
import SimpleITK as sitk
sitk中有以下四种常见的属性,分别可以使用get的方式获取,代码如下所示:
print(image.GetSize()) #获取图像的大小,size为图像的每一个维度的长度,即每个维度像素点的个数
print(image.GetOrigin()) #获取图像的原点坐标
print(image.GetSpacing()) #获取每个维度上像素或体素之间的间距,单位mm,其中对于二维图像是像素,三维图像是体素
print(image.GetDirection()) #获取图像的方向,即图像坐标系相对世界坐标系的角度,角度采用的是方向余弦矩阵
举例说明:以二维图像为例,左下图是世界坐标系,右下图是图像坐标系。属性值如下所示
print(image.GetSize())
(7, 6) #在SimpleITK中,读取的顺序为[X, Y, Z], 即先宽度,后高度,再深度,对应的三维医学图像中为:先矢状位,后冠状位,再横断位。
print(image.GetOrigin())
(60.0, 70.0) #读取顺序同GetSize()方法
print(image.GetSpacing())
(20.0, 30.0) #读取顺序同GetSize()方法, 每个维度可以具有不同的间距,且每个维度不一定是正交的。
print(image.GetDirection())
(1.0, 0.0, 0.0, 1.0)
SimpleITK可以读取如.mhd , .nii, .nrrd等图像数据格式。
#以读取和保存mhd图像为例
imagepath = "xxx.mhd" #图像路径
writepath = "xxx.mhd" #保存图像路径
image = sitk.ReadImage(imagepath)
sitk.Write(image, writepath)
image1 = sitk.ReadImage(imagepath, sitk.sitkFloat32) #你可以指定读取的数据类型
Note1:图像访问是以x, y, z顺序GetPixel(x,y,z) 或 image[x,y,z], 从0开始索引。
Note2:默认的mask图像类型和默认值为sitkUInt8或标量图像uint8_t, 默认值为0和1,其中1代表的是mask。
使用SimpleITK读取和保存Nii.gz文件:
## using simpleITK to load and save data.
import SimpleITK as sitk
itk_img = sitk.ReadImage('./nifti.nii.gz')
img = sitk.GetArrayFromImage(itk_img)
print("img shape:",img.shape)
## save
out = sitk.GetImageFromArray(img)
out.SetSpacing(itk_img.GetSpacing())
out.SetOrigin(itk_img.GetOrigin())
out.SetDirection(itk_img.GetDirection())
sitk.WriteImage(out,'simpleitk_save.nii.gz')
像素类型表示为枚举类型,下面是部分枚举类型的表。
还有sitkUnknown类型,用于未定义或错误的像素ID,值为-1。
Note:64位整数类型并非在所有的发行版上都可用,如果不可用,则值为sitkUnknown,将图像保存为nii文件,用ITKsnap读取时就会出现的错误如下:
一般我们会用SimpleITK读取图像,再转换为numpy矩阵格式,这样方便数据的处理。
Note1:在SimpleITK中,各术语对应如下:
Width: 宽度,X轴,矢状面(Sagittal)
Height: 高度,Y轴,冠状面(Coronal)
Depth: 深度, Z轴,横断面(Axial)
Note2: SimpleITK图像顺序是x,y,z三个方向的大小(在第一节中也讲过),而numpy矩阵的顺序是z,y,x三个方向的大小, 要注意索引位置。
举个例子:假设实验用的图片大小为32025080,即矢状面(x轴方向)切片数为320,冠状面(y轴方向)切片数为250,横断面(z轴方向)片数为80。
**GetArrayFromImage():**返回图像数据的副本。然后可以自由地修改数据,因为它对原始SimpleITK图像没有影响。
# sitk image to numpy
shape_img = image.GetSize() #输出形状为:(Width, Height, Depth),即原始SimpleITK数据的存储形式
print("image size:", shape_img)
#image size:(320, 250, 80)
datanp_array = sitk.GetArrayFromImage(image) # 将SimpleITK对象转换为ndarray,X轴与Z轴发生了对调,输出形状为:(Depth, Height, Width)
print("np_array size:", np_array.shape)
# np_array size:(80, 250, 320)
**GetImageFromArray():**返回一个SimpleITK图像,原点设置为0,所有维度的间距设置为1,方向余弦矩阵设置为identity,强度数据从numpy数组中复制。在大多数情况下需要设置适当的元数据值。
# numpy data to sitk
imagesitk_image = sitk.GetImageFromArray(np_array)
sitk_image.SetOrigin(origin)
sitk_image.SetSpacing(spacing)
sitk_image.SetDirection(direction)
Note: 从array转成image需要设置原点,维度间距以及方向。
两种方式:一是使用GetPixel和SetPixel函数,二是使用python的切片操作符。例子如下:
#法一:使用GetPixel和SetPixel函数
image_3D = sitk.Image(256, 128, 64, sitk.sitkInt16)
print(image_3D.GetPixel(0, 0, 0)) #注意:是从0开始索引
image_3D.SetPixel(0, 0, 0, 1)
print(image_3D.GetPixel(0, 0, 0))
#法二:使用python的切片操作符
print(image_3D[0,0,1])
image_3D[0,0,1] = 2
print(image_3D[0,0,1])
重采样目的是将医疗图像中不同的体素归一化到相同的大小,可分为两种方案,
这两种方案具体操作分为三个步骤:
resampler = sitk.ResampleImageFilter() #初始化一个图像重采样滤波器resampler
resampler.SetReferenceImage(itkimage) #设置需要重新采样的目标图像
resampler.SetSize(newSize.tolist()) #设置目标尺寸
resampler.SetTransform(sitk.Transform(3, sitk.sitkIdentity))
resampler.SetInterpolator(sitk.sitkNearestNeighbor) #设置插值方式,体数据采用线性插值,mask采用最近邻插值
itkimgResampled = resampler.Execute(itkimage) #得到重新采样后的图像
下面以指定Spacing大小,对原始数据进行重采样,例子如下:
def resampleImage(image, targetSpacing, resamplemethod=sitk.sitkNearestNeighbor):
"""
将体数据重采样的指定的spacing大小
paras:
image:sitk读取的image信息,这里是体数据
targetSpacing:指定的spacing,例如[1,1,1]
resamplemethod:插值类型
return:重采样后的数据
"""
targetsize = [0, 0, 0]
#读取原始数据的size和spacing信息
ori_size = image.GetSize()
ori_spacing = image.GetSpacing()
transform = sitk.Transform()
transform.SetIdentity()
#计算改变spacing后的size,用物理尺寸/体素的大小
targetsize[0] = round(ori_size[0] * ori_spacing[0] / targetsize[0])
targetsize[1] = round(ori_size[1] * ori_spacing[1] / targetsize[1])
targetsize[2] = round(ori_size[2] * ori_spacing[2] / targetsize[2])
#设定重采样的一些参数
resampler = sitk.ResampleImageFilter()
resampler.SetTransform(transform)
resampler.SetSize(targetsize)
resampler.SetOutputOrigin(image.GetOrigin())
resampler.SetOutputSpacing(targetSpacing)
resampler.SetOutputDirection(image.GetDirection())
resampler.SetInterpolator(resamplemethod)
if resamplemethod=sitk.sitkNearestNeighbor:
#mask用最近邻插值,保存为uint8
resampler.SetOutputPixelType(sitk.sitkUInt8)
else:
#体数据用线性插值,保存为float32
resampler.SetOutputPixelType(sitk.sitkFloat32)
newImage = resampler.Execute(image)
return newImage
图像二值化分割: 是分割方法中最基础的,通过定义lowerThreshold和upperThreshold两个像素临界点,只要像素值在两者之间,则该像素值改为insideValue,否则改为outsideValue。这种方法只是简单的基于灰度范围标记图像像素,不考虑几何或连通性。
#sitk API
sitk.BinaryThreshold(
sitk_src,
lowerThreshold=lowervalue,
upperThreshold=uppervalue,
insideValue=255,
outsideValue=0)
#例子:将0-500之间的像素置为1,其他置为0
image = sitk.ReadImage("./***.nii.gz")
outimage = sitk.BinaryThreshold(
image,
lowerThreshold=0,
upperThreshold=500,
insideValue=1,
outsideValue=0
)
图像区域生长分割: 需要确定种子点、生长准则和终止条件。具体来说,对每一个需要分割的区域找一个种子像素作为生长的起点,根据生长准则将种子像素邻域中与种子像素具有相同或相似的像素合并到种子像素所在的区域,直到没有满足条件的像素可以被包进来就终止。
在SimpleITK中,首先会计算当前区域中包含的所有像素点灰度值的标准差和期望,通过定义multiplier因子(乘以标准差)来计算以期望为中心的灰度值范围,如果initialNeighborhoodRadius邻域半径内的灰度值位于这个范围就被包进来,灰度值改为replaceValue,当遍历了所有邻域像素,即认为完成了一次迭代,下一次迭代时,像素点的灰度值期望和标准差是以新的像素区域为基础进行计算的,一共要迭代numberOfIterations次。
sitk.ConfidenceConnected(
image,
seedList=[seed],
numberOfIterations=1,
multiplier=2.5,
initialNeighborhoodRadius=1,
replaceValue=1)
图像形态学操作一般常用的是开,闭,膨胀,腐蚀操作:
# 图像的形态学操作:开、闭、膨胀、腐蚀
sitk.BinaryMorphologicalOpening(sitk.ReadImage() != 0, kernelsize)
sitk.BinaryMorphologicalClosing(sitk.ReadImage() != 0, kernelsize)
sitk.BinaryDilate(sitk.ReadImage() != 0, kernelsize)
sitk.BinaryErode(sitk.ReadImage() != 0, kernelsize)
连通域分析一般是针对二值图像,将具有相同像素值且相邻的像素找出来并标记,其中二维图像连通域一般包括4连通和8连通,三维图像连通域包括6连通、18连通和26连通。
# ConnectedComponentImageFilter类,用于连通域划分
ccfilter = sitk.ConnectedComponentImageFilter()
ccfilter.SetFullyConnected(True)
output = ccfilter.Execute(image) #image为要进行连通域划分的二值图像,output为输出被标记的图像
count = ccfilter.GetObjectCount()
# LabelShapeStatisticsImageFilter类,用于获取各label的形状属性
lssfilter = sitk.LabelShapeStatisticsImageFilter()
lssfilter.Execute(output)
#根据连通域的体积大小保留最大连通域
area_max_label = 0 #记录最大的连通域的label
area_max = 0 #记录连通域中最大的体积
for i in range(1, count + 1):
area = lssfilter.GetNumberOfPixels(i) # 根据label获取连通域体积
if area > area_max:
area_max_label = i
area_max = area
output_array = sitk.GetArrayFromImage(output)
res = np.zeros_like(output_array)
res[output_array == area_max_label] = 1 #获取最大连通域
SimpleITK Sphinx Documentation — SimpleITK 2.0rc2 documentation
https://github.com/SimpleITK/SimpleITK
SimpleITK图像基础(三)——SimpleITK学习笔记_Jiaxxxxxx的博客-CSDN博客
SimpleITK的使用