SUV normalization of PET images
PET images are stored in DICOM as activity concentration (for example in Bq/mL). To make the values
comparable across patients and scanners they are converted to the Standardized Uptake Value (SUV),
which accounts for the injected dose, the radionuclide decay and the patient body weight.
Radiomics.jl performs this conversion while reading the DICOM series, before the radiomic features
are extracted: set suv_ibsi_normalization=true in
extract_radiomic_features and pass the paths of a PET DICOM series and of the mask.
julia> using Radiomics
# PET DICOM series (folder) and mask (NIfTI, NRRD or DICOM)
julia> pet = "Path/to/PET_dicom_folder"
julia> mask = "Path/to/Mask.nii.gz"
# Normalize the PET to SUV and extract the radiomic features
julia> radiomic_features = Radiomics.extract_radiomic_features(pet, mask; suv_ibsi_normalization=true)
Without suv_ibsi_normalization=true the PET series is read as any other DICOM series,
and the features are computed on the stored values (rescale slope and intercept applied).
Requirements
What the normalization needs
- The image must be a DICOM PET series (Modality
PT), either single-frame (a folder with one file per slice) or Enhanced multiframe. - The mask must be a path to a 3D image in NIfTI, NRRD or DICOM format, with the same size and voxel spacing as the PET.
- The DICOM files must contain the metadata needed by the conversion: patient weight, injected dose, radionuclide half-life, administration time and acquisition times, units, and the rescale slope.
- With arrays, NIfTI or NRRD images, or a series that is not PET, the function stops with an error: the SUV cannot be computed without the DICOM metadata.
The SUV is computed on the slices that contain the region of interest (on all the frames for Enhanced multiframe files). When a required value is missing or inconsistent, the function raises an error that reports the cause, instead of returning a wrong SUV.
Validation with the digital reference objects
The conversion is validated against the digital reference objects (DRO) of the suv_computation repository. The test suite downloads the whole set, runs the full pipeline on each case and checks the result:
| DRO set | Cases | Expected outcome |
|---|---|---|
| Valid DROs | 43 | SUV in the region of interest equal to the nominal values of the DRO: minimum 0.20, median 1.00, maximum 4.00 |
Error DROs (DRO_error_*) |
15 | The normalization must be rejected, for the specific reason listed below |
Cases that must be rejected
Each error DRO has a defect in the DICOM metadata. The test suite checks not only that the normalization fails, but that it fails for the expected reason.
| DRO | Reason of the rejection |
|---|---|
DRO_error_2_0, DRO_error_2_1 |
Patient weight absent or not positive |
DRO_error_2_2, DRO_error_2_3 |
Invalid patient sex (needed by the ideal body weight normalization) |
DRO_error_2_4, DRO_error_2_5 |
Patient height absent (needed by the ideal body weight normalization) |
DRO_error_2_6 |
Counts without a conversion factor |
DRO_error_2_7 |
Unsupported units (proportional counts) |
DRO_error_3_0, DRO_error_3_1 |
Injected dose absent |
DRO_error_3_2 |
Reference time for the decay correction cannot be determined |
DRO_error_4_0 |
Administration time absent |
DRO_error_4_1, DRO_error_4_2 |
Administration time without a reliable date, with a long-half-life radionuclide |
DRO_error_5_0 |
Radionuclide half-life absent |
How to test the SUV normalization
The DRO tests are part of the test suite: they run with the IBSI validation test suite described in the IBSI Compliance page, and the code is available in the runtests.jl file. The DRO data are downloaded automatically.
julia> using Pkg
julia> Pkg.activate(".")
julia> Pkg.test("Radiomics")
To reproduce a single case, download a DRO from the repository and run it on its own. Each DRO has a
PT folder with the DICOM series and a mask in mask/DRO_mask.nii.gz:
julia> using Radiomics
julia> pet = "DRO_0_0/PT"
julia> mask = "DRO_0_0/mask/DRO_mask.nii.gz"
julia> r = Radiomics.extract_radiomic_features(pet, mask; features=[:first_order], suv_ibsi_normalization=true)
julia> r["firstorder_minimum"], r["firstorder_median"], r["firstorder_maximum"]