English · 简体中文
Differentiable CUDA projectors for CT: arbitrary trajectories, multi-GPU, multi-node, geometry gradients.
Intro video (MP4) · Technical report · DOI
Install · Quickstart · Features · Performance · Examples · Citation
Version 2.0 replaces the circular-orbit API of 1.x with per-view trajectories. Code written for 1.x needs changes; see the migration notes. For Apple Silicon, use the MLX port by Linda-Sophie Schneider.
This README describes the source checkout. Features listed under Unreleased require installation from source.
To our knowledge, diffct is the only open-source GPU CT library that combines arbitrary per-view trajectories, autograd through the volume, first-order gradients through the acquisition geometry, and built-in multi-GPU and multi-node execution. We compared it with LEAP, TIGRE, ASTRA/tomosipo, DiffDRR and others in October 2026.
- Arbitrary trajectories. Each view has its own ray direction or source, detector centre and detector axes. Circular, helical, saddle, sinusoidal, random and calibrated scans use the same code. Trajectory tensors with
requires_grad=Truereceive geometry gradients for calibration. - Curved detectors.
ParameterizedSurfacedefines native arc, cylindrical or arbitrary pixels from global coordinates and explicit parameters, sampled in bounded view/pixel batches. Legacydetector_surface(u, v)callbacks remain available. Projection and matched backprojection support first-order surface gradients. See the surface API. - Matched operators.
project()andbackproject()form an exact adjoint pair for the cell-constant Siddon model. Both support PyTorch autograd, with volume/sinogram gradients and Hessian-vector products. - Large volumes. Automatic spatial, view and pixel blocks with bounded transfer buffers control working memory.
project_into/backproject_intoaccept caller-owned tensor or disk stores; tensor autograd keeps complete data on its input device. See chunked execution. - Multi-GPU and multi-node. Use
devices=[0, 1, 2, 3]in one process, or torchrun and NCCL across ranks. Views, space and hybrid partitions control replication of volumes and projections. Speedup depends on workload and communication costs. - Precision and training. Explicit native/PyTorch backends, FP32/FP64 modes, per-axis voxel spacing, leading batch/channel dimensions and data
torch.functransforms. The portable path supports CPU and PyTorch CUDA/ROCm devices; see execution contracts. - Solver API.
cgls,sirtandtv_reconstructionreport residual history, tolerances and callbacks, with scan/data fingerprints and complete checkpoint state.cgls_intokeeps state in bounded mapped blocks. - Differentiable FBP and FDK.
fbp,fdkand approximatehelical_fdkcompose the public filters, weights and analytical backprojectors. Their backward kernels transpose the actual interpolation stencil; custom filters and weights can be learned. See the analytical guide.
Capabilities, limits and isocenter rules: docs/REFERENCE.md.
For the native path, install NVIDIA CUDA PyTorch first, then diffct with the matching CUDA major extra:
pip install "diffct[cu12]" # PyTorch built for CUDA 12
pip install "diffct[cu13]" # PyTorch built for CUDA 13The extra installs the CUDA compiler libraries Numba needs. For CPU or PyTorch ROCm execution, install the appropriate PyTorch build and plain pip install diffct, then select backend="torch". Importing diffct and using this backend require no CUDA runtime; Numba CUDA initializes when native execution is used. Plain installation also works for native execution when a compatible CUDA Toolkit is already present.
From source, with the examples:
git clone https://github.com/sypsyp97/diffct.git
cd diffct && pip install -e ".[cu12]"
python examples/quickstart.py # prints adjoint mismatch and gradients for each beamNotes on CUDA versions
- Numba CUDA imports
numpy.row_stack, which NumPy 2.5 removes. Keepnumpy<2.5. - Keep NVVM and NVJitLink compatible with the CUDA libraries that PyTorch loads. A newer NVVM with an older NVJitLink fails when the kernels compile.
- A tested CUDA 13 set:
pip install "numpy<2.5" "numba-cuda[cu13]" "cuda-toolkit[cccl,cudart,nvrtc,nvvm]==13.0.2" "nvidia-nvjitlink<13.1". - A tested CUDA 12 set: PyTorch 2.10 (cu126) with
numba-cuda[cu12]0.30.4 on NVIDIA driver 535.
Create the acquisition once. Then call project() and backproject().
import torch
from diffct import Projector, spiral_trajectory_3d
trajectory = spiral_trajectory_3d(360, sid=320.0, sdd=512.0, z_range=40.0, n_turns=1.0, device="cpu")
A = Projector(trajectory, volume_shape=(128, 128, 128), detector_shape=(384, 256), detector_spacing=0.8)
volume = torch.rand(A.volume_shape, device="cuda")
sinogram = A.project(volume) # (360, 384, 256) line integrals
adjoint = A.backproject(sinogram) # matched adjoint A^T
x = torch.zeros_like(volume, requires_grad=True)
loss = 0.5 * (A.project(x) - sinogram).square().sum()
loss.backward() # x.grad = A^T (A x - y)Custom or calibrated trajectories. Pass calibrated tensors directly instead of a generator; no circular fit is needed. See REFERENCE.
Geometry gradients. Reuse one projector with A.project(volume, trajectory=current) or A.backproject(sinogram, trajectory=current). Circular helpers accept learnable angle tensors, and transform_trajectory applies differentiable rigid transforms. Omitting the override keeps the constructor trajectory. See REFERENCE.
3D parallel beams. Use circular_trajectory_3d_parallel(...) and beam="parallel3d". The PyTorch backend provides matched projection and backprojection on CPU and CUDA.
Native volume layout. For unbatched, flat-detector cone operations, set native_volume_layout=True to avoid volume layout copies. This applies to the native full-CUDA path on one compute device. Shapes stay (D, H, W); the returned strides follow the kernel layout. The default is False. See chunked execution for limits.
Several GPUs and nodes. See REFERENCE and DISTRIBUTED.md.
A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8, devices=[0, 1, 2, 3])Volumes larger than GPU memory. Keep the arrays on CPU. The same calls use CUDA tiles automatically; CUDA inputs retain the full-volume path when its estimated working set fits. Explicit limits are optional:
A = Projector(trajectory, (128, 128, 128), (384, 256), detector_spacing=0.8)
volume = torch.ones(A.volume_shape, device="cpu", requires_grad=True)
sinogram = A.project(volume) # CPU output, CUDA computation
sinogram.square().mean().backward() # CPU volume.grad
# Optional: volume_chunk_shape=(32, 64, 64), view_chunk_size=16Run python examples/chunked_reconstruction.py for CPU-backed CGLS and measured
CUDA allocation. Chunking preserves the Siddon model, with float32 summation
differences; smaller blocks can increase transfer and launch overhead. Full
tensor arrays and iterative state still need enough host RAM. For disk-backed
state and sharded checkpoints, run
python examples/disk_reconstruction.py --output disk-run; for fixed rank-local
volume slabs use --partition space under torchrun. Run
python examples/benchmark_execution.py to compare schedules and copy payloads.
| 512³ volume | 1 GPU | 4 GPUs, 1 node | 8 GPUs, 2 nodes |
|---|---|---|---|
| Forward projection | 566.79 ms | 150.76 ms (3.8×) | 81.58 ms (6.9×) |
| Adjoint (backprojection) | 1060.77 ms | 279.37 ms (3.8×) | 224.04 ms (4.7×) |
| CGLS iteration | 1753.73 ms | 468.09 ms (3.7×) | 337.39 ms (5.2×) |
diffct 2.4.3, FP32, A100 64 GB GPUs; circular cone beam, 360 views, 1024×768 detector, pitch 1.25. Default memory settings, without an imposed allocator cap.
Projection times are medians of seven samples after three warmups. CGLS uses three ten-iteration solves after a two-iteration warmup; the median includes initialization and is divided by ten. Timings use synchronized wall time and the slowest rank. Raw samples and configuration: scaling.json.
Run the same workload:
python examples/benchmark_scaling.py --output one.json
torchrun --standalone --nproc-per-node=4 examples/benchmark_scaling.py --expected-nodes 1 --output four.jsonTV denotes TV-regularized iterative reconstruction; Adam is the optimizer. Figure labels distinguish the method from its optimizer.
Measured walnut. 240 measured views, circular cone beam, 256³: FDK (Hann window), SIRT (200 iterations), CGLS (20), TV (300, weight 0.3); axial and coronal centre slices.
python examples/walnut_reconstruction.py --figure out.pngSimulated helical scan of the walnut. The FDK walnut volume as ground truth, 720 views, 1% noise, 256³. PSNR: FDK 26.67 dB, CGLS (30 iterations) 34.13 dB, SIRT (200) 34.52 dB, TV (200, weight 1.0) 37.77 dB.
python examples/walnut_reconstruction.py --algorithms --save-volume walnut256.npy
python examples/iterative_reconstruction.py --size 256 --views 720 --trajectory helical --noise 0.01 --phantom walnut256.npy --figure out.pngWalnut data: Meaney 2022, Zenodo 6986012, CC BY 4.0; see examples/data/NOTICE.
quickstart.py, curved_detector.py, chunked_reconstruction.py, disk_reconstruction.py, benchmark_execution.py, benchmark_trajectory_admission.py, benchmark_surface_geometry.py, benchmark_gpu_overheads.py, analytical_reconstruction.py, learnable_reconstruction.py, helical_fdk.py, iterative_reconstruction.py, walnut_reconstruction.py, geometry_calibration.py, benchmark_projector.py, benchmark_scaling.py, plot_trajectory.py, and the Slurm template slurm/multi_node.sbatch. Launch modes and distributed-loss rules are in examples/README.md.
| Document | Contents |
|---|---|
| Precision, training and reconstruction | Backend modes, batch/spacing contracts, analytical storage, hybrid loss scaling and solver checkpoints |
| Trajectory guide | Trajectory tuples and Projector usage |
| docs/REFERENCE.md | Capabilities and limits, custom trajectories, geometry gradients, multi-GPU details, geometry and units |
| docs/DISTRIBUTED.md | Execution/memory choices, distributed loss rules, Slurm and cross-node checks |
| docs/MIGRATION.md | Moving from the circular-only API |
| docs/video/README.md | How the intro video is rendered |
| CHANGELOG.md | Release history |
Run the test suite on a CUDA host with python -m pytest tests/ -q.
If you use this library in your research, please cite the software and the technical report:
@software{diffct2025,
author = {Yipeng Sun},
title = {diffct: Differentiable Computed Tomography Reconstruction with CUDA},
year = 2025,
publisher = {Zenodo},
doi = {10.5281/zenodo.14999333},
url = {https://doi.org/10.5281/zenodo.14999333}
}
@article{202605.1446,
doi = {10.20944/preprints202605.1446.v2},
url = {https://doi.org/10.20944/preprints202605.1446.v2},
year = 2026,
month = {Oct},
publisher = {Preprints},
author = {Yipeng Sun and Linda-Sophie Schneider and Chengze Ye and Andreas Maier},
title = {diffct: Differentiable CT Operators from Circular Orbits to Arbitrary Trajectories},
journal = {Preprints}
}Apache 2.0, see LICENSE. The project draws on PYRO-NN and geometry_gradients_CT. Issues and pull requests are welcome. The walnut data are CC BY 4.0 (Meaney 2022); see NOTICE.


