大家好,半个多月之前,我介绍了Radon变换和直接反投影以及滤波反投影的算法,现在向大家介绍一下ART算法,这是另一种CT图像重建的算法,同时给出Python实现。下面先简单地介绍一下投影矩阵的生成和ART算法的数学基础。
投影矩阵代数重建算法的基础,它将投影数据和断层图像联系了起来,投影矩阵的计算方法也将影响重建图像的质量,投影矩阵的模型可以分为以下几种:
其中模型1、模型3较为简单,但是容易受噪声影响,而模型4 计算量较大,所以后面的源码中实现的是模型2。
Algebraic Reconstruction Technique (ART) 代数重建算法可以利用不完全的信息进行重建(不需要将180 ° \degree °分为几百上千个步来扫描,而FBP算法需要的投影数据较多),重建结果不易受噪声影响等优点,但是重建耗费的时间长。这个算法其实就是解一个方程,射束组成一个一维数组,记为 [ p 1 , p 2 , . . . , p m ] [p_{1}, p_{2}, ..., p_{m}] [p1,p2,...,pm],其中 m m m为穿过物体的x射线的条数,它等于投影角度与每个投影角度上射束的条目的乘积,则有一个方程:
{ ω 11 f 1 + ω 12 + . . . + ω 1 N f N = p 1 ω 21 f 1 + ω 22 + . . . + ω 2 N f N = p 2 . . . ω m 1 f 1 + ω m 2 + . . . + ω m N f N = p m \begin{cases} \omega_{11} f_1 + \omega_{12}+...+\omega_{1N} f_N = p_{1}\\ \omega_{21} f_1 + \omega_{22}+...+\omega_{2N} f_N = p_{2}\\ ...\\ \omega_{m1} f_1 + \omega_{m2}+...+\omega_{mN} f_N = p_{m} \end{cases} ⎩⎪⎪⎪⎨⎪⎪⎪⎧ω11f1+ω12+...+ω1NfN=p1ω21f1+ω22+...+ω2NfN=p2...ωm1f1+ωm2+...+ωmNfN=pm
一共N个未知数( f 1 , f 2 , . . . , f N f_{1}, f_{2}, ..., f_{N} f1,f2,...,fN),M个方程,其中的 ω i j \omega_{ij} ωij就是前面介绍的投影矩阵的元素。我们只要进行以下形式的迭代:
f j k + 1 = f j k + λ p i − ∑ n = 1 N w i n f n k ∑ n = 1 N ω i n 2 ω i j , j = 1 , 2 , . . . , N f_{j}^{k+1} = f_{j}^{k} + \lambda \frac{p_{i}-\sum_{n=1}^{N} w_{in}f_{n}^{k}}{\sum_{n=1}^{N}\omega_{in}^{2}}\omega_{ij}, \quad j=1, 2, ..., N fjk+1=fjk+λ∑n=1Nωin2pi−∑n=1Nwinfnkωij,j=1,2,...,N
进行一次迭代就是一次投影和反投影操作,我们可以进行多次迭代,直到结果收敛。
#作者:CSDN用户“宋体的微软雅黑(hsyxxyg)”
#时间:2020年6月18日
#脚本任务:生成投影系统矩阵,并利用此矩阵进行ART重建。
import numpy as np
import pandas as pd
from scipy import ndimage
import imageio
import matplotlib.pyplot as plt
def CalSystemMatrix(theta, pictureSize, projectionNum, delta):
squareChannels = np.power(pictureSize, 2)
totalPorjectionNum = len(theta) * projectionNum
gridNum = np.zeros((totalPorjectionNum, 2 * pictureSize))
gridLen = np.zeros((totalPorjectionNum, 2 * pictureSize))
t = np.arange(-(pictureSize - 1) / 2, (pictureSize - 1) / 2+1)
for loop1 in range(len(theta)):
for loop2 in range(pictureSize):
u = np.zeros((2 * pictureSize))
v = np.zeros((2 * pictureSize))
th = theta[loop1]
if th == 90:
#如果计算的结果超出了网格的范围,则立刻开始计算下一个射束
if ((t[loop2] >= pictureSize / 2 * delta) or (t[loop2] <= - pictureSize / 2 * delta)):
continue
kout = pictureSize * np.ceil(pictureSize/2 - t[loop2]/delta)
kk = np.arange(kout - (pictureSize -1 ), kout+1)
u[0:pictureSize] = kk
v[0:pictureSize] = np.ones(pictureSize) * delta
elif th==0:
if (t[loop2] >= pictureSize / 2 * delta) or (t[loop2] <= -pictureSize / 2 * delta):
continue
kin = np.ceil(pictureSize/2 + t[loop2] / delta)
kk = np.arange(kin, (kin + pictureSize * pictureSize), step=pictureSize)
u[0:pictureSize] = kk
v[0:pictureSize] = np.ones(pictureSize) * delta
else:
if th>90:
th_temp = th - 90
elif th<90:
th_temp = 90 - th
th_temp = th_temp * np.pi / 180
#计算束线的斜率和截距
b = t / np.cos(th_temp)
m = np.tan(th_temp)
y1d = -(pictureSize / 2) * delta * m + b[loop2]
y2d = (pictureSize / 2) * delta * m + b[loop2]
#if (y1d < -pictureSize / 2 * delta and y2d < -pictureSize/2 * delta) or (y1d > pictureSize / 2 * delta and y2d > -pictureSize / 2 * delta):
if (y1d < -pictureSize / 2 * delta and y2d < -pictureSize/2 * delta) or (y1d > pictureSize / 2 * delta and y2d > pictureSize / 2 * delta):
continue
if (y1d <= pictureSize / 2 * delta and y1d >= -pictureSize/2 * delta and y2d > pictureSize / 2 * delta):
yin = y1d
d1 = yin - np.floor(yin / delta) * delta
kin = pictureSize * np.floor(pictureSize / 2 - yin / delta) + 1
yout = pictureSize / 2 * delta
xout = (yout - b[loop2]) / m
kout = np.ceil(xout / delta) + pictureSize / 2
elif (y1d <= pictureSize/2 * delta and y1d >= -pictureSize/2 * delta and y2d >= -pictureSize/2 * delta and y2d < pictureSize/2 * delta):
yin = y1d
d1 = yin - np.floor(yin/delta) * delta
kin = pictureSize * np.floor(pictureSize / 2 - yin / delta) + 1
yout = y2d
#1:
#2:xout = (yout - b[loop2]) / m
kout = pictureSize * np.floor(pictureSize/2 - yout/delta) + pictureSize
elif (y1d < - pictureSize / 2 * delta and y2d > pictureSize / 2 * delta):
yin = - pictureSize / 2 * delta
xin = (yin - b[loop2]) / m
d1 = pictureSize / 2 * delta + np.floor(xin / delta) * delta * m + b[loop2]
kin = pictureSize * (pictureSize - 1) + pictureSize / 2 + np.ceil(xin / delta)
yout = pictureSize / 2 * delta
#error: xout = (yout / b[loop2])/m
xout = (yout - b[loop2]) / m
kout = np.ceil(xout / delta) + pictureSize / 2
elif (y1d < - pictureSize / 2 * delta and y2d >= -pictureSize / 2 * delta and y2d < pictureSize / 2 * delta):
yin = -pictureSize / 2 * delta
xin = (yin - b[loop2]) / m
d1 = pictureSize / 2 * delta + np.floor(xin / delta) * delta * m + b[loop2]
kin = pictureSize * (pictureSize - 1) + pictureSize / 2 + np.ceil(xin / delta)
yout = y2d
kout = pictureSize * np.floor(pictureSize / 2 - yout / delta) + pictureSize
else:
continue
#计算第i条射束穿过的像素的编号和长度
k = kin
c = 0
d2 = d1 + m * delta
while k >= 1 and k <= squareChannels:
if d1 >= 0 and d2 > delta:
u[c] = k
v[c] = (delta - d1) * np.sqrt(np.power(m, 2) + 1) / m
if k > pictureSize and k != kout:
k = k - pictureSize
d1 = d1 - delta
d2 = d1 + m * delta
else:
break
elif d1 >= 0 and d2 == delta:
u[c] = k
v[c] = delta * np.sqrt(np.power(m, 2) + 1)
if k>pictureSize and k != kout:
k = k - pictureSize + 1
d1 = 0
d2 = d1 + m * delta
else:
break
elif d1 >= 0 and d2 < delta:
u[c] = k
v[c] = delta * np.sqrt(np.power(m, 2) + 1)
if k!=kout:
k = k + 1
d1 = d2
d2 = d1 + m * delta
else:
break
elif d1 <= 0 and d2 >= 0 and d2 <= delta:
u[c] = k
v[c] = d2 * np.sqrt(np.power(m, 2) + 1) / m
if k != kout:
k = k + 1
d1 = d2
d2 = d1 + m * delta
else:
break
elif d1 <= 0 and d2 > delta:
u[c] = k
v[c] = delta * np.sqrt(np.power(m, 2) + 1) / m
if k > pictureSize and k != kout:
k = k - pictureSize
#k = k + 1
d1 = -delta + d1
d2 = d1 + m * delta
else:
break
else:
print(d1, d2, "error!!!")
c = c + 1
#如果投影角度小于90度,应该利用投影射线关于y轴的对称性计算出权重因子向量。
if th < 90:
u_temp = np.zeros(2 * pictureSize)
if u.any() == 0:
continue
indexULTZero = np.where(u>0)
for innerloop in range(len(u[indexULTZero])):
r = np.mod(u[innerloop], pictureSize)
if r == 0:
u_temp[innerloop] = u[innerloop] - pictureSize
else:
u_temp[innerloop] = u[innerloop] - 2 * r + pictureSize
u = u_temp
gridNum[loop1 * projectionNum + loop2, :] = u
gridLen[loop1 * projectionNum + loop2, :] = v
return gridNum, gridLen
def DiscreteRadonTransform(image, theta):
projectionNum = len(image[0])
thetaLen = len(theta)
radontansformRes = np.zeros((projectionNum, thetaLen), dtype='float64')
for s in range(len(theta)):
rotation = ndimage.rotate(image, -theta[s], reshape=False).astype('float64')
radontansformRes[:, s] = sum(rotation)
return radontansformRes
image = imageio.imread("shepplogan.jpg").astype(np.float64)
theta = np.linspace(0, 180, 60, dtype=np.float64)
res = DiscreteRadonTransform(image, theta)
#定义用到一些参数:旋转角度的矩阵,探测器的道数,图片尺寸,平移步长,最大迭代次数,驰豫因子
projectionNum = np.int64(256)
pictureSize = np.int64(256)
pictureSizeSquare = pictureSize * pictureSize
delta = np.int64(1)
irt_Num = np.int64(20)
lam = np.float64(0.25)
gridNum, gridLen = CalSystemMatrix(theta, pictureSize, projectionNum, delta)
dfgridNum = pd.DataFrame(gridNum)
dfgridLen = pd.DataFrame(gridLen)
dfgridNum.to_csv("gridNum.csv")
dfgridLen.to_csv("gridLen.csv")
#gridNum = np.array(pd.read_csv("gridNum1.csv"))
#gridLen = np.array(pd.read_csv("gridLen1.csv"))
F = np.zeros((pictureSize*pictureSize, ))
irt_Num = 10
lam = 0.25
c = 0
#开始迭代
while(c < irt_Num):
for loop1 in range(len(theta)):
for loop2 in range(pictureSize):
u = gridNum[loop1 * pictureSize + loop2, :]
v = gridLen[loop1 * pictureSize + loop2, :]
if u.any() == 0:
continue
w = np.zeros(pictureSizeSquare, dtype=np.float64)
uLargerThanZero = np.where(u > 0)
w[u[uLargerThanZero].astype(np.int64)-1] = v[uLargerThanZero]
PP = w.dot(F)
C = (res[loop2, loop1] - PP) / sum(np.power(w, 2)) * w.conj()
F = F + lam * C
F[np.where(F < 0)] = 0
c = c + 1
F = F.reshape(pictureSize, pictureSize).conj()
#绘制Sinogram图和重建的结果
plt.subplot(1, 2, 1)
plt.imshow(res, cmap="gray")
plt.subplot(1, 2, 2)
plt.imshow(F, cmap="gray")
plt.savefig("modify90Sample.png", cmap="gray")
plt.show()
写在最后:上面的代码已经是可以运行的程序了,但是计算时间有点长,在我的CPU为Ryzen 4800u的笔记本上需要27分钟左右的时间(包括生成投影矩阵和迭代过程)。当然,我们可以使用numba对迭代过程中的循环进行加速,我大致看了一下,加速之后代码的运行时间缩短到了4分钟左右。大家可以自行尝试一下使用numba对Python的循环过程进行提速,还挺简单的。