Skip to main content

A CUDA-based library for computed tomography (CT) projection and reconstruction with differentiable operators

Project description

diffct: Differentiable Computed Tomography Operators

License DOI PyPI version Documentation CI/CD Ask DeepWiki

A high-performance, CUDA-accelerated library for circular orbits CT reconstruction with end-to-end differentiable operators, amplitude- calibrated analytical FBP / FDK, and a separable-footprint projector family for cell-integrated forward models. Built for optimization and deep-learning integration.

โญ Please star this project if you find it is useful!

๐Ÿ”€ Branches

Main Branch (Stable, PyPI)

This is the stable release branch supporting circular-orbit CT reconstruction. Every versioned release on PyPI comes from main. See CHANGELOG.md for the full release history.

Dev Branch (arbitrary trajectories)

The dev branch is the arbitrary-trajectory evolution of the library. Kernels take per-view (src_pos, det_center, det_u_vec[, det_v_vec]) arrays instead of closed-form sdd / sid / beta scalars, so you can reconstruct along spiral, saddle, sinusoidal, or any user-supplied trajectory. It is kept in sync with main's 1.2.11 analytical reconstruction overhaul (ramp filter, weighted backproject, tests, benchmark suite); the only thing currently deferred from main is the 1.3.0 separable-footprint (SF) backends, because generalising the trapezoidal footprint math to arbitrary trajectories is a separate research effort.

โš ๏ธ Note: The dev branch is under active development and is not published to PyPI. If you find any bugs please raise an issue.

โœจ Features

  • Fast: CUDA-accelerated forward and backward projectors with Numba CUDA kernels, plus dedicated voxel-driven FBP / FDK gather kernels with coalesced memory writes.
  • Differentiable: End-to-end gradient propagation via torch.autograd. Every projector / backprojector pair is a byte-accurate adjoint, verified by tests/test_adjoint_inner_product.py and torch.autograd.gradcheck in tests/test_gradcheck.py.
  • Analytical reconstruction: Amplitude-calibrated FBP / FDK pipelines via ramp_filter_1d (Ram-Lak / Hann / Hamming / cosine / Shepp-Logan windows, configurable padding and physical sample_spacing), fan_cosine_weights / cone_cosine_weights, parker_weights, angular_integration_weights, and parallel_weighted_backproject / fan_weighted_backproject / cone_weighted_backproject. A unit-density phantom reconstructs back to amplitude 1 without any manual scaling.
  • Separable-footprint projectors: Optional backend="sf" (fan) and backend="sf_tr" / "sf_tt" (cone) selectors on every FanProjectorFunction / ConeProjectorFunction call expose voxel-driven SF projectors (Long-Fessler-Balter, IEEE TMI 2010). The matched SF forward model is a cell-integrated alternative to ray-sampled Siddon and is useful when an iterative or learned pipeline wants a mass-conserving forward operator. The analytical FBP / FDK gather (backend="sf" on fan_weighted_backproject / cone_weighted_backproject) uses LEAP's chord-weighted matched-adjoint form (projectors_SF.cu); on standard Shepp-Logan phantoms it reaches Siddon-VD parity in both amplitude and MSE (within ~1 %). See the Core Algorithm section below for when SF is worth the extra ~2-3x forward cost.
  • Tested: 66 pytest tests covering adjoint identity, gradcheck, smoke, FBP / FDK accuracy per geometry, detector / center offsets, and 29 ramp-filter window cases. Opt-in 27-case pytest-benchmark perf suite under tests/benchmarks/ for before/after regression tracking.

๐Ÿ“ Supported Geometries

  • Parallel Beam: 2D parallel-beam geometry
  • Fan Beam: 2D fan-beam geometry
  • Cone Beam: 3D cone-beam geometry

๐Ÿ”ฌ Core Algorithm

At the heart of every diffct projector / backprojector pair is Siddon's algorithm (Siddon 1985) โ€” a ray-driven integer DDA that walks each ray through the voxel grid, stepping forward only at voxel boundaries and giving exact parametric intersection lengths in O(N) per ray.

The classical Siddon samples the cell value at each step (nearest-neighbor). diffct instead uses bilinear (2D) / trilinear (3D) interpolation of the surrounding voxel corners at every sample point. The same interpolation appears twice in the analytical pipeline, both in the Siddon forward projector walking the image, and in the voxel-driven FBP / FDK gather backprojector (*_weighted_backproject) sampling the filtered sinogram at each voxel's projected detector footprint. The matched autograd adjoint uses exactly the same weights as a scatter-add, giving a byte- accurate adjoint verified by <Ax, y> โ‰ˆ <x, A^T y> at float32 precision (see tests/test_adjoint_inner_product.py).

Why this choice. Interpolation weights are continuous in the voxel values, so โˆ‚sinogram / โˆ‚voxel is well-defined everywhere and torch.autograd flows gradients back through the projector without surrogate tricks or straight-through estimators. Nearest- neighbor Siddon would give piecewise-constant outputs with zero gradient on the interior of each cell โ€” fine for one-shot FBP / FDK, useless for iterative or learned reconstruction. A single integer-DDA kernel also keeps the forward and adjoint code structurally identical across parallel, fan, and cone, which is what makes the adjoint byte-accurate in the first place.

The trade-off: slight blur. Bi / trilinear interpolation behaves like a mild low-pass filter on top of the voxel grid, so the effective reconstruction MTF rolls off a bit earlier than a cell- integrated projector would give. To recover that resolution the sharpest knob is the ramp filter window:

  • Ramp filter window: ramp_filter_1d(window=...) picks the frequency-domain apodization applied on top of the ramp. In order of sharpness: "ram-lak" > "hamming" > "hann". Sharper windows recover more high frequency content at the cost of more visible ringing / noise. This is by far the biggest knob on the reconstruction MTF at typical CBCT geometries.

Separable-footprint (SF) backend โ€” honest scope. The optional backend="sf" (fan) and "sf_tr" / "sf_tt" (cone) selectors on fan_weighted_backproject and cone_weighted_backproject replace the default bilinear voxel-driven gather with a gather that integrates the filtered sinogram over each voxel's projected trapezoidal footprint, in LEAP's chord-weighted matched-adjoint form (projectors_SF.cu). What you actually get, verified on Shepp-Logan and the walnut dataset:

  • Amplitude: matches Siddon VD to within ~1 % at nominal, sub-nominal (voxel = 0.5 * detector_pitch * sid / sdd) and mildly supra-nominal voxel grids. Both backends are consistent with the same analytical FBP / FDK scale constant on unit- density phantoms.
  • MSE / SSIM: SF is typically a whisker better than VD (fractions of a percent) on standard phantoms; do not expect a noticeable MSE win from the backend alone.
  • Visible MTF: at the ~1.5-3x CBCT magnifications used in fan / cone examples, SF and VD produce visually indistinguishable edge profiles. Plot a line through a sharp edge and the two curves trace each other. The "SF is sharper at sub-nominal" claim you see in the SF literature is real in the extreme-sub-nominal regime (voxel much smaller than one detector bin) but invisible at the geometries in the shipped examples.

So why ship the SF backend at all? Because the forward side is the actually interesting one:

  • SF forward is mass-conserving per voxel โ€” a single voxel's contribution is spread across the correct multi-bin footprint instead of concentrated at one bin, which matters for iterative reconstruction, learned priors, and any loss that compares sinograms directly. Siddon-bilinear forward is a point sampler and only conserves mass statistically.
  • SF's matched adjoint is byte-accurate (verified by tests/test_adjoint_inner_product.py), so gradients flow correctly through the cell-integrated forward model.
  • The SF-matched autograd adjoint and the LEAP chord-weighted FBP gather live in different kernels: the first is used by FanProjectorFunction / ConeProjectorFunction on the backward pass, the second is used by fan_weighted_backproject / cone_weighted_backproject when you pick the SF backend. Both are exposed; picking backend="sf" on both sides gives you a consistent cell-integrated pipeline.

Bottom line. If you just want the cleanest FBP / FDK of a Shepp-Logan or a walnut on typical CBCT geometry, stay on backend="siddon" and tune the ramp window. Switch to backend="sf" / "sf_tr" / "sf_tt" when you care about the forward model being cell-integrated โ€” iterative reconstruction, learned priors, sinogram-level losses, or comparing against a LEAP baseline. Concrete usage of both paths is in examples/fbp_fan.py, examples/fdk_cone.py and examples/realdata_walnut_fdk.py.

๐Ÿฅœ Real-data Example

Cone-beam FDK reconstruction of a real walnut from the Helsinki industrial CBCT scanner (Meaney 2022, Zenodo 10.5281/zenodo.6986012, CC-BY 4.0). The shipped sinogram at examples/data/walnut_cone.npz is a 241-view, 256x256-per-view, flat-field-normalised and logged subset of the original 721 x 2368 x 2240 uint16 acquisition (binned 8x, cropped 256x256, float16, ~25 MB). A sample reconstruction montage is shipped at examples/data/walnut_reco.png. Run the full analytical FDK pipeline with

python examples/realdata_walnut_fdk.py

The same analytical wrappers (cone_cosine_weights, ramp_filter_1d, angular_integration_weights, cone_weighted_backproject) that reconstruct Shepp-Logan phantoms in fdk_cone.py are used here with no algorithmic changes โ€” only the geometry is swapped for the one stored in the .npz. The example reconstructs at half the nominal voxel size on a 512ยณ grid with backend="sf_tr" and a Hamming ramp window; you can switch to backend="siddon" and get a visually equivalent result (the backend choice is a forward-model preference here, not a reconstruction-sharpness knob). See examples/data/NOTICE for the full attribution and the regeneration procedure.

๐Ÿงฉ Code Structure

diffct/
โ”œโ”€โ”€ diffct/
โ”‚   โ”œโ”€โ”€ __init__.py            # public API re-exports
โ”‚   โ””โ”€โ”€ differentiable.py      # CUDA kernels, autograd Functions,
โ”‚                              # analytical helpers, SF backends
โ”œโ”€โ”€ examples/                  # circular-orbit example scripts
โ”‚   โ”œโ”€โ”€ fbp_parallel.py
โ”‚   โ”œโ”€โ”€ fbp_fan.py             # with Parker short-scan switch
โ”‚   โ”œโ”€โ”€ fdk_cone.py            # with Parker short-scan switch
โ”‚   โ”œโ”€โ”€ iterative_reco_parallel.py
โ”‚   โ”œโ”€โ”€ iterative_reco_fan.py
โ”‚   โ”œโ”€โ”€ iterative_reco_cone.py
โ”‚   โ”œโ”€โ”€ realdata_fbp_parallel.py  # synthetic real-data pipeline
โ”‚   โ”œโ”€โ”€ realdata_fbp_fan.py       #   (Beer-Lambert + Poisson + -log)
โ”‚   โ”œโ”€โ”€ realdata_fdk_cone.py
โ”‚   โ”œโ”€โ”€ realdata_walnut_fdk.py    # real Helsinki walnut CBCT data
โ”‚   โ””โ”€โ”€ data/
โ”‚       โ”œโ”€โ”€ walnut_cone.npz    # ~25 MB preprocessed walnut sinogram
โ”‚       โ”œโ”€โ”€ walnut_reco.png    # sample FDK reconstruction montage
โ”‚       โ”œโ”€โ”€ preprocess_walnut.py  # regenerator from Zenodo source
โ”‚       โ””โ”€โ”€ NOTICE             # CC-BY 4.0 attribution
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ test_*.py              # adjoint / gradcheck / accuracy /
โ”‚   โ”‚                          # offsets / weights / ramp-filter
โ”‚   โ””โ”€โ”€ benchmarks/            # opt-in pytest-benchmark perf suite
โ”œโ”€โ”€ docs/                      # Sphinx documentation sources
โ”œโ”€โ”€ pyproject.toml             # project metadata
โ”œโ”€โ”€ pytest.ini
โ”œโ”€โ”€ CHANGELOG.md               # Keep-a-Changelog release notes
โ”œโ”€โ”€ README.md
โ””โ”€โ”€ LICENSE

๐Ÿš€ Quick Start

Prerequisites

Installation

CUDA 12 (Recommended):

# Create and activate conda environment
conda create -n diffct python=3.12
conda activate diffct

# Install CUDA (here 12.8.1 as example) PyTorch, and Numba
conda install nvidia/label/cuda-12.8.1::cuda-toolkit

# Install Pytorch, you can find the commend here: https://pytorch.org/get-started/locally/

# Install Numba with CUDA 12
pip install numba-cuda[cu12]

# Install diffct
pip install diffct
CUDA 13 Installation
# Create and activate conda environment
conda create -n diffct python=3.12
conda activate diffct

# Install CUDA (here 13.0.2 as example) PyTorch, and Numba
conda install nvidia/label/cuda-13.0.2::cuda-toolkit

# Install Pytorch, you can find the commend here: https://pytorch.org/get-started/locally/

# Install Numba with CUDA 13
pip install numba-cuda[cu13]

# Install diffct
pip install diffct
CUDA 11 Installation
# Create and activate conda environment
conda create -n diffct python=3.12
conda activate diffct

# Install CUDA (here 11.8.0 as example) PyTorch, and Numba
conda install nvidia/label/cuda-11.8.0::cuda-toolkit

# Install Pytorch, you can find the commend here: https://pytorch.org/get-started/locally/

# Install Numba with CUDA 11
pip install numba-cuda[cu11]

# Install diffct
pip install diffct

Running the tests

pytest tests/ -q                             # 66 tests, ~15 s
pytest tests/benchmarks/ --benchmark-only    # opt-in perf suite, requires pytest-benchmark

๐Ÿ“ Citation

If you use this library in your research, please cite:

@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}
}

๐Ÿ“„ License

This project is licensed under the Apache 2.0 - see the LICENSE file for details.

๐Ÿ™ Acknowledgements

This project was highly inspired by:

  • PYRO-NN
  • geometry_gradients_CT
  • LEAP (LLNL / Hyojin Kim et al.) โ€” the LEAP chord-weighted matched-adjoint form in projectors_SF.cu is the reference implementation that the 1.3.1 analytical SF backprojection kernels (_fan_2d_sf_fbp_backproject_kernel, _cone_3d_sf_tr_fdk_backproject_kernel, _cone_3d_sf_tt_fdk_backproject_kernel) in diffct are ported from. Apache 2.0-licensed, big thanks to the LEAP team.

Issues and contributions are welcome!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

diffct-1.3.1.tar.gz (26.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

diffct-1.3.1-py2.py3-none-any.whl (44.9 kB view details)

Uploaded Python 2Python 3

File details

Details for the file diffct-1.3.1.tar.gz.

File metadata

  • Download URL: diffct-1.3.1.tar.gz
  • Upload date:
  • Size: 26.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for diffct-1.3.1.tar.gz
Algorithm Hash digest
SHA256 288d22e3575d59772082e5ef6e458e54e1d6673c7394deee83404a6ba2977c4d
MD5 825af294eda8a8be5ef1b5989f143259
BLAKE2b-256 8a875f741648b712902df3bc9132a9088f9dbf86f18d83b929490a3ee275e71e

See more details on using hashes here.

File details

Details for the file diffct-1.3.1-py2.py3-none-any.whl.

File metadata

  • Download URL: diffct-1.3.1-py2.py3-none-any.whl
  • Upload date:
  • Size: 44.9 kB
  • Tags: Python 2, Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.11

File hashes

Hashes for diffct-1.3.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 f4ba9087ce8c9611e62b5ac301d764d3fedcef9e8f9a6a77442e27d122e0751f
MD5 043b26445cde3eecd56cb5e7418a73f0
BLAKE2b-256 dd886562b02311bfbcd89dbb9eff26749af270c96ee51faf5915b13e6b317d5f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page