CT 重建从零开始(五):ASTRA Toolbox 上手——几何、投影器与算法

前几篇的实验都用 scikit-image 的 radon / iradon,或者自己写几十行 NumPy。这样讲原理足够,但一到真实尺寸就不够用了:没有 GPU 加速,没有锥束,也很难嵌进深度学习网络。

上一篇精读的 Learned Primal-Dual,每次迭代里的正投影和反投影,就是 ODL 调用 ASTRA Toolbox 的 GPU 后端完成的。ASTRA 是 CT 重建论文里最常见的底层投影库之一,很多上层框架都建立在它之上。

从这一篇开始,基础系列用几篇的篇幅,一个库一个库地讲常用的 CT 工具。本篇只讲 ASTRA:它是什么、怎么装、几何怎么写、投影器怎么选、自带哪些算法、怎么把它当成一个线性算子来用,以及最容易踩的坑。

文中代码全部原样摘自 ASTRA 官方仓库的 samples/python 和官方文档,没有改动。其中只用 CPU 的示例,我在 CPU 版 ASTRA 2.5.0 上原样跑通了;需要 GPU 的示例会单独注明。

一、ASTRA 是什么

官方的定位是:一个面向 2D 和 3D 断层成像的高性能 GPU 基本运算库,提供 Python 和 MATLAB 接口。按官网列出的特性:

  • 几何:2D 平行束、扇束,3D 平行束、锥束,并且源和探测器的位置可以逐角度自由指定;
  • 算法:FBP、SIRT、SART、CGLS 等 2D 和 3D 重建算法;
  • 正投影和反投影都做了 GPU 加速,可以直接从 Python / MATLAB 调用,用来搭自己的算法;
  • 数据放不进显存时自动切分,支持多 GPU;
  • 通过 DLPack 和 NumPy、PyTorch、CuPy、JAX 零拷贝交换数据。

它由安特卫普大学 imec-Vision Lab 和荷兰 CWI 开发维护,许可证是 GPLv3。学术研究随便用;如果要把它打包进闭源产品,先看清 GPL 的条款。

一句话概括它的位置:ASTRA 负责"算得快、几何写得灵活"的投影和反投影,本身不提供 TV 之类的正则化,也不提供自动求导。正则化、优化算法、和深度学习框架的衔接,通常交给上层库或者自己的代码。

二、安装

官方 README 给出的安装方式(命令原文照录,中文注释是我加的):

bash 复制代码
# conda(官方频道,带 CUDA)
conda install -c astra-toolbox -c nvidia astra-toolbox

# conda-forge(含 Linux ARM 和 Apple Silicon 的 CPU 版)
conda install -c conda-forge astra-toolbox

# conda-forge 上的纯 CPU 版(无 GPU 加速,只有 2D 算法)
conda install -c conda-forge astra-toolbox=*=py*

# pip
pip install astra-toolbox

几点说明:

  1. 纯 CPU 版只有 2D 功能。 官方 README 原话是 "no GPU acceleration and CUDA dependencies, 2D algorithms only"。我在一台 ARM 服务器上装的就是 conda-forge 的 CPU 版:调用 'cuda' 投影器会直接报 Unknown Projector2D type,3D 正投影报 Unknown algorithm type。想做锥束和 FDK,必须有 NVIDIA GPU。
  2. 老显卡注意 CUDA 版本。 官网提示:CUDA 13 的包不再支持 GTX 9 系和 10 系显卡,这类显卡要用 CUDA 12 的包。
  3. pip 轮子有平台限制。 2.5.0 在 PyPI 上提供 Linux x86_64 和 Windows 的预编译包(Windows 的 PyPI 发布是 2.5.0 新加的);其他平台走 conda 或源码编译。

装好后可以先确认版本和 GPU 是否可用:astra.__version__、astra.use_cuda()。

三、四个核心概念

ASTRA 的 API 风格和 scikit-image 很不一样:核心接口不是"传数组进去、拿数组出来"的函数,而是先创建一个个对象 ,用整数 ID 引用它们。只要理解下面四个概念,所有示例都能看懂。

概念 作用 典型调用
几何(geometry) 体数据放在哪、射线源和探测器怎么摆 create_vol_geom、create_proj_geom
数据对象(data) 体数据或投影数据,存在 ASTRA 内部,用 ID 引用 astra.data2d.create、get、delete
投影器(projector) 用什么模型计算射线和像素的权重 create_projector
算法(algorithm) 正投影、反投影、FBP、SIRT 等,用配置字典描述 astra_dict、algorithm.create、run

基本流程是:定几何 → 建数据对象 → 选投影器 → 写算法配置 → 运行 → 取结果 → 释放对象(GPU 算法不需要在配置里指定投影器,见下文)。

四、第一个完整例子:CPU 上的 SIRT 重建

下面是官方示例 s004_cpu_reconstruction.py 的全部正文(省略了开头的版权注释),纯 CPU 就能跑:

python 复制代码
import astra
import numpy as np
import matplotlib.pyplot as plt
plt.gray()


vol_geom = astra.create_vol_geom(256, 256)
proj_geom = astra.create_proj_geom(
    'parallel', 1.0, 384, np.linspace(0, np.pi, 180, False)
)

# For CPU-based algorithms, a 'projector' object specifies the projection
# model used. In this case, we use the 'strip' model
proj_id = astra.create_projector('strip', proj_geom, vol_geom)

# Create a sinogram from a phantom
phantom_id, P = astra.data2d.shepp_logan(vol_geom)
sinogram_id, sinogram = astra.create_sino(phantom_id, proj_id)

plt.imshow(P)
plt.figure()
plt.imshow(sinogram)

# Create a data object for the reconstruction
rec_id = astra.data2d.create('-vol', vol_geom)

# Set up the parameters for a reconstruction algorithm using the CPU
# The main difference with the configuration of a GPU algorithm is the
# extra ProjectorId setting
cfg = astra.astra_dict('SIRT')
cfg['ReconstructionDataId'] = rec_id
cfg['ProjectionDataId'] = sinogram_id
cfg['ProjectorId'] = proj_id
# Available algorithms:
# ART, SART, SIRT, CGLS, FBP

# Create the algorithm object from the configuration structure
alg_id = astra.algorithm.create(cfg)

# Run 20 iterations of the algorithm
# This will have a runtime in the order of 10 seconds
astra.algorithm.run(alg_id, 20)

# Get the result
rec = astra.data2d.get(rec_id)
plt.figure()
plt.imshow(rec)
plt.show()

# Clean up.
astra.algorithm.delete(alg_id)
astra.data2d.delete(rec_id)
astra.data2d.delete(sinogram_id)
astra.data2d.delete(phantom_id)
astra.projector.delete(proj_id)

逐段看:

  1. 几何。 create_vol_geom(256, 256) 是 256×256 的图像;create_proj_geom('parallel', 1.0, 384, angles) 是平行束,探测器 384 个单元、间距 1.0,180 个角度均匀分布在 [ 0 , π ) [0, \pi) [0,π)。np.linspace(0, np.pi, 180, False) 的最后一个参数是 endpoint=False,所以不包含 π \pi π。
  2. 投影器。 'strip' 把每条射线看成和探测器单元等宽的条带,权重是条带和像素的相交面积。CPU 算法必须显式指定投影器。
  3. 正投影。 create_sino 返回两个东西:投影数据对象的 ID,以及对应的 NumPy 数组。这里的正弦图形状是 (180, 384),即**(角度数,探测器单元数)**。
  4. 算法配置。 astra_dict('SIRT') 生成一个配置字典,填入重建结果、投影数据和投影器三个 ID。按官方文档,ReconstructionDataId 里原有的内容会被当作迭代初值,这里 data2d.create('-vol', ...) 建出来的是全零图像。注释里写了 CPU 版可选的算法:ART、SART、SIRT、CGLS、FBP。
  5. 运行。 run(alg_id, 20) 迭代 20 次。官方注释估计运行时间在 10 秒量级;我在 ARM 服务器的 CPU 版上,整段脚本约 6 秒。
  6. 释放。 最后五行 delete 不能省。ASTRA 的对象不随 Python 变量自动回收:我在函数里创建数据对象后不删除,函数返回、手动 gc.collect() 之后,astra.data2d.info() 里仍然列着它们。循环里反复创建而不释放,内存会一直涨。

SIRT 和 SART 的原理在(二)里推过。这里的区别只是:不用自己写迭代,ASTRA 把整个循环在 C++ / CUDA 里跑完了。

换成 GPU 版要改什么

对比官方的 GPU 示例 s003_gpu_reconstruction.py,算法配置部分是这样的:

python 复制代码
rec_id = astra.data2d.create('-vol', vol_geom)

# Set up the parameters for a reconstruction algorithm using the GPU
cfg = astra.astra_dict('SIRT_CUDA')
cfg['ReconstructionDataId'] = rec_id
cfg['ProjectionDataId'] = sinogram_id
# Available algorithms:
# SIRT_CUDA, SART_CUDA, EM_CUDA, CGLS_CUDA, FBP_CUDA (see the FBP sample)

# Create the algorithm object from the configuration structure
alg_id = astra.algorithm.create(cfg)

# Run 150 iterations of the algorithm
astra.algorithm.run(alg_id, 150)

和 CPU 版相比只有两处不同:算法名加了 _CUDA 后缀,并且配置里不需要 ProjectorId 。官方文档的说法是:CUDA 算法总是使用 'cuda' 投影器,它对应的是 GPU 加速的 'linear'(Joseph)模型。同一个示例把迭代次数设成 150。

加上取值约束

SIRT 还支持对重建值加上下界(CPU 版和 GPU 版的文档里都有 MinConstraint / MaxConstraint 选项),例如把灰度限制在 0 , 1 0, 1 0,1。官方示例 s013_constraints.py 用的是 GPU 版,50 个角度:

python 复制代码
# Set up the parameters for a reconstruction algorithm using the GPU
cfg = astra.astra_dict('SIRT_CUDA')
cfg['ReconstructionDataId'] = rec_id
cfg['ProjectionDataId'] = sinogram_id
cfg['option'] = {'MinConstraint': 0, 'MaxConstraint': 1}

# Create the algorithm object from the configuration structure
alg_id = astra.algorithm.create(cfg)

# Run 150 iterations of the algorithm
astra.algorithm.run(alg_id, 150)

五、几何怎么写

1. 2D 几何

类型 参数 说明
parallel 探测器间距、单元数、角度 平行束
fanflat 探测器间距、单元数、角度、源到旋转中心距离、旋转中心到探测器距离 平板探测器扇束
parallel_vec / fanflat_vec 单元数、vectors 矩阵 每个角度单独指定位置

几个要点(均来自官方文档):

  • 角度用弧度。
  • 默认像素是边长为 1 的正方形,体数据以原点为中心。 所以探测器间距 1.0 的意思是"和一个像素一样宽",不是 1 mm。要用物理尺寸,就在 create_vol_geom 里额外传 min_x, max_x, min_y, max_y 指定范围,这时所有长度单位要统一。文档同时警告:部分投影器(包括 2D CUDA 投影器)要求像素是正方形。
  • vec 版本的每一行描述一个角度:parallel_vec 是 (dirX, dirY, dX, dY, uX, uY),分别是射线方向、探测器中心、相邻两个探测器单元中心之间的向量;fanflat_vec 把第一项换成射线源位置 (srcX, srcY)。普通几何可以用 astra.geom_2vec(proj_geom) 一键转换。

2. 3D 几何

3D 有 parallel3d、cone 两种常规类型和对应的 vec 版本。官方示例 s005_3d_geometry.py 的锥束部分:

python 复制代码
# ----------
# Cone beam
# ----------

# Circular

# Parameters: width of detector column, height of detector row, #rows, #columns,
#             angles, distance source-origin, distance origin-detector
angles = np.linspace(0, 2 * np.pi, 48, False)
proj_geom = astra.create_proj_geom('cone', 1.0, 1.0, 32, 64, angles, 1000, 0)

# Free

vectors = np.zeros((len(angles), 12))
for i in range(len(angles)):
    # source
    vectors[i, 0] = np.sin(angles[i]) * 1000
    vectors[i, 1] = -np.cos(angles[i]) * 1000
    vectors[i, 2] = 0

    # center of detector
    vectors[i, 3:6] = 0

    # vector from detector pixel (0,0) to (0,1)
    vectors[i, 6] = np.cos(angles[i])
    vectors[i, 7] = np.sin(angles[i])
    vectors[i, 8] = 0

    # vector from detector pixel (0,0) to (1,0)
    vectors[i, 9] = 0
    vectors[i, 10] = 0
    vectors[i, 11] = 1

# Parameters: #rows, #columns, vectors
proj_geom = astra.create_proj_geom('cone_vec', 32, 64, vectors)

示例开头的注释说得很清楚:常规类型假设绕 z 轴做圆轨迹;vec 类型对源和探测器的位置完全不设限制 。每个角度 12 个数:射线源位置、探测器中心、探测器上 ( 0 , 0 ) → ( 0 , 1 ) (0,0)\to(0,1) (0,0)→(0,1) 的向量 u u u、 ( 0 , 0 ) → ( 1 , 0 ) (0,0)\to(1,0) (0,0)→(1,0) 的向量 v v v。C 形臂、螺旋、带偏移和倾斜的真实扫描几何,都靠 cone_vec 描述。

注意示例里 'cone' 的最后两个参数是 1000, 0:源到旋转中心 1000,旋转中心到探测器 0。也就是说探测器放在了经过旋转中心的平面上,探测器间距因此是按旋转中心处的尺度来算的。真实设备的探测器在后方,放大比等于源到探测器距离除以源到旋转中心距离,要么如实填写两段距离和真实像素尺寸,要么把探测器尺寸除以放大比后令第二段距离为 0,两种写法不要混。

另外,2.4 版本加入了柱面探测器几何 cyl_cone_vec,官方标注为实验性功能。

3. 数组形状

这是最容易出错的地方。我在 2.5.0 上逐一验证过:

数据 NumPy 数组形状
2D 体数据 (行数, 列数)
2D 投影数据 (角度数, 探测器单元数)
3D 体数据 (切片数, 行数, 列数)
3D 投影数据 (探测器行数, 角度数, 探测器列数)

3D 投影数据的角度在中间一维 ,和不少其他库的习惯不同。官方 s007 示例里显示单张投影用的就是 proj_data[:, 20, :]。从别的库导入数据时,先 transpose 对齐。

六、投影器怎么选

投影器决定"一条射线经过一个像素时权重是多少",也就是系统矩阵 A A A 的每个元素怎么算。2D 可选的有:

投影器 适用几何 权重模型
line parallel 把射线看成零宽度直线,权重是直线穿过像素的长度
strip parallel 把射线看成与探测器单元等宽的条带,权重是相交面积
linear parallel 逐行或逐列步进,在相邻两个像素之间线性插值(Joseph 模型)
line_fanflat fanflat、fanflat_vec 扇束版的 line
strip_fanflat fanflat 扇束版的 strip;官方注明它没有正确考虑扇束的放大效应
cuda parallel、parallel_vec、fanflat、fanflat_vec GPU 加速的 linear

实际使用时记住两点:

  1. CPU 算法必须指定投影器,GPU 算法只用 cuda。 每个 CPU 投影器只适用于平行束或扇束中的一种。
  2. 模型不同,数值就不同。 我在 CPU 版上对同一个 Shepp--Logan 体模分别用 line、strip、linear 做正投影,三张正弦图并不逐元素相等。GPU 的 cuda 对应的是 linear,所以 CPU 上用 strip 生成的数据,和 GPU 上算出来的也不会完全一致。做对比实验时,正投影和反投影、训练数据和测试数据,要用同一种投影器。

七、FBP 和 FDK

1. 2D FBP

官方示例 s023_FBP_filters.py 演示了三种传滤波器的方法,第一种是标准的 Ram-Lak 滤波,纯 CPU 可跑:

python 复制代码
N = 256

vol_geom = astra.create_vol_geom(N, N)
proj_geom = astra.create_proj_geom(
    'parallel', 1.0, N, np.linspace(0, np.pi, 180, False)
)

phantom_id, P = astra.data2d.shepp_logan(vol_geom)

proj_id = astra.create_projector('strip', proj_geom, vol_geom)
sinogram_id, sinogram = astra.create_sino(P, proj_id)

rec_id = astra.data2d.create('-vol', vol_geom)
cfg = astra.astra_dict('FBP')
cfg['ReconstructionDataId'] = rec_id
cfg['ProjectionDataId'] = sinogram_id
cfg['ProjectorId'] = proj_id
cfg['option'] = {}

# 1. Use a standard Ram-Lak filter
cfg['option']['FilterType'] = 'ram-lak'

alg_id = astra.algorithm.create(cfg)
astra.algorithm.run(alg_id)
rec_RL = astra.data2d.get(rec_id)

CPU 版 FBP 只支持平行束;扇束 FBP 要用 GPU 版 FBP_CUDA。文档列出的 FilterType 包括 ram-lak(默认)、shepp-logan、cosine、hamming、hann 等十几种窗函数,以及 projection、sinogram、rprojection、rsinogram 四种自定义方式:前两种在频域给滤波器,后两种在空间域给卷积核,projection 类是所有角度共用一个滤波器,sinogram 类是每个角度各自一个。滤波器的原理见(一)。

2. 3D FDK(需要 GPU)

(三)讲过的 FDK,在 ASTRA 里是 FDK_CUDA。下面是官方文档 FDK_CUDA 页面给出的 Python 示例原文。我手上的 CPU 版跑不了它,这里只做解读:

python 复制代码
import astra
import matplotlib.pyplot as plt
import numpy as np

# Create geometries
N = 256
N_ANGLES = 360
det_spacing = 1.0
angles = np.linspace(0, 2*np.pi, N_ANGLES)
src_obj_dist = 500
obj_det_dist = 0
proj_geom = astra.create_proj_geom('cone', det_spacing, det_spacing, N, N, angles,
                                   src_obj_dist, obj_det_dist)
vol_geom = astra.create_vol_geom(N, N, N)

# Generate phantom image
phantom_id, phantom = astra.data3d.shepp_logan(vol_geom)

# Create forward projection
sinogram_id, sinogram = astra.create_sino3d_gpu(phantom_id, proj_geom, vol_geom)

# Reconstruct
recon_id = astra.data3d.create('-vol', vol_geom)
cfg = astra.astra_dict('FDK_CUDA')
cfg['ProjectionDataId'] = sinogram_id
cfg['ReconstructionDataId'] = recon_id
algorithm_id = astra.algorithm.create(cfg)

astra.algorithm.run(algorithm_id)

reconstruction = astra.data3d.get(recon_id)
plt.imshow(reconstruction[N//2], cmap='gray')

# Clean up
astra.data3d.delete([sinogram_id, recon_id, phantom_id])
astra.algorithm.delete(algorithm_id)

文档里的几点说明:

  • 支持 cone 和 cone_vec;cone_vec 如果明显偏离圆轨迹,重建会出现伪影,因为 FDK 本身就是为圆轨迹推导的近似算法(见(三));
  • 选项 ShortScan 打开 Parker 加权,用于不足 360° 的扫描,要求角度范围至少为 180° 加两倍扇角;
  • FDK 不支持各向异性体素,会直接抛异常。

八、把 ASTRA 当成矩阵或线性算子

做研究时,经常不想用库里自带的算法,而是只要 A A A 和 A T A^T AT,自己写优化或嵌进网络。ASTRA 有三种方式。

1. 直接导出稀疏系统矩阵(仅 2D CPU 投影器)

官方示例 s009_projection_matrix.py:

python 复制代码
vol_geom = astra.create_vol_geom(256, 256)
proj_geom = astra.create_proj_geom(
    'parallel', 1.0, 384, np.linspace(0, np.pi, 180, False)
)

# For CPU-based algorithms, a 'projector' object specifies the projection
# model used. In this case, we use the 'line' model.
proj_id = astra.create_projector('line', proj_geom, vol_geom)

# Generate the projection matrix for this projection model.
# This creates a matrix W where entry w_{i,j} corresponds to the
# contribution of volume element j to detector element i.
matrix_id = astra.projector.matrix(proj_id)

# Get the projection matrix as a Scipy sparse matrix.
W = astra.matrix.get(matrix_id)

# Manually use this projection matrix to do a projection:
phantom_id, P = astra.data2d.shepp_logan(vol_geom)
s = W.dot(P.ravel())
s = np.reshape(s, (len(proj_geom['ProjectionAngles']), proj_geom['DetectorCount']))

plt.imshow(s)
plt.show()

# Each row of the projection matrix corresponds to a detector element.
# Detector t for angle p is for row 1 + t + p*proj_geom.DetectorCount.
# Each column corresponds to a volume pixel.
# Pixel (x,y) corresponds to column 1 + x + y*vol_geom.GridColCount.

astra.projector.delete(proj_id)
astra.data2d.delete(phantom_id)
astra.matrix.delete(matrix_id)

这里的 W W W 就是(二)里 A x = b Ax=b Ax=b 的 A A A:行数是 180 × 384 = 69120 180 \times 384 = 69120 180×384=69120,列数是 256 × 256 = 65536 256 \times 256 = 65536 256×256=65536,W.dot(P.ravel()) 就是正投影。示例末尾的注释说明了行列的排列顺序(注释里的下标是从 1 开始的 MATLAB 写法)。

文档的提醒是:这个功能只对 2D CPU 投影器实现,大几何下矩阵会占用大量内存。教学和小规模实验很好用,临床尺寸的 3D 就别想了。

2. OpTomo:可以直接交给 SciPy 求解器的线性算子

官方示例 s017_OpTomo.py(这个示例用 'cuda' 投影器,需要 GPU):

python 复制代码
vol_geom = astra.create_vol_geom(256, 256)
proj_geom = astra.create_proj_geom(
    'parallel', 1.0, 384, np.linspace(0, np.pi, 180, False)
)

# As before, create a sinogram from a phantom
phantom_id, P = astra.data2d.shepp_logan(vol_geom)
proj_id = astra.create_projector('cuda', proj_geom, vol_geom)

# construct the OpTomo object
W = astra.OpTomo(proj_id)

sinogram = W * P
sinogram = sinogram.reshape([180, 384])

plt.imshow(P)
plt.figure()
plt.imshow(sinogram)

# Run the lsqr linear solver
output = scipy.sparse.linalg.lsqr(W, sinogram.ravel(), iter_lim=150)
rec = output[0].reshape([256, 256])

plt.figure()
plt.imshow(rec)
plt.show()

# Clean up
astra.projector.delete(proj_id)
astra.data2d.delete(phantom_id)

astra.OpTomo 把投影器包装成 SciPy 的 LinearOperator:W * P 是正投影,W.T 对应反投影,因此能直接传给 scipy.sparse.linalg.lsqr 这类迭代求解器,而不用显式构造矩阵。注意 W * P 返回的是一维向量,示例里手动 reshape 成了 [180, 384]。

3. direct_FP / direct_BP:直接作用在张量上(2.5.0 新增)

官方文档给出的接口:

python 复制代码
astra.projector.direct_FP(projector_id, volume_array, out=projection_array)
astra.projector.direct_BP(projector_id, projection_array, out=volume_array)

按 2.5.0 的发布说明和文档:这两个函数不需要再创建 ASTRA 的数据对象和算法对象,直接接受支持 DLPack 标准的数组(NumPy、PyTorch、JAX 等),2D 投影器的 DLPack 支持也是 2.5.0 才加上的。GPU 上的张量只能配 'cuda' 投影器;CPU 数组可以配任何投影器。输出数组必须用关键字参数 out= 传入。

对做深度学习 CT 的人来说,这意味着正投影、反投影可以直接作用在 GPU 张量上,省掉来回拷贝。但 ASTRA 本身不提供自动求导 :要让梯度穿过投影算子,需要把 direct_FP 作为前向、direct_BP 作为反向,自己封装成框架里的自定义算子,或者使用已经封装好的上层库。

九、最容易踩的坑

  1. CPU 版没有 3D,也没有 cuda 投影器。 报 Unknown Projector2D type 或 Unknown algorithm type,先检查 astra.use_cuda()。
  2. CPU 算法必须传 ProjectorId。 漏了它,algorithm.create 这一步就会报错;GPU 算法则不需要。
  3. 数组形状。 2D 正弦图是(角度,探测器),3D 投影是(探测器行,角度,探测器列),3D 体数据是(切片,行,列)。
  4. 角度单位是弧度。 平行束扫 [ 0 , π ) [0, \pi) [0,π) 时用 endpoint=False,避免 0 和 π \pi π 两个方向重复。
  5. 长度单位。 默认一个像素边长为 1,探测器间距按像素计;锥束里 origin_detector_distance 设成 0 时,探测器尺寸要换算到旋转中心处。
  6. 对象要手动释放。 数据对象占内存,算法对象可能还占着显存,用完 delete。
  7. 生成数据和重建用的模型要一致。 用 strip 生成仿真数据、再用 cuda(即 linear)重建,等于人为引入了模型不一致,评估结果会受影响。

十、小结

适合 不适合
需要快速的 2D/3D 正投影、反投影 想要现成的 TV、PnP 等正则化重建
锥束、C 形臂等非标准几何(vec 系列) 想要开箱即用的自动求导
拿 FBP、FDK、SIRT、CGLS 当基线 没有 NVIDIA GPU 却要做 3D
小规模实验时导出系统矩阵 闭源商业产品(GPLv3)

所以 ASTRA 更像一块底层砖:几何和投影交给它,上面的优化、正则化和网络自己搭,或者交给建立在它之上的框架。基础系列后面会讲的 ODL,就是用"算子"的抽象把 ASTRA 这类后端包装起来,LPD 的代码正是这样写的。

下一篇按排期回到论文精读:RED-CNN,2017 年低剂量 CT 去噪的经典网络,正好接上(四)讲过的低剂量噪声模型和 PSNR / SSIM。

ASTRA 和其他 CT 工具包、论文和数据集,我整理在了一个开源列表里,可以按方向筛选、支持中文搜索:

参考文献

  1. van Aarle W, Palenstijn W J, Cant J, et al. Fast and flexible X-ray tomography using the ASTRA toolbox. Optics Express, 2016, 24(22): 25129--25147.
  2. ASTRA Toolbox 官方文档(2.5.0):https://www.astra-toolbox.com/docs/

如果这篇对你有帮助,欢迎收藏,有问题可以在评论区讨论。

相关推荐
Rabitebla1 小时前
【系统性学习笔记】Linux 权限篇(二):文件属性、权限修改与目录权限的底层逻辑
linux·c++·笔记·学习·算法
远离UE42 小时前
半拉格朗日学习笔记 动力学 实时渲染
人工智能·算法
小郎君6582 小时前
MPTT 不是万能编号:一棵百万节点树的增删改与批量事务
算法
FPGA小徐2 小时前
【一生一芯 / PA】AM 核心文件逐行解析(上):从 start.S 到 gpu.c
前端·算法
Tairitsu_H3 小时前
[动态规划#1] 子数组 | 最大子数组和 | 环形子数组的最大和 | 乘积最大子数组 | 乘积为正数的最长子数组
算法·动态规划
凯瑟琳.奥古斯特3 小时前
动态调优IoU提升OCR文本合并精度
开发语言·python·算法
陈嘿萌3 小时前
CVPR 2026|TherA:让可见光照片“懂冷热”,用文字和参考图控制红外生成
图像处理·人工智能·深度学习·计算机视觉·学术研究与理论基础
布莱克6053 小时前
A* 算法详解:从原理到实现
开发语言·c++·算法
段一凡-华北理工大学4 小时前
高炉炼铁机器视觉与智能识别十八讲~系列文章15:可靠性篇:防误报、容错与识别系统的鲁棒性设计
人工智能·算法·机器学习·高炉大模型·高炉机器视觉·高炉炼铁模型开发