Scanner definition

In YRT-PET, a scanner is defined in two parts: the scanner parameters file (JSON format) and the Look-Up-Table (LUT) file in binary format.

Scanner parameters

The following parameters in the JSON file define the scanner:

  • VERSION : Scanner file format version. The current version is 3.2. [Mandatory]

  • scannerName : Scanner name. This is value is not used in the reconstruction. [Mandatory]

  • detCoord: Path to the LUT file. The path is relative to the JSON file’s parent folder.

    • If this field is unspecified, a LUT will be generated from the scanner’s properties.

  • axialFOV : Axial Field-of-View (mm). [Mandatory]

  • crystalSize_trans : Crystal size in the transaxial dimension (mm). [Mandatory]

  • crystalSize_z : Crystal size in the axial dimension (mm). [Mandatory]

  • crystalDepth : Crystal size in the direction of its orientation (mm). [Mandatory]

  • scannerRadius : Scanner radius (mm). [Mandatory].

    • This represents the distance between detector blocks and the isocenter.

  • detsPerRing : Number of detectors per ring. [Mandatory]

  • numRings : Number of rings. [Mandatory]

  • numDOI : Number of DOI layers. [Mandatory]

  • detsPerBlock : Number of detectors per block in the transaxial dimension.

    • This property is ignored if a LUT is provided

The following properties are necessary to define sensitivity images and histogram shapes:

  • maxRingDiff : Maximum ring difference two detectors must have to define a valid LOR. [Mandatory]

  • minAngDiff : Minimum difference two detectors must have to define a valid LOR. Has to be an even number. [Mandatory]

The following properties are used only in scatter estimation:

  • collimatorRadius : Collimator radius (mm). Only used in scatter estimation.

  • fwhm : Energy resolution FWHM (keV). Only used in scatter estimation.

  • energyLLD : Energy Low-Level-Discriminant (keV). Only used in Scatter estimation.

An optional field can be used to mask detectors:

  • detMask : Path to mask file. The mask file stores one boolean per detector (in the same order as the LUT file): true if the detector is acive, false if it is masked. The file is store in binary format, without header, and with one byte per detector.

Example

Here’s an example of a Scanner’s JSON file

{
  "VERSION": 3.2,
  "scannerName": "myscanner",
  "detCoord": "myscanner.lut",
  "axialFOV": 250.0,
  "crystalSize_z": 1.0,
  "crystalSize_trans": 1.0,
  "crystalDepth": 8.0,
  "scannerRadius": 130.0,
  "detsPerRing": 800,
  "numRings": 150,
  "numDOI": 2,
  "maxRingDiff": 50,
  "minAngDiff": 230,
  "detsPerBlock": 40,
  "fwhm": 0.2,
  "energyLLD": 400,
  "collimatorRadius": 100
}

Look-Up-Table

The LUT is a binary file that defines all the detecting elements, which can either be individual crystals, or, in the case of scanners with Depth-Of-Interation (DOI), detection positions.

The number of detection elements in the LUT must match the following:

detsPerRing * numRings * numDOI

For each element, the LUT defines, in 32-bit float:

  • X, Y, Z center position of the element

  • X, Y, Z orientation (unit vector) of the element, pointing towards the exterior of the scanner

X Position of detector 0 (float32)
Y Position of detector 0 (float32)
Z Position of detector 0 (float32)
X Orientation of detector 0 (float32)
Y Orientation of detector 0 (float32)
Z Orientation of detector 0 (float32)
X Position of detector 1 (float32)
Y Position of detector 1 (float32)
Z Position of detector 1 (float32)
X Orientation of detector 1 (float32)
Y Orientation of detector 1 (float32)
Z Orientation of detector 1 (float32)
...

The LUT’s elements should be ordered in the following way:

  • Position in the ring in either clockwise or anti-clockwise

  • Ring position, either ascending or descending order

  • DOI position, from inner to outer layers.

For Python users

Due to the simplicity of this format, it can be read using the following lines:

import numpy as np

lut = np.fromfile("<myscanner>.lut", dtype=np.float32).reshape((-1, 6))

Then, one can use matplotlib to display the scanner’s detector positions:

#<>
import matplotlib.pyplot as plt
N = 800 # Example: The number of crystals per ring is 800
plt.scatter(lut[:N, 0], lut[:N, 1])

Or the scanner’s detector orientations:

#<>
plt.plot(lut[:N, 3])  # X orientation
plt.plot(lut[:N, 4])  # Y orientation

Here, N is the number of detectors in a ring.

To display the scanner in three dimensions, the repository plot_scanner can do that using VTK.

One can also generate the LUT using Python and save it using:

lut.tofile("<mynewscanner>.lut")

Note: The code above assumes the data type of lut to be np.float32.

For plugin developers

The value returned by the functions getDetector1, getDetector2, and getDetectorPair should correspond to the index of the detector(s) in that LUT.