1 nii格式介绍
1.0 Analyze 格式
在解释NII(NIFTI,neuroimaging information technology initiative)格式之前,我们需要先知道一下Analyze格式。
每一个Analyze格式的数据包含了两个文件:带有二进制图像信息的.img,包含图像元数据的头文件.hdr
但是,Analyze的头文件不能很真实地反应元数据(比如没有方向信息,eg在左边还是在右边),于是NIFTI格式文件应运而生
1.1 组成部分
nii格式(nifti格式的扩展)是为多维神经影像学发明的。一个nii格式主要包含三部分:hdr, ext, img
- hdr
- header,数据头
- 这部分数据长度是固定的,当然不同版本可能规定的长度不同,但是同一版本的多个nii文件是相同的。
- header里包含的信息有:
- --维度,至少三个维度,x,y,z,单位是毫米。还有可能有第四个维度,就是时间。这部分储存的主要是四个数字。
- --voxel size(体素大小):毫米单位的x,y,z大小。
- --数据类型,一般是int16,这个精度不够,最好使用double类型。
- Form和转换矩阵,每一个Form都对应一个转换矩阵。
- 转换矩阵可以轻松分清图像的左右,作用是将体素索引(i,j,k)转换为空间位置(x,y,z)。
- 具体使用方法是转换矩阵乘以一个包含(i,j,k)的矩阵,就可以得到一个包含(x,y,z)的矩阵。
- 体素可以类比成二维图像中的像素。体素就是固定分辨率的三维栅格地图
- ext
- extension
- 是自己可以随意定义数据的部分,可以自己用。
- img
- image
- 储存3D或者4D的图像数据
- .hdr 和 .img可以用单文件.nii存储
1.2 坐标
医学影像数据有两种格式,dicom和nii格式,他们定义了不同的方向
2 打开图像
2.1 ITK-SNAP软件
注意:使用这个软件的时候,路径中不能有中文
三视图
3 nibabel (python库)
3.1 基本操作
3.1.1 导入nii
import nibabel as nib
filename = 'CTC-1835078273_seg.nii'
img = nib.load(filename)
img.shape
#(512, 512, 539)
一个nibable 图像有三部分组合而成:
- image数据,三维/四维图像
- 一个仿射数组 affine,image数据在参考空间中的位置
- image元数据 (header)
3.1.2 获得图像的ndarray类型数据
data = img.get_fdata()
data.shape,type(data)
#((512, 512, 539), numpy.memmap)
3.1.3 创建新image
至少需要一些image 数据 和一个image 坐标转换矩阵(affine)
import numpy as np
data = np.random.randint((32, 32, 15, 100), dtype=np.int16)
img = nib.Nifti1Image(data, np.eye(4))
nib.save(img, 'exa.nii')
这里numpy.memmap是内存映像文件。它是一种将磁盘上的非常大的二进制数据文件当做内存中的数组进行处理的方式。它允许将大文件分成小段进行读写,而不是一次性将整个数组读入内存。
memmap也拥有跟普通数组一样的方法,因此,基本上只要是能用于ndarray的算法就也能用于memmap。
3.2 坐标系和affine
在官方文档中,提供了一个人大脑的MRI图像。
import nibabel as nib
epi_img = nib.load('someones_epi.nii.gz')
epi_img_data = epi_img.get_fdata()
epi_img_data.shape
#(53, 61, 33)
[EPI数据]我们看看数组的第一维、第二维和第三维上的切片。
import matplotlib.pyplot as plt def show_slices(slices): """ Function to display row of image slices """ fig, axes = plt.subplots(1, len(slices)) for i, slice in enumerate(slices): axes[i].imshow(slice.T, cmap="gray", origin="lower") slice_0 = epi_img_data[0, :, :] slice_1 = epi_img_data[:, 0, :] slice_2 = epi_img_data[:, :, 0] show_slices([slice_0, slice_1, slice_2]) plt.suptitle("Center slices for EPI image")
image维度是(53, 61, 33),可以想成是53张61*33的图,可以想成是61张53*33的图,还可以想成是33张53*61的图
[结构数据(解剖)]
anat_img = nib.load('someones_anatomy.nii.gz') anat_img_data = anat_img.get_fdata() print(anat_img_data.shape) #(57, 67, 56) show_slices([anat_img_data[0, :, :], anat_img_data[:, 0, :], anat_img_data[:, :, 0]]) plt.suptitle("Center slices for anatomical image")
通常情况下,我们有不同的解剖扫描视野,因此解剖图像具有不同的形状、大小和方向。
3.2.1 体素坐标
体素是具有体积的像素。
在上面的代码中,来自 EPI 数据的 slice_0 是来自 3D 图像的 2D 切片。 切片灰度图像中的每个像素也代表一个体素,因为这个 2D 图像代表了 3D 图像中具有一定厚度的切片。
因此,3D 阵列也是体素阵列。 对于任何数组,我们都可以通过索引来选择特定的值。 例如,我们可以像这样获取 EPI 数据数组中中间体素的值:
epi_img_data[0,0,0]
#10.755071640014648
[0,0,0]就是体素坐标
3.2.2 体素坐标和空间点
体素坐标几乎不告诉我们数据在扫描仪中的位置来自何处。 例如,假设我们有体素坐标 (26, 30, 16)。 如果没有更多信息,我们不知道这个体素位置是在大脑的左侧还是右侧,还是来自扫描仪的左侧或右侧。 这是因为扫描仪允许我们在几乎任何任意位置和方向收集体素数据。
比如我们对EPI图,将扫描仪稍微旋转一下,得到红色框内的部分
我们进行了解剖和 EPI 扫描,稍后我们肯定希望能够将 somes_epi.nii.gz 中的数据与someones_anatomy.nii.gz 相关联。目前我们不能轻易做到这一点,因为我们收集到的解剖图像与 EPI 图像具有不同的视野和方向。所以体素坐标暂时不能一一对应。
我们通过跟踪体素坐标与某些参考空间的关系来解决这个问题。具体而言,仿射阵列(affine)存储图像数据中的体素坐标与参考空间中的坐标之间的关系
“参考空间”中的“空间”是什么意思? 该空间由一组有序的轴定义。 对于我们的 3D 空间世界,它是一组 3 个独立的轴。 我们可以通过选择这些轴来决定我们想要使用的空间。 我们需要选择轴的原点、方向和单位。(xyz-坐标轴,单位毫米)
3.2.3 affine matrix
我们希望将体素坐标(i,j,k)转换到参考空间坐标(x,y,z),即(x,y,z)=f(i,j,k)
而基本的矩阵乘法,可以满足
坐标轴伸缩 | |||
旋转 | 围绕第一个坐标轴旋转 | ||
围绕第二个坐标轴旋转
| |||
围绕第三个坐标轴旋转
|
这几个基本的操作相结合(矩阵乘法),就可以实现坐标轴的变换
上面完成了坐标轴的对应,但是体素的原点(0,0,0)对应的不一定是参考空间的(0,0,0)点,假设对应的是参考空间中的(a,b,c)坐标,那么,体素中的(i,j,k)对应的是参考空间中的(x,y,z)
3.2.4 获得image的affine
参考文献
NII - Just Solve the File Format Problem (archiveteam.org)
How to read NIFTI format image (. nii file) (programmer.group)