本文内容、图片与涉及的源码均为作者原创,未经许可不得转载。版权声明或联系作者请移步 “关于”

继续计算机与医学影像的跨界之旅,本文所述内容是理解磁共振成像的K空间,并利用计算机还原和处理K空间的数据。K空间的概念在大部分书中的描述不是高深莫测就是晦涩难懂,天书之中再罗列一堆公式,医学影像小伙伴们绝对蒙!作为医学界的理工男,有责任和义务以自己的层次和认知为大家对磁共振成像中棘手的概念进行解读,尽量用最简单的语言把K空间这个事说清楚。

磁共振信号的采集和图像的重建远远复杂于CT,是至今为止最复杂的医学成像方式。我们可以通过改变射频脉冲的发射和接收时间、数据采集的方法等等,来缩短成像时间,改变图像的对比度、空间分辨率,甚至消除图像伪影。图像一旦重建完成,后续的诊断、分析和研究工作也都是以图像本身为基础进行展开,基本的物理学和医学知识是磁共振成像的基础,而脉冲序列和K空间则是磁共振成像的核心!

理解K空间之前必须要了解图像的空域、频域以及傅里叶变换的概念。

图像的空域

一幅灰度图像就是一个平面数据,而一幅彩色图像就是红绿蓝三个颜色通道叠加而成的平面数据。透过图像看这个平面数据,其实和我们看到的excel表格的内容差不多,多行多列的数据点分布在一个平面内。空域内的图像也是由许多个点组成,不同位置的点表现出不同颜色或不同灰度值,这个点叫像素,一幅我们能看得懂的图像就是许多附带位置信息的像素的数据集合,这个集合我们称之为像素域,也称空间域,简称空域。一副256阶灰度图像的灰度值从0-255分布在一定分辨率下的平面内,0为黑色,255为白色,其它灰度值在0-255数值之间离散分布。在空域对图像的处理其实就是对像素的处理,即对像素位置和大小的改变以及灰度值的改变,因此对图像进行放大、缩小、旋转、剪裁,或者由彩色变成黑白图像,再或者对黑白图像赋予伪彩等都是在图像的空域下进行。一副1920x1080分辨率的普通彩色数码图片及对应的灰度图像,以空域格式呈现出来就是我们能看懂的图像:

图像的频域

通过以上对空域的解释,我们可以猜到,图像的频域就是图像频率数据的集合,图像也有频率?!当然,每一个像素对应的颜色值或者灰度值,都会有个幅度,而相邻像素和像素之间幅度值的变化就反映了图像的频率差异,那么把这些频率差异按照不同位置不同振幅重新整合成一幅频谱图,就是图像的频域数据或者叫频率数据,一提到频率肯定会有振幅和相位,因此这个数据中还包含该频率对应下的振幅和相位信息。图像的频率数据分布与空域不同,空域数据的分布是按照像素值的大小组成的数据,频域则是按照频率的高低组成的数据,因此,从频域的角度理解,可以把图像分成高中低三部分频率分量。一幅图像的主要部分,其灰度值分布一般比较平坦,那么低频分量就比较强,图像的边缘及噪声的像素灰度变化非常剧烈,则为高频分量,因此,对图像进行分割、对比度增强以及降噪滤波等都应在图像的频域下进行。说到这里,医学影像界的小伙伴们可能有些蒙圈,但也是不是想到了些什么?对,几乎每一本磁共振书都会提到过的:K空间的中心部分决定磁共振图像的对比度,边缘部分决定图像细节!哎妈呀,似乎确实有些联系,先按下不表。先来看看前述同一副图像在频域下的样子:

傅里叶变换

看到这里,我们已经知道,图像其实会有两种表现形式,空域和频域,尽管两种数据格式不同,但它们反映的都是同一副图像!能对图像的两种形式进行互相变换的方法就是傅里叶变换!傅里叶变换是纯数学理论,最初应用在信号处理领域,将其应用在磁共振领域再合适不过了,由此可见学科交叉是多么重要!傅里叶变换在书本中枯燥的定义为:能将满足一定条件的某个函数表示成一系列三角函数(正弦和/或余弦函数)或者它们的积分的线性组合。蒙圈吧,其实通俗理解,傅里叶变换有点像三棱镜的作用,白色光经过三棱镜就被分解成7色光!对于信号来讲,傅里叶变换能把混合了不同频率、不同振幅、不同相位的单一信号进行分解,同样的,经过傅里叶逆变换,也可以将七色光重建成白色光。

傅里叶变换的数学基础

磁共振接收线圈每次接收到的信号都是经激发的那个层面中的每一个氢质子所发出的信号叠加而成,而信号其实就是圆周运动在时域下的投影,且具有特定频率、振幅以及相位的正弦波,任意一个正弦波都可以由若干个简单的正弦波叠加而成,例如:

傅里叶变换的作用就是要将某个波形的信号进行分解,找到该原始信号究竟是经由哪些正弦波分量叠加而成(余弦波可由正弦波平移得到)。更复杂和深层次的数学解释请自行谷歌,咱们医学小伙伴们不必过分深究,看个图最直接:

由上图可延伸出如下信号叠加的三角函数公式:

w1,w2,… 是频率的变化,A1,A2,…就是对应频率下的振幅,ϕ1,ϕ2,…就是对应频率下的相位。那么在数学上如何用一个数描述信号的振幅和相位呢?这就需要借助复数及欧拉公式,一个复数有是实部和虚部,经过运算就可以表示振幅和相位(在复平面内,实部就是圆周运动到的某个点在实数轴的投影,虚部就是在虚数轴的投影,实部和虚部的夹角就是相位,因此振幅就是实部与虚部的平方和再开方,相位就是atan(实部/虚部),即反正切三角函数,得到相位的弧度角)。欧拉公式被数学界称为”数学中最非凡的公式“,它和三角函数类似都是描述圆周运动,但它能将三角函数与复指数函数关联起来,最经典的作用就是一个实数通过欧拉公式的映射可以得到一个复数,反之便可以得到一个实数。有了振幅和相位,又能将复数映射成一个实数,我们就可以重建出一副图像,这在后边的实验中会见证。

K空间

医学影像的小伙伴们对于磁共振成像的空间编码、激发和采集的基本过程已经了解,我们应该知道磁共振K空间里的每一个点就是直接来自MR信号的数据点,那么这个数据点究竟是什么呢?磁共振的每次激发和采集前首先利用频率梯度场对人体进行选层,而选定层面内的体素则继续利用另外的频率梯度场进行频率和相位的空间编码,随后进行射频激发,我们就可以获得该层面的全层且附带空间编码的回波信号,反复以不同频率激发这个层面就会获得该层面不同频率下的回波信号,将这些回波信号们按照频率高低以及某种填充方式填入K空间中,这种数据的组织方式是不是有点像前述的图像频域数据格式?没错,K空间中的数据点其实就是最终磁共振图像的频域数据!对K空间的频域数据进行傅里叶逆变换(需要指出,杨正汉的<>K空间一节中提到的从K空间重建出磁共振图像应为傅里叶逆变换,估计是笔误,因为本文会有代码佐证。),我们就能得到频域数据对应的空域数据格式,即一副磁共振图像。这有点像前述的三棱镜,填充K空间的作用就是先准备好七色光,然后经三棱镜后我们就可得到一束白光。K空间的数据既然是频域数据,低频分量会集中在K空间的中心区域,高频分量集中在K空间的周边,因此,K空间中心部分的低频分量决定最终图像对比度,周边部分的高频分量决定图像细节。另外,在K空间中的每个数据点都包含频率的振幅和相位信息,单一数值是无法表示的,需要借助前述的复数来表达,因此K空间中数据点的数值都是复数,利用复数的实部和虚部计算出该点的的振幅和相位。这就是磁共振成像的核心,也是K空间概念的极简版,之前按下不表的地方是不是建立起了广泛联系?

还原和重建K空间数据

说了这么多,我们用计算机来加以验证。一般情况下,我们很难从磁共振主机上得到刚刚填充好的K空间原始数据,但利用傅里叶变换便可以将图像的空域数据转化成频域数据,我们仍以GE MR 750w的磁共振图像为例,将其还原出K空间数据。

之前提到过,灰度图像的空域数据中的像素点都是在0-255之间分布的灰度值,而频域数据是具有空间位置编码的信号,即利用复数形式保存的的振幅和相位信息,这就是为什么图像的频域数据会有振幅和相位两幅图。此时此刻,我们应该祭出numpy,利用它提供的快速傅里叶变换函数还原磁共振K空间数据,核心代码如下:

1

2

3

4

5

6

7

8

9

10

11dicomfile_ge = pydicom.read_file('/home/ray/Downloads/ge_head_1.dcm')

img_original = dicomfile_ge.pixel_array

f = np.fft.fft2(img_original) # 快速傅里叶变换算法得到频率分布

fshift = np.fft.fftshift(f) # 移频,默认结果中心点位置是在左上角,转移到中间位置

a_fshift = np.log(np.abs(fshift)) # 取振幅

ph_fshift = np.angle(fshift) # 取相位

plt.subplot(131), plt.imshow(img_original, 'gray'), plt.title('original')

plt.subplot(132), plt.imshow(a_fshift, 'gray'), plt.title('amp')

plt.subplot(133), plt.imshow(ph_fshift, 'gray'), plt.title('phase')

plt.show()

复数形式的数据无法进行显示,因此我们提取出振幅和相位数据分别进行显示。

空域数据和频域数据尽管指向的都是同一幅图像,但其数据本质截然不同,摘要如下:

GE MR 750w 的最终图像的空域数据保存的都是16位整数形式的灰度值,而频域数据果然是复数,在我的开发环境中虚部单位用字母j表示。

有了K空间的振幅和相位数据 a_fshift 和 ph_fshift ,我们可以利用傅里叶逆变换及欧拉公式再重建出空域图像,核心代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18# 逆变换--利用欧拉公式 cos(x) + isin(x) ⇔ a + ib

# 将振幅和相位整合成复数的实部和虚部,即整合成频域数据

s_real = np.exp(a_fshift)*np.cos(ph_fshift) # 整合复数的实部

s_imag = np.exp(a_fshift)*np.sin(ph_fshift) # 整合复数的虚部

s = np.zeros([512, 512], dtype=complex) # 指定数据类型为复数

s.real = np.array(s_real)

s.imag = np.array(s_imag)

fshift = np.fft.ifftshift(s) # 对整合好的频域数据进行逆变换

img_back = np.fft.ifft2(fshift)

# 出来的是复数,取绝对值,转化成实数

img_back = np.abs(img_back)

plt.subplot(141), plt.imshow(img_original, 'gray'), plt.title('original')

plt.subplot(142), plt.imshow(a_fshift, 'gray'), plt.title('amp')

plt.subplot(143), plt.imshow(ph_fshift, 'gray'), plt.title('phase')

plt.subplot(144), plt.imshow(img_back, 'gray'), plt.title('inverse fourier')

plt.show()

对K空间数据进行简单处理

想必每位医学影像小伙伴们都读过不少磁共振书,其中都会提到K空间中心决定图像对比度周边决定图像细节,有了以上内容做铺垫,我们就继续动手实验对K空间的数据进行简单处理来验证上述说法。我们分别创建两个滤波矩阵并与K空间的频率数据相乘,一个滤波矩阵中心部分为1(白色)周边部分为0(黑色),和K空间的数据相乘后会保留K空间中心部分的频率值,另一个滤波矩阵中心部分为0(黑色)周边部分为1(白色),和K空间的数据相乘后会保留K空间周边部分的频率值,原始K空间频域数据和滤波矩阵相乘后再经傅里叶逆变换得到的图像如下:

上图第一行中只利用K空间中心圆部分很少的低频分量就能重建出磁共振图像,但图像模糊、对比度稍差;上图第二行利用K空间周边部分的高频分量只能重建出图像的轮廓,而图像几乎没有对比度。由此可见K空间的中心部分对于磁共振图像重建相当重要,这也是为什么快速成像序列、扩散序列、防止运动伪影序列以及插值算法必须优先甚至反复填充K空间中心的原因。产生上图的部分核心代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23def make_transform_matrix(d, image):

transform_matrix = np.zeros(image.shape)

center_point = tuple(map(lambda x: (x-1)/2, image.shape))

for i in range(transform_matrix.shape[0]):

for j in range(transform_matrix.shape[1]):

def cal_distance(pa, pb):

from math import sqrt

# d值决定中心圆大小,遍历并计算频率域中的每一个点与这个圆边界的距离

dis = sqrt((pa[0]-pb[0])**2+(pa[1]-pb[1])**2)

return dis

dis = cal_distance(center_point, (i, j))

if dis <= d:

transform_matrix[i, j] = 1

else:

transform_matrix[i, j] = 0

return transform_matrix

d_1 = make_transform_matrix(40, a_fshift) # 40 是中心园的半径

img_d1 = np.abs(np.fft.ifft2(np.fft.ifftshift(fshift*d_1)))

plt.subplot(141), plt.imshow(dicomfile_ge.pixel_array, 'gray'), plt.title('original')

plt.subplot(142), plt.imshow(a_fshift, 'gray'), plt.title('k-space amp')

plt.subplot(143), plt.imshow(d_1, 'gray'), plt.title('filter')

plt.subplot(144), plt.imshow(img_d1, 'gray'), plt.title('result')

总结

借助计算机并通过以上实验,我们知道K空间就是最终磁共振图像的频域格式,经过傅里叶逆变换将频域数据转化成空域数据格式,就是我们看得懂的图像了。有了频域数据,我们可以像处理信号一样进行滤波处理、对比度增强以及边缘分割等一系列复杂的图像处理。书本中的概念往往晦涩难懂,从那个维度去理解K空间确实让非理工科的小伙伴们不知所措,但是如果我们能摆脱专业束缚,用另一种方式去理解它,说不定就能豁然开朗、拨云见日。

matlab mri的k空间,理解磁共振K空间,自己动手还原和处理K空间数据相关推荐

  1. C++:应用有限差分法求解 稳平流扩散方程 v*ux-k*uxx=0 in 一个空间维度,具有恒定的速度 v 和扩散系数 k(附完整源码)

    C++:应用有限差分法求解 稳平流扩散方程 v*ux-k*uxx=0 in 一个空间维度,具有恒定的速度 v 和扩散系数 k # include <cstdlib> # include & ...

  2. 深刻理解空间(线性空间,度量空间,赋范空间,线性赋范空间,内积空间,巴拿赫空间以及希尔伯特空间)

    在我们学习矩阵理论和统计理论的时候,总是会出现"**空间".在之前的时候对于空间理解的过程中,总是试图拿出一个具体的例子来加深自己的理解.但是这样做是不对的,因为如果说对于类似&q ...

  3. 373. Find K Pairs with Smallest Sums 找出求和和最小的k组数

    [抄题]: You are given two integer arrays nums1 and nums2 sorted in ascending order and an integer k. D ...

  4. 算法—2,记一个自己的算法题 计算数字k在0到n中的出现的次数,k可能是0~9的一个值

    3 计算数字k在0到n中的出现的次数,k可能是0~9的一个值 例如n=12,k=1,在 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],我们发现1出现了5次 (1 ...

  5. K近邻分类器(李飞飞CS231n学习笔记---lecture2:K最近邻算法)

    在讲解K近邻分类器之前,我们先来看一下最近邻分类器(Nearest Neighbor Classifier),它也是K = 1时的K近邻分类器. 目录 最近邻分类器 定义 存在问题 K近邻分类器(KN ...

  6. 声网在线K歌房解决方案:一站式接入版权曲库与K歌组件

    9月8日,实时互动云服务商声网Agora在北京举办主题为"K歌有声·想唱就唱"的发布会,正式发布了在线K歌房场景化解决方案,开发者与企业可一站式接入海量正版曲库与K歌组件.场景功能 ...

  7. matlab空间截面回归,截面空间回归模型操作应用手册

    原标题:截面空间回归模型操作应用手册 SAR模型数据集包含对地理区域或其他单元的观测;所以需要的是有一些距离的度量标准来区分哪些单位彼此之间比较近. spregress命令对横断面数据进行建模.它要求 ...

  8. python能画k线图吗_k线图怎么画?_Python绘制K线图

    本文介绍关于Python绘制K线图与股票中怎样才能画出有效的趋势线.压力和支撑位?与手工绘制股票K线图有什么技巧,要先从哪学起?与外汇怎样绘制蜡烛图?与k线图怎么变宽了,怎么复原?与怎样判断K线点位高 ...

  9. [Oracle]理解undo表空间

    [Oracle]理解undo表空间 一.回退段介绍 在Oracle数据库中,当某个事物对数据进行修改时,Oracle首先将数据的原始值保存到一个回退段中.一个事物只能将它的回退信息保存到一个回退段中, ...

最新文章

  1. 使用antd报less的错误
  2. php程序layer,php 提交表单 关闭layer弹窗iframe的实例讲解
  3. 如果要做小程序创业,哪种方式最赚钱?
  4. python网络库_python的网络库
  5. 深度学习attention原理_深度学习Anchor Boxes原理与实战技术
  6. 创建维护计划失败_如何善于创建和维护大型系统
  7. Qt5.7 win10环境 调试器未设置问题解决
  8. Python基础第五天
  9. Kofi's back
  10. 地理空间数据云 数据
  11. java引用不同包下同名类_Java--一个类中引用不同包下同名类
  12. python安装作业
  13. 哈哈哈!段子手们在家被迫营业,每一个都能笑到窒息!
  14. 收深圳2022年的高新技术企业(软件开发)
  15. 2023软考信息系统项目管理师论文写作
  16. marlin速度前瞻运动控制c语言程序,开源cnc项目Marlin2.0运动控制部分代码理解-Go语言中文社区...
  17. CA证书(数字证书的原理)
  18. ES6之二(解构赋值)
  19. Windows好用软件
  20. Android模拟器(ADM)打不开/data,无法导出数据库文件

热门文章

  1. Android未找到分区,Android System分区文件丢失分析
  2. 24 直面配分函数Confronting Partition Function
  3. 计算机考研数学书,计算机考研参考书(专业课、数学、英语)
  4. unity3d场景导入webgl/three.js
  5. Linux学习(Kali为蓝本)
  6. 3D坐标系、矩阵变换、视景体与裁剪
  7. c语言用递归求奇数和,奇数正整数和的递归算法
  8. ESLint+Prettier+Vetur 统一Vue项目代码风格
  9. C语言初步学习笔记——第四节 有符号数与常见关键字
  10. 【精】iOS知识树,知识点(包括对象、Block、消息转发、GCD、运行时、runloop、动画、Push、KVO、tableview,UIViewController、提交AppStore)