Quickstart¶
This page shows the shortest path from a fresh install to a forward and back
projection with parallelproj. It uses the built-in
DemoPETScannerGeometry so that the scanner setup is a single line; for
a real scanner you would replace it with
RegularPolygonPETScannerGeometry (or build a custom geometry), but the
rest of the workflow is identical.
Make sure parallelproj is installed first (see Installation).
A minimal PET sinogram projection¶
The example below uses the regular-polygon route, which is the right choice for scanners with cylindrical symmetry and a single layer of LOR endpoints:
scanner geometry → Michelogram (axial plane layout) →
RegularPolygonPETLORDescriptor → RegularPolygonPETProjector.
(General block / panel scanners use the equal-block route instead –
ModularizedPETScannerGeometry → EqualBlockPETLORDescriptor →
EqualBlockPETProjector; see the API reference.)
import array_api_compat.numpy as xp # the array backend (swap for .torch / .cupy)
import matplotlib.pyplot as plt
from parallelproj.pet_scanners import DemoPETScannerGeometry
from parallelproj.pet_lors import Michelogram, RegularPolygonPETLORDescriptor
from parallelproj.projectors import RegularPolygonPETProjector
# the device used for every array we create
dev = "cpu" # or "cuda"
# demo cylindrical PET scanner (here trimmed to 4 rings)
# describes world coordinates of LOR endpoints
scanner = DemoPETScannerGeometry(xp, dev, num_rings=4)
# line of response descriptor that describes how order pairs of LOR endpoints
# including options for radial trimming and axial compression
lor_desc = RegularPolygonPETLORDescriptor(
scanner,
Michelogram(scanner.num_rings, max_ring_difference=3, span=1),
radial_trim=50,
)
# non-TOF projector
proj = RegularPolygonPETProjector(
lor_desc, img_shape=(100, 100, 8), voxel_size=(4.0, 4.0, 4.0)
)
# a simple test image: a hot box in the centre
img = xp.zeros(proj.in_shape, dtype=xp.float32, device=dev)
img[50:90, 10:40, :] = 1.0
img_fwd = proj(img) # forward projection: image -> sinogram
back = proj.adjoint(img_fwd) # back projection (adjoint); proj.H(sino) works too
print("image shape: ", img.shape)
print("sinogram shape: ", img_fwd.shape)
fig = plt.figure(figsize=(8, 8), tight_layout=True)
ax = fig.add_subplot(111, projection="3d")
proj.show_geometry(ax)
plt.show()
That is the whole core API: a projector is a
LinearOperator, so proj(img) forward projects, proj.adjoint(sino)
(equivalently proj.H(sino)) back projects, and the two are exact adjoints of
each other – everything else in the library (reconstruction algorithms, priors,
resolution and attenuation models) is built on top of this.
A few things worth knowing¶
Imports come from submodules. The top-level
parallelprojnamespace is intentionally minimal, so classes are imported from their submodule (from parallelproj.projectors import RegularPolygonPETProjector). The import map in the API reference lists where each public name lives.Arrays are float32. Projectors expect and return single-precision arrays; create your images with
dtype=xp.float32.Same code on CPU and GPU.
parallelprojis python array API compatible. To run the snippet above on a CUDA GPU, change only the import (import array_api_compat.torch as xporimport array_api_compat.cupy as xp) and setdev = "cuda". Useto_numpy_array()to bring a result back to NumPy (e.g. for plotting).Time-of-flight. For a TOF projector, create a
TOFParametersobject and assign it toproj.tof_parameters; the forward projection then gains a trailing TOF-bin axis. See the02_pet_sinogram_projectionsgallery examples.
Next steps¶
Examples gallery – scanner geometries, TOF and listmode projectors, and full reconstruction algorithms (MLEM/OSEM, PDHG, transmission/MLTR, MLAA, …).
API reference – all projectors, operators and objective functions.
Source code & issue tracker on GitHub.