Source code for parallelproj.tof

"""Time-of-flight (TOF) parameter dataclass for PET scanners.

Defines the bin width, Gaussian kernel width, truncation threshold, and
centre offset that parameterise the TOF response model used by the projectors
in :mod:`parallelproj.projectors`.
"""

from __future__ import annotations

import math
from dataclasses import dataclass

#: Speed of light in mm per nanosecond.
C_MM_PER_NS: float = 299.792458


[docs] @dataclass class TOFParameters: """TOF kernel parameters for a PET scanner. All spatial quantities are in **mm**. To convert a timing quantity :math:`t` (in ns) to a spatial displacement along the LOR, use .. math:: d \\;[\\text{mm}] = \\frac{c}{2} \\cdot t \\;[\\text{ns}] where :math:`c` = :data:`C_MM_PER_NS` mm/ns (= 299.792 mm/ns). The factor of 1/2 arises because both photons travel simultaneously toward the two detectors. Parameters ---------- num_tofbins : int Number of TOF bins covering the full LOR. tofbin_width : float Width of each TOF bin in mm. sigma_tof : float Standard deviation of the Gaussian TOF kernel in mm. num_sigmas : float, optional Number of sigmas at which the Gaussian kernel is truncated. default ``3.0``. tofcenter_offset : float, optional Global shift of the TOF bin grid centre from the LOR midpoint in mm. This is a scalar approximation (identical for all LORs); default ``0``. A non-zero value is needed when systematic timing offsets are present, e.g. due to unequal cable lengths or electronics delays. The offset is measured along the ``xstart -> xend`` direction of each LOR, so if the LOR endpoint convention is reversed (see :class:`~parallelproj.pet_lors.LOREndpointOrder` ``END_START``) a non-zero offset must be negated to keep the same physical meaning. Raises ------ ValueError If ``num_tofbins`` is not a positive integer, if ``tofbin_width``, ``sigma_tof`` or ``num_sigmas`` is not strictly positive, or if any value is not finite. A common cause is passing timing quantities in seconds/ps instead of the expected spatial **mm** (see the conversion above). Example ------- A scanner with 385 ps FWHM TOF resolution and 13 fine bins of 0.01302 ns each grouped into one sinogram bin:: # sigma_tof: FWHM [ns] / 2.355 * c/2 [mm/ns] sigma_tof = (385e-3 / 2.355) * (C_MM_PER_NS / 2) # ~ 24.5 mm # tofbin_width: 13 fine bins * 0.01302 ns * c/2 tofbin_width = 13 * 0.01302 * (C_MM_PER_NS / 2) # ~ 25.4 mm p = TOFParameters( num_tofbins=29, tofbin_width=tofbin_width, sigma_tof=sigma_tof, num_sigmas=3.0, ) """ num_tofbins: int tofbin_width: float sigma_tof: float num_sigmas: float = 3.0 tofcenter_offset: float = 0 def __post_init__(self) -> None: if int(self.num_tofbins) != self.num_tofbins or self.num_tofbins < 1: raise ValueError( f"num_tofbins must be a positive integer, got {self.num_tofbins!r}" ) if not (self.tofbin_width > 0): raise ValueError( f"tofbin_width must be > 0 (mm), got {self.tofbin_width!r}" ) if not (self.sigma_tof > 0): raise ValueError(f"sigma_tof must be > 0 (mm), got {self.sigma_tof!r}") if not (self.num_sigmas > 0): raise ValueError(f"num_sigmas must be > 0, got {self.num_sigmas!r}") if not math.isfinite(self.tofcenter_offset): raise ValueError( f"tofcenter_offset must be finite (mm), got {self.tofcenter_offset!r}" )
[docs] def get_tof_parameters_G1(**kwargs) -> TOFParameters: """Demo :class:`TOFParameters` **G1** (matches the G1 scanner). Defaults to 27 TOF bins with a 385 ps coincidence timing resolution (``sigma_tof``) and a 169 ps TOF-bin width, converted from ns to mm via ``x = t * C_MM_PER_NS / 2``. Pair it with :func:`parallelproj.pet_lors.get_lor_descriptor_G1`. Any :class:`TOFParameters` field (``num_tofbins``, ``tofbin_width``, ``sigma_tof``, ``num_sigmas``, ``tofcenter_offset``) can be overridden via keyword. """ kwargs.setdefault("num_tofbins", 27) kwargs.setdefault("tofbin_width", 0.169 * 0.5 * C_MM_PER_NS) kwargs.setdefault("sigma_tof", 0.385 * 0.5 * C_MM_PER_NS / 2.3548) return TOFParameters(**kwargs)
[docs] def get_tof_parameters_G2(**kwargs) -> TOFParameters: """Demo :class:`TOFParameters` **G2** (matches the G2 scanner). Identical to :func:`get_tof_parameters_G1` but with 29 TOF bins by default. Pair it with :func:`parallelproj.pet_lors.get_lor_descriptor_G2`. Any :class:`TOFParameters` field can be overridden via keyword. """ kwargs.setdefault("num_tofbins", 29) kwargs.setdefault("tofbin_width", 0.169 * 0.5 * C_MM_PER_NS) kwargs.setdefault("sigma_tof", 0.385 * 0.5 * C_MM_PER_NS / 2.3548) return TOFParameters(**kwargs)