Skip to content

Commit 8e6558b

Browse files
add calibration factor to DetectionEfficiencies (#165)
* add calibration factorand text field to DetectionEfficiencies * add calibration_factor to helpers --------- Co-authored-by: Casper da Costa-Luis <[email protected]>
1 parent e007ee5 commit 8e6558b

File tree

8 files changed

+50
-13
lines changed

8 files changed

+50
-13
lines changed

cpp/helpers/include/petsird_helpers.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ make_detection_bin(const ScannerInformation& scanner, const TypeOfModule& type_o
101101

102102
inline float
103103
get_detection_efficiency(const ScannerInformation& scanner, const TypeOfModulePair& type_of_module_pair,
104-
const DetectionBin& detection_bin_1, const DetectionBin& detection_bin_2)
104+
const DetectionBin& detection_bin_1, const DetectionBin& detection_bin_2,
105+
bool with_calibration_factor = true)
105106
{
106-
float eff = 1.0F;
107+
float eff = with_calibration_factor ? scanner.detection_efficiencies.calibration_factor : 1.0F;
107108
const auto& detection_bin_efficiencies = scanner.detection_efficiencies.detection_bin_efficiencies;
108109
if (detection_bin_efficiencies)
109110
{
@@ -141,9 +142,10 @@ get_detection_efficiency(const ScannerInformation& scanner, const TypeOfModulePa
141142

142143
inline float
143144
get_detection_efficiency(const ScannerInformation& scanner, const TypeOfModulePair& type_of_module_pair,
144-
const CoincidenceEvent& event)
145+
const CoincidenceEvent& event, bool with_calibration_factor = true)
145146
{
146-
return get_detection_efficiency(scanner, type_of_module_pair, event.detection_bins[0], event.detection_bins[1]);
147+
return get_detection_efficiency(scanner, type_of_module_pair, event.detection_bins[0], event.detection_bins[1],
148+
with_calibration_factor);
147149
}
148150

149151
} // namespace petsird_helpers

cpp/helpers/include/petsird_helpers/create.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ construct_2D_nested_vector(std::size_t size0, std::size_t size1)
4949
5050
Elements will be constructed via the default constructors, so you will still have
5151
to fill in the actual values.
52+
53+
To prevent dramatic errors, the calibration_factor is set to 1, but this factor has to be set
54+
correctly afterwards.
5255
*/
5356
inline void
5457
initialize_scanner_information_dimensions(petsird::ScannerInformation& scanner, const std::size_t num_module_types,
@@ -60,6 +63,8 @@ initialize_scanner_information_dimensions(petsird::ScannerInformation& scanner,
6063
scanner.energy_resolution_at_511 = construct_vector<float>(num_module_types);
6164

6265
scanner.detection_efficiencies = petsird::DetectionEfficiencies();
66+
// set this to 1 to avoid the case where the user forgets to set it, in which case it might be zero.
67+
scanner.detection_efficiencies.calibration_factor = 1.F;
6368

6469
if (allocate_detection_bin_efficiencies)
6570
{

cpp/helpers/petsird_analysis.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ main(int argc, char const* argv[])
135135
std::cout << "Event energy mid points: " << energy_mid_points << std::endl;
136136
all_energy_mid_points.push_back(energy_mid_points);
137137

138+
std::cout << "Calibration factor: " << scanner.detection_efficiencies.calibration_factor << std::endl;
138139
std::cout << "Singles Histogram Level: ";
139140
switch (scanner.singles_histogram_level)
140141
{

cpp/helpers/petsird_generator.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ get_scanner_geometry()
113113
void
114114
set_detection_efficiencies(petsird::ScannerInformation& scanner)
115115
{
116+
// use some non-physical value for the calibration factor
117+
scanner.detection_efficiencies.calibration_factor = 42.F;
118+
116119
const auto num_module_types = scanner.scanner_geometry.NumberOfModuleTypes();
117120
// only 1 type of module in the current scanner
118121
assert(num_module_types == 1);

model/DetectionEfficiencies.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,31 @@ ModulePairEfficienciesVector: ModulePairEfficiencies*
4848
# 3. SGID = modulePairSGIDLUT[type_of_module1][type_of_module2][module_index1, module_index2]
4949
# 4. if (SGID < 0) return 0
5050
# 5. module_pair_efficiencies = modulePairEfficienciesVector[type_of_module1][type_of_module2][SGID]
51-
# 6. return detectionBinEfficiencies[type_of_module1](detection_bin1) * detectionBinEfficiencies[type_of_module2](detection_bin2)
51+
# 6. return calibrationFactor
52+
# * detectionBinEfficiencies[type_of_module1](detection_bin1) * detectionBinEfficiencies[type_of_module2](detection_bin2)
5253
# * module_pair_efficiencies[detection_bin_in_module1, detection_bin_in_module2]
5354
#
5455
# If either of the components is not present, its value is considered to be 1.
5556
#
57+
# The calibrationFactor is defined in terms of measured/expected "true unscattered" coincidences.
58+
# Moreover, it is (in principle) defined in terms of an idealised measurement of large uniform source.
59+
# Therefore, "expected" counts should take into account the
60+
# - activity (as estimated via a dose calibrator)
61+
# - positron emission branching fraction
62+
# - detectionBinEfficiencies and modulePairEfficiencies
63+
# - the line integral (in millimeter) through the source
64+
# This definition has the effect that for a given pair of detection_bins, we should have
65+
# mean_precorrected_true_coincidences =
66+
# detection_effiency * line_integral(number_of_positron_annihilations_in_source_as_estimated_from_dose_calibrator)
67+
# (with the precorrection taking into scatter and attenuation)
68+
#
5669
# Note that computing a detection efficiency for a triple coincidence is left to the user.
5770
DetectionEfficiencies: !record
5871
fields:
72+
# Text field describing how these efficiencies were determined
73+
methodDescription: string
74+
# Calibration factor for "absolute" efficiency
75+
calibrationFactor: float
5976
# List of detection efficiencies for every detection bin (one for each module-type).
6077
# Constraint: size(detectionBinEfficiencies) == ScannerGeometry.numberOfModuleTypes()
6178
detectionBinEfficiencies: DetectionBinEfficiencies*?

python/petsird/helpers/__init__.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,28 +87,31 @@ def make_detection_bin(
8787
def get_detection_efficiency(scanner: petsird.ScannerInformation,
8888
type_of_module_pair: petsird.TypeOfModulePair,
8989
detection_bin_1: petsird.DetectionBin,
90-
detection_bin_2: petsird.DetectionBin) -> float:
90+
detection_bin_2: petsird.DetectionBin,
91+
with_calibration_factor: bool = ...) -> float:
9192
"""Compute the detection efficiency for a pair of detectors"""
9293
...
9394

9495

9596
@typing.overload
9697
def get_detection_efficiency(scanner: petsird.ScannerInformation,
9798
type_of_module_pair: petsird.TypeOfModulePair,
98-
event: petsird.CoincidenceEvent) -> float:
99+
event: petsird.CoincidenceEvent,
100+
with_calibration_factor: bool = ...) -> float:
99101
"""Compute the detection efficiency for a coincidence event"""
100102
...
101103

102104

103105
_DetectionBinUnannotated = typing.get_args(petsird.DetectionBin)[0]
104106

105107

106-
def get_detection_efficiency(
107-
scanner: petsird.ScannerInformation,
108-
type_of_module_pair: petsird.TypeOfModulePair,
109-
event_or_detection_bin_1: typing.Union[petsird.CoincidenceEvent,
110-
petsird.DetectionBin],
111-
detection_bin_2: petsird.DetectionBin = None) -> float:
108+
def get_detection_efficiency(scanner: petsird.ScannerInformation,
109+
type_of_module_pair: petsird.TypeOfModulePair,
110+
event_or_detection_bin_1: typing.Union[
111+
petsird.CoincidenceEvent,
112+
petsird.DetectionBin],
113+
detection_bin_2: petsird.DetectionBin = None,
114+
with_calibration_factor: bool = True) -> float:
112115
"""Compute the detection efficiency"""
113116
if isinstance(event_or_detection_bin_1, _DetectionBinUnannotated):
114117
detection_bin_1 = event_or_detection_bin_1

python/petsird/helpers/analysis.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ def parserCreator():
7575
event_energy_bin_edges[1:]) / 2
7676
all_energy_mid_points.append(energy_mid_points)
7777
print("Event energy mid points: ", energy_mid_points)
78+
79+
print("Calibration factor: ",
80+
scanner.detection_efficiencies.calibration_factor)
7881
if scanner.detection_efficiencies.module_pair_sgidlut is not None:
7982
for type_of_module0 in range(num_module_types):
8083
for type_of_module1 in range(num_module_types):

python/petsird/helpers/generator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,8 @@ def get_detection_efficiencies(
343343
) -> petsird.DetectionEfficiencies:
344344
"""return some (non-physical) detection efficiencies"""
345345

346+
# use some non-physical value for the calibration factor
347+
calibration_factor = 42.
346348
num_types_of_modules = scanner.scanner_geometry.number_of_module_types()
347349
# single-level list for detection_bin_efficiencies
348350
all_detection_bin_efficiencies = []
@@ -377,6 +379,7 @@ def get_detection_efficiencies(
377379
module_pair_efficiencies_vectors0)
378380

379381
return petsird.DetectionEfficiencies(
382+
calibration_factor=calibration_factor,
380383
detection_bin_efficiencies=all_detection_bin_efficiencies,
381384
module_pair_sgidlut=all_LUTs,
382385
module_pair_efficiencies_vectors=all_module_pair_efficiencies_vectors,

0 commit comments

Comments
 (0)