Source code for toor.Corrections.General.detector_sensitivity
# Copyright (c) 2025. Pedro Encarnação . Universidade de Aveiro LICENSE: CC BY-NC-SA 4.0 # ****************************
#
# *******************************************************
# * FILE: calibration
# * AUTHOR: Pedro Encarnação
# * DATE: 09/04/2025
# * LICENSE: "CC BY-NC-SA 4.0"
# *******************************************************
import numpy as np
import matplotlib.pyplot as plt
[docs]
class DetectorSensitivityResponse:
"""
This class is used to calculate the detector sensitivity of a system.
"""
def __init__(self, use_detector_energy_resolution=True):
"""
Initialize the DetectorSensitivity class.
:param device: The device to be used for the calculation.
:param file_path: The path to the file where the results will be saved.
"""
self._useDetectorEnergyResolution = use_detector_energy_resolution
self._energyWindows = None
self._energyPeaks = None
self._fields = None
self._detectorSensitivity = None
self._probabilityOfDetection = None
@property
def fields(self):
"""
Get the fields.
:return: The fields.
"""
return self._fields
[docs]
def setFields(self):
"""
Set the fields for the detector sensitivity.
:param fields: The fields to be used for the calculation.
"""
self._fields = [str(i) for i in self._energyPeaks]
@property
def energyPeaks(self):
"""
Get the energy peaks.
:return: The energy peaks.
"""
return self._energyPeaks
[docs]
def setEnergyPeaks(self, energyPeaks=None):
"""
Set the energy peaks for the detector sensitivity.
:param energyPeaks: The energy peaks to be used for the calculation.
"""
if not isinstance(energyPeaks, list) and not isinstance(energyPeaks, np.ndarray):
raise ValueError("The energy peaks must be a list or numpy array.")
self._energyPeaks = energyPeaks
self.setFields()
@property
def energyWindows(self):
"""
Get the energy windows.
:return: The energy windows.
"""
return self._energyWindows
[docs]
def setEnergyWindows(self, energyWindows=None, torFile=None):
"""
Set the energy regions for the detector sensitivity.
:param energyRegions: The energy regions to be used for the calculation.
"""
if self._useDetectorEnergyResolution:
# check if energyresolution function is defined
# if not hasattr(self._torFile.systemInfo, "getEnergyResolution"):
# raise ValueError("The energy resolution function is not defined in the sensitivity file. Run the TOR file generator with a device with this function set.")
energyWindows = np.zeros((len(self._energyPeaks), 2))
low = self._energyPeaks - self._energyPeaks * torFile.systemInfo.getFWHMSystemEnergyResponse(self._energyPeaks)
high = self._energyPeaks + self._energyPeaks * torFile.systemInfo.getFWHMSystemEnergyResponse(self._energyPeaks)
energyWindows[:, 0] = np.clip(low, 0, None)
energyWindows[:, 1] = np.clip(high, 0, None)
energyWindows = np.round(energyWindows, 2)
print("Energy windows: ", energyWindows)
else:
if not isinstance(energyWindows, list) and not isinstance(energyWindows, np.ndarray):
raise ValueError("The energy regions must be a list or numpy array.")
self._energyWindows = energyWindows
[docs]
def setDetectorSensitivity(self, torFile=None, fileBodyData=None, generate_uniform=False):
numberOfEnergyCuts = len(self._energyPeaks)
if generate_uniform:
if fileBodyData is None:
raise ValueError("The fileBodyData must be provided when generate_uniform is True.")
column = fileBodyData.listmodeFields.index("IDB")
self._probabilityOfDetection = np.ones((numberOfEnergyCuts, len(fileBodyData.uniqueValues[column])))
self._probabilityOfDetection /= np.sum(self._probabilityOfDetection, axis=1)[:, np.newaxis]
else:
if torFile.systemInfo.deviceType == "PET":
# "Not developed yet"
numberOfModules = len(torFile.systemInfo.detectorModules)
pass
else:
# plt.figure()
column = torFile.fileBodyData.listmodeFields.index("IDB")
self._probabilityOfDetection = np.zeros((len(self._energyPeaks), len(torFile.fileBodyData.uniqueValues[column])))
for i in range(numberOfEnergyCuts):
energyWindow = self._energyWindows[i]
energyPeak = self._energyPeaks[i]
# Get the number of events in the energy window
#cut listmode
energy_array = torFile.fileBodyData["ENERGYB"]
indexes_active = np.where((energy_array >= energyWindow[0]) & (energy_array <= energyWindow[1]))[0]
id_array = torFile.fileBodyData["IDB"][indexes_active]
# get collumn of IDA
probability = np.histogram(id_array, bins=len(torFile.fileBodyData.uniqueValues[column]), density=True)[0]
self._probabilityOfDetection[i] = probability
# plt.plot(probability, label=f"Energy window {i + 1} ({energyWindow[0]}-{energyWindow[1]})")
# plt.legend()
# plt.show()
@property
def probabilityOfDetection(self):
"""
Get the detector sensitivity.
:return: The detector sensitivity.
"""
return self._probabilityOfDetection
def __str__(self):
"""
Return the string representation of the class.
:return: The string representation of the class.
"""
return f"DetectorSensitivityResponse(energyPeaks={self._energyPeaks}, energyWindows={self._energyWindows})"
def __repr__(self):
"""
Return the string representation of the class.
:return: The string representation of the class.
"""
return self.__str__()
def __getitem__(self, item):
"""
Get the item from the class.
:param item: The item to be get.
:return: The item.
"""
return getattr(self, item)
def __iter__(self):
"""
Return the iterator of the class.
:return: The iterator of the class.
"""
return iter(self.__dict__.items())
def __len__(self):
"""
Return the length of the class.
:return: The length of the class.
"""
return len(self._probabilityOfDetection)