4Quant / LungStageAnnotator

The documentation and support for the LungStageAnnotator tool
http://4quant.com/LungStageAnnotator
2 stars 0 forks source link

Fix PET SUV calculation to use DICOM Headers #40

Closed kmader closed 7 years ago

kmader commented 7 years ago

PET SUV calculation is based on phantom weight and injection time and should read these values from the header

OME NOTES on SUV and parameters of interest:
This is the first-pass implementation we'll make:
Standardized uptake value, SUV, (also referred to as the dose uptake ratio, DUR) is a widely used, simple PET quantifier, calculated as a ratio of tissue radioactivity concentration (e.g. in units kBq/ml) at time T, CPET(T) and injected dose (e.g. in units MBq) at the time of injection divided by body weight (e.g. in units kg).
SUVbw = CPET(T) / (Injected dose / Patient's weight)
Instead of body weight, the injected dose may also be corrected by the lean body mass, or body surface area (BSA) (Kim et al., 1994). Verbraecken et al. (2006) review the different formulas for calculating the BSA.
SUVbsa= CPET(T) / (Injected dose / BSA)
If the above mentioned units are used, the unit of SUV will be g/ml.
===
Later, we can try a more careful computation that includes decay correction:
Most PET systems record their pixels in units of activity concentration (MBq/ml) (once Rescale Slope has been applied, and the units are specified in the Units attribute).
To compute SUVbw, for example, it is necessary to apply the decay formula and account for the patient's weight. For that to be possible, during de-identification, the Patient's Weight must not have been removed, and even though dates may have been removed, it is important not to remove the times, since the difference between the time of injection and the acquisition time is what is important.
In particular, DO NOT REMOVE THE FOLLOWING DICOM TAGS:
Radiopharmaceutical Start Time (0018,1072) Decay Correction (0054,1102) Decay Factor (0054,1321) Frame Reference Time (0054,1300) Radionuclide Half Life (0018,1075) Series Time (0008,0031) Patient's Weight (0010,1030)
Note that to calculate other common SUV values like SUVlbm and SUVbsa, you also need to retain:
Patient's Sex (0010,0040)
Patient's Size (0010,1020)
If there is a strong need to remove times from an identity leakage perspective, then one can normalize all times to some epoch, but it has to be done consistently across all images in the entire study (preferably including the CT reference images); note though, that the time of injection may be EARLIER than the Study Time, which you might assume would be the earliest, so it takes a lot of effort to get this right.
For Philips images, none of this applies, and the images are in COUNTS and the private tag (7053,1000) SUV Factor must be used.
To calculate the SUV of a particular pixel, you just have to calculate [pixel _value * tag 7053|1000 ]
The tag 7053|1000 is a number (double) taking into account the patient's weight, the injection quantity.
We get the tag from the original file with:
double suv;
itk::ExposeMetaData<double>(*dictionary[i], "7053|1000", suv);

Or simply using: https://github.com/Slicer/Slicer/blob/master/Modules/CLI/PETStandardUptakeValueComputation/PETStandardUptakeValueComputation.cxx

kmader commented 7 years ago

Implemented basic SUV code from radiomics paper: image

[1] Vallières, M. et al. (2015). A radiomics model from joint FDG-PET and MRI texture features for the prediction of lung metastases in soft-tissue sarcomas of the extremities. Physics in Medicine and Biology, 60(14), 5471-5496. doi:10.1088/0031-9155/60/14/5471

kmader commented 7 years ago

this was nice piece of code i felt sad deleting

def _extract_pet_header_tags(pet_series):
    # type: (str) -> Dict[str, Optional[str]]
    """
    A tag based implementation
    :param pet_series:
    :return:
    """
    PET_TAGS = {
        "Acquisition Time": "0008,0032",
        "Patient's Sex": "0010,0040",
        "Patient's Size": "0010,1020",
        "Patient's Weight": "0010,1030",
        "Philipps SUV Scalar": "7053,1000",
        "Radiopharmaceutical Start Time": "0018,1072",
        "Decay Correction": "0054,1102",
        "Decay Factor": "0054,1321",
        "Frame Reference Time": "0054,1300",
        "Radiopharmaceutical Information Sequence": "0054,0016",
        "Radiopharmaceutical Volume": "0018,1071",
        "Radionuclide Half Life": "0018,1075",
        "Radionuclide Total Dose": "0018,1074",
        "Series Time": "0008,0031"
    }
    try:
        pet_files = slicer.dicomDatabase.filesForSeries(pet_series)
        assert len(pet_files) > 0, "No pet files found for series {}".format(pet_series)
        f_file = pet_files[0]

        def _get_tag(c_tag):
            try:
                tag_val = slicer.dicomDatabase.fileValue(f_file, c_tag)
                return tag_val if len(tag_val) > 0 else None
            except Exception as e:
                print('Cannot read tag', c_tag, e)
                return None
    except:
        _get_tag = lambda x: None
    pet_header = {c_name: _get_tag(c_tag) for c_name, c_tag in PET_TAGS.items()}
    return pet_header