Skip to content

Commit 76d1a91

Browse files
Merge pull request #160 from KrisThielemans/SinglesQuadruples
Add singles and quadruples and improve naming for other event types - Adds `singleEvents` and `quadrupleEvents` to `EventTimeBlock` - Add related policies and `*AreStored` variables to `ScannerInformation` - Add ScannerInformation.delayedEventsPolicy - Remove "optional" from `delayedEvents` and `tripleEvents` (***WARNING***: breaks backward compatibility on C++ level) - More consistent naming: (***WARNING***: breaks backward compatibility) - Rename promptCoincidencesAreStored to promptEventsAreStored, and same for delayeds - Renamed ScannerInformation.coincidencePolicy to promptEventPolicy - Rename CoincidencePolicy.rejectMultiples to rejectHigherMultiples
2 parents bd3563e + a36579b commit 76d1a91

File tree

9 files changed

+191
-51
lines changed

9 files changed

+191
-51
lines changed

cpp/helpers/petsird_analysis.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,13 +173,15 @@ main(int argc, char const* argv[])
173173
for (unsigned mtype1 = 0; mtype1 < num_module_types; ++mtype1)
174174
{
175175
const petsird::TypeOfModulePair mtype_pair{ mtype0, mtype1 };
176+
177+
// This code would need work to be able to handle a list-mode file without prompts
176178
const auto& prompt_events = event_time_block.prompt_events[mtype0][mtype1];
177179

178180
// count events
179181
num_prompts += prompt_events.size();
180-
if (event_time_block.delayed_events)
182+
if (scanner.delayed_events_are_stored)
181183
{
182-
num_delayeds += (*event_time_block.delayed_events)[mtype0][mtype1].size();
184+
num_delayeds += event_time_block.delayed_events[mtype0][mtype1].size();
183185
}
184186

185187
if (print_events)

cpp/helpers/petsird_generator.cpp

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,12 @@ get_scanner_info()
243243

244244
set_detection_efficiencies(scanner_info);
245245

246-
scanner_info.coincidence_policy = petsird::CoincidencePolicy::kRejectMultiples;
247-
scanner_info.delayed_coincidences_are_stored = false;
246+
scanner_info.prompt_event_policy = petsird::CoincidencePolicy::kRejectHigherMultiples;
247+
scanner_info.single_events_are_stored = false;
248+
scanner_info.prompt_events_are_stored = true;
249+
scanner_info.delayed_events_are_stored = false;
248250
scanner_info.triple_events_are_stored = false;
251+
scanner_info.quadruple_events_are_stored = false;
249252

250253
return scanner_info;
251254
}
@@ -349,9 +352,10 @@ main(int argc, char* argv[])
349352
petsird::EventTimeBlock time_block;
350353
time_block.time_interval.start = t * EVENT_TIME_BLOCK_DURATION;
351354
time_block.time_interval.stop = (t + 1) * EVENT_TIME_BLOCK_DURATION;
352-
time_block.prompt_events.resize(1);
353-
time_block.prompt_events[type_of_module].resize(1);
354-
time_block.prompt_events[type_of_module][type_of_module] = prompts_this_block;
355+
auto& prompt_events = time_block.prompt_events;
356+
prompt_events.resize(1);
357+
prompt_events[type_of_module].resize(1);
358+
prompt_events[type_of_module][type_of_module] = prompts_this_block;
355359
writer.WriteTimeBlocks(time_block);
356360
}
357361
writer.EndTimeBlocks();

model/CoincidenceEvent.yml

Lines changed: 0 additions & 7 deletions
This file was deleted.

model/Events.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# All information about a single event specified as indices (i.e. discretized).
2+
SingleEvent: !record
3+
fields:
4+
# identifiers of detecting bin (see doc for DetectionBin)
5+
detectionBin: DetectionBin
6+
# arrival time w.r.t. start of the time-block (in ps) (see EventTimeBlock).
7+
timeOffsetInTimeBlock: uint
8+
9+
# All information about a coincidence event specified as indices (i.e. discretized).
10+
CoincidenceEvent: !record
11+
fields:
12+
# identifiers of the two detecting bins (see doc for DetectionBin)
13+
detectionBins: DetectionBin*2
14+
# an index into the tofBinEdges field in the ScannerInformation
15+
tofIdx: uint
16+
17+
# All information about a triple coincidence event specified as identifiers (i.e. discretized).
18+
TripleEvent: !record
19+
fields:
20+
# identifiers of the three detecting bins (see doc for DetectionBin)
21+
# Note that it is possible that 2 "detecting elements" (i.e. module/element in the ExpandedDetectionBin)
22+
# are equal (e.g. for inter-crystal Compton events)
23+
detectionBins: DetectionBin*3
24+
# timing differences w.r.t. first event, stored as
25+
# indices into the tofBinEdges field in the ScannerInformation
26+
# Note: only 2, corresponding to the arrival time differences of the second and third detectionBins
27+
# listed w.r.t. the first detectionBin
28+
tofIndices: uint*2
29+
30+
# All information about a quadruple coincidence event specified as indices (i.e. discretized).
31+
QuadrupleEvent: !record
32+
fields:
33+
# identifiers of the four detecting bins (see doc for DetectionBin)
34+
detectionBins: DetectionBin*4
35+
# timing differences w.r.t. first event, stored as
36+
# indices into the tofBinEdges field in the ScannerInformation.
37+
# Note: only 3, see documentation for TripleEvent
38+
tofIndices: uint*3

model/Protocol.yml

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,57 @@ Header: !record
1515
# types of timeBlocks
1616
TimeBlock: [EventTimeBlock, ExternalSignalTimeBlock, BedMovementTimeBlock, GantryMovementTimeBlock, DeadTimeTimeBlock, SinglesHistogramTimeBlock]
1717

18+
# Type definition for a list of single events (for a module of a specific type)
19+
ListOfSingleEvents: SingleEvent*
20+
1821
# Type definition for a list of coincidences, where all coincidences are between 2 modules of specific types
1922
ListOfCoincidenceEvents: CoincidenceEvent*
2023

2124
# Type definition for a list of triples, where all triples are between 3 modules of specific types
2225
ListOfTripleEvents: TripleEvent*
2326

27+
# Type definition for a list of triples, where all triples are between 3 modules of specific types
28+
ListOfQuadrupleEvents: TripleEvent*
29+
30+
# time-block that stores the detected (gamma photon) events as lists.
31+
# An event time block optionally stores
32+
# - singles
33+
# - "prompt" coincidences (i.e. 2 photons detected within the coincidence window),
34+
# - "delayed"coincidences (i.e. 2 photons with arrival time within the coincidence window after subtracting a
35+
# (scanner dependent) offset for the second event)
36+
# - triples
37+
# - quadruples
38+
# Note: PETSIRD currently does not allow storing higher order coincidences. These are very unlikely to occur in practice.
39+
# It is expected that most scanners will only store prompt and delayed coincidences. If singles are stored, it is expected
40+
# that coincidences are not stored, but this is not guaranteed.
41+
#
42+
# See also the "policy" fields in ScannerInformation.
43+
# For instance, if coincidences, triples and quadruples are stored, it is likely that ScannerInformation.promptEventPolicy = rejectHigherMultiples,
44+
# ScannerInformation.tripleEventPolicy = rejectHigherMultiples, and ScannerInformation.quadrupleEventPolicy = rejectHigherMultiples as well,
45+
# but this is not guaranteed.
2446
EventTimeBlock: !record
2547
fields:
2648
# time interval for this time block
2749
# We suggest that for each coincidence/multiple event, the earliest time of arrival
2850
# of a photon is used for deciding which time block the event is entered in.
2951
# However, the impact of this choice is negligible and therefore can be vendor dependent.
52+
# WARNING: if singleEvents are recorded, the time interval currently has to be smaller
53+
# than 2^32 picoseconds due to how time is stored, see SingleEvent.
3054
timeInterval: TimeInterval
31-
# Nested vector with lists of prompts in this time block
32-
# There is one separate list for every pair of module types.
55+
56+
# nested vector with lists of singles
57+
# If ScannerInformation.singleEventsAreStored is False, the vector should have
58+
# size 0. Otherwise, there is one separate list for every module type.
59+
# To get the list of singles for a type of module where the `detectionBin`
60+
# is in a module of the first type, use
61+
# singleEvents[typeOfModule]
62+
# Constraint: (size(singleEvents) > 0) == ScannerInformation.singleEventsAreStored
63+
# Constraint: (size(singleEvents) == 0) || (size(singleEvents) == ScannerInformation.numberOfModuleTypes)
64+
singleEvents: ListOfSingleEvents*
65+
66+
# nested vector with lists of prompts in this time block
67+
# If ScannerInformation.promptEventsAreStored is False, the vector should have
68+
# size 0. If not, there is one separate list for every pair of module types.
3369
# To get the list of prompts for 2 types of modules where the first `detectionBin`
3470
# given in the `CoincidenceEvent` is in a module of the first type, use
3571
# promptEvents[typeOfModule1][typeOfModule2]
@@ -38,24 +74,40 @@ EventTimeBlock: !record
3874
# but this is currently not enforced. Therefore, to check all coincidences between two
3975
# different types of modules, a user will have to check two lists, i.e. also
4076
# promptEvents[typeOfModule2][typeOfModule1]
41-
# Constraint: size(promptsEvents) == ScannerInformation.numberOfModuleTypes
42-
# Constraint: size(promptsEvents[*]) == ScannerInformation.numberOfModuleTypes
77+
# Constraint: (size(promptEvents) > 0) == ScannerInformation.promptEventsAreStored
78+
# Constraint: (size(promptEvents) == 0) || (size(promptsEvents) == ScannerInformation.numberOfModuleTypes)
79+
# Constraint: (size(promptEvents) == 0) || (size(promptsEvents[*]) == ScannerInformation.numberOfModuleTypes)
4380
promptEvents: ListOfCoincidenceEvents**
44-
# optional nested vector with lists of delayed coincidences in this time block
81+
82+
# nested vector with lists of delayed coincidences in this time block
4583
# To get the list of delayeds for 2 types of modules, use
4684
# delayedEvents[typeOfModule1][typeOfModule2]
4785
# See promptEvents for more information.
48-
# Constraint: size(delayedEvents) == ScannerInformation.numberOfModuleTypes
49-
# Constraint: size(delayedEvents[*]) == ScannerInformation.numberOfModuleTypes
50-
delayedEvents: ListOfCoincidenceEvents**?
51-
# optional nested vector with lists of triple coincidences in this time block
86+
# Constraint: (size(delayedEvents) > 0) == ScannerInformation.delayedCoincidencesAreStored
87+
# Constraint: (size(delayedEvents) == 0) || (size(delayedEvents) == ScannerInformation.numberOfModuleTypes)
88+
# Constraint: (size(delayedEvents) == 0) || (size(delayedEvents[*]) == ScannerInformation.numberOfModuleTypes)
89+
delayedEvents: ListOfCoincidenceEvents**
90+
91+
# nested vector with lists of triple coincidences in this time block
5292
# To get the list of triples for 3 types of modules, use
5393
# tripleEvents[typeOfModule1][typeOfModule2][typeOfModule3]
5494
# See promptEvents for more information.
55-
# Constraint: size(tripleEvents) == ScannerInformation.numberOfModuleTypes
56-
# Constraint: size(tripleEvents[*]) == ScannerInformation.numberOfModuleTypes
57-
# Constraint: size(tripleEvents[*][*]) == ScannerInformation.numberOfModuleTypes
58-
tripleEvents: ListOfTripleEvents***?
95+
# Constraint: (size(tripleEvents) > 0) == ScannerInformation.tripleEventsAreStored
96+
# Constraint: (size(tripleEvents) == 0) || (size(tripleEvents) == ScannerInformation.numberOfModuleTypes)
97+
# Constraint: (size(tripleEvents) == 0) || (size(tripleEvents[*]) == ScannerInformation.numberOfModuleTypes)
98+
# Constraint: (size(tripleEvents) == 0) || (size(tripleEvents[*][*]) == ScannerInformation.numberOfModuleTypes)
99+
tripleEvents: ListOfTripleEvents***
100+
101+
# nested vector with lists of quadruple coincidences in this time block
102+
# To get the list of quadruples for 4 types of modules, use
103+
# quadrupleEvents[typeOfModule1][typeOfModule2][typeOfModule3][typeOfModule4]
104+
# See promptEvents for more information.
105+
# Constraint: (size(quadrupleEvents) > 0) == ScannerInformation.quadrupleEventsAreStored
106+
# Constraint: (size(quadrupleEvents) == 0) || (size(quadrupleEvents) == ScannerInformation.numberOfModuleTypes)
107+
# Constraint: (size(quadrupleEvents) == 0) || (size(quadrupleEvents[*]) == ScannerInformation.numberOfModuleTypes)
108+
# Constraint: (size(quadrupleEvents) == 0) || (size(quadrupleEvents[*][*]) == ScannerInformation.numberOfModuleTypes)
109+
# Constraint: (size(quadrupleEvents) == 0) || (size(quadrupleEvents[*][*][*]) == ScannerInformation.numberOfModuleTypes)
110+
quadrupleEvents: ListOfQuadrupleEvents****
59111

60112
ExternalSignalTypeEnum: !enum
61113
values:
@@ -116,7 +168,12 @@ DeadTimeTimeBlock: !record
116168
SinglesHistogram: uint64[singlesDetectionBin]
117169

118170
# A time block that stores a singles histogram.
119-
# See SinglesHistogramLevelType for information.
171+
# Note that all detected singles (within the ScannerInformation.singlesHistogramEnergyBinEdges)
172+
# should be stored. This is in contrast to the EventTimeBlock.singleEvents, where optionally
173+
# only singles that are in coincidence are recorded (see SingleEventPolicy).
174+
#
175+
# See also SinglesHistogramLevelType for information.
176+
#
120177
# Constraint: if ScannerInformation.singlesHistogramLevel == SinglesHistogramLevelType.none, there should
121178
# be no time blocks of this type in the stream.
122179
# Otherwise, the union of SinglesHistogramTimeBlock.timeInterval should be at least as large as the union of all EventTimeBlocks.timeInterval

model/ScannerInformation.yml

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
1+
# Type definition for how to encode how the scanner records single events (if it does).
2+
SingleEventPolicy: !enum
3+
values:
4+
# store all single events
5+
- all
6+
7+
# only store those that are in coincidence with another single
8+
- rejectIfNotInCoincidence
9+
10+
# other options, to be listed in the future
11+
- other
112

213
# Type definition for how to encode how the scanner handles multiple coincidences when recording the prompts.
314
# Due to various effects (such as high count rate, prompt gammas), it is possible that multiple single
415
# events are detected within the coincidence window. This type encodes some different ways
5-
# that this multiple events are handled, and recorded in the coincidence stream.
16+
# that this multiple events are handled, and recorded in the EventTimeBlock.promptEvents field.
617
CoincidencePolicy: !enum
718
values:
8-
# multiples will be rejected
9-
- rejectMultiples
19+
# multiples (more than 2 events in the coincidence window) will be rejected (i.e., not stored in the EventTimeBlock.promptEvents field)
20+
- rejectHigherMultiples
1021

1122
# multiples will be stored as a sequence of all pairs, e.g. a triple leads to 3 pairs
1223
- multiplesAsAllCoincidences
@@ -18,6 +29,25 @@ CoincidencePolicy: !enum
1829
# other options, to be listed in the future
1930
- other
2031

32+
33+
# Type definition for how to encode how the scanner handles higher order coincidences when recording the triples.
34+
TripleEventPolicy: !enum
35+
values:
36+
# quadruples etc are not stored in the EventTimeBlock.tripleEvents field
37+
- rejectHigherMultiples
38+
39+
# other options, to be listed in the future
40+
- other
41+
42+
# Type definition for how to encode how the scanner handles higher order coincidences when recording the quadruples.
43+
QuadrupleEventPolicy: !enum
44+
values:
45+
# quadruples etc are not stored in the EventTimeBlock.quadrupleEvents field
46+
- rejectHigherMultiples
47+
48+
# other options, to be listed in the future
49+
- other
50+
2151
# Type definition for how single histograms are stored.
2252
# Many scanners are able to store singles accumulated in time intervals, i.e.
2353
# histogrammed (as opposed to a list of singles with their corresponding DetectionBin).
@@ -115,14 +145,39 @@ ScannerInformation: !record
115145
# (singlesHistogramLevel == SinglesHistogramLevelType.none && size(singlesHistogramEnergyBinEdges) == 0)
116146
singlesHistogramEnergyBinEdges: BinEdges*
117147

118-
# Encode how the scanner handles multiple coincidences
119-
coincidencePolicy: CoincidencePolicy
148+
# Encode how the scanner handles single events in the EventTimeBlock.singleEvents field
149+
singleEventPolicy: SingleEventPolicy
150+
151+
# Encode how the scanner handles multiple coincidences in the EventTimeBlock.promptEvents field
152+
promptEventPolicy: CoincidencePolicy
120153

121-
# a flag to indicate of delayed coincidences are recorded in the stream
122-
delayedCoincidencesAreStored: bool
154+
# Encode how the scanner handles multiple coincidences in the EventTimeBlock.delayedEvents field
155+
# It is expected that this will have value rejectHigherMultiples, but it is not guaranteed.
156+
delayedEventPolicy: CoincidencePolicy
123157

124-
# a flag to indicate of triple events are recorded in the stream
158+
# Encode how the scanner handles higher order coincidences in the EventTimeBlock.tripleEvents field
159+
# This field is ignored if tripleEventsAreStored == false.
160+
tripleEventPolicy: TripleEventPolicy
161+
162+
# Encode how the scanner handles higher order coincidences in the EventTimeBlock.quadrupleEvents field
163+
# This field is ignored if quadrupleEventsAreStored == false.
164+
quadrupleEventPolicy: QuadrupleEventPolicy
165+
166+
# a flag to indicate of single events are recorded in the stream (see EventTimeBlock)
167+
# Note that this is separate from the singlesHistogramLevel, where singles are histogrammed in a time-block.
168+
singleEventsAreStored: bool
169+
170+
# a flag to indicate of prompt coincidences are recorded in the stream (see EventTimeBlock)
171+
promptEventsAreStored: bool
172+
173+
# a flag to indicate of delayed coincidences are recorded in the stream (see EventTimeBlock)
174+
delayedEventsAreStored: bool
175+
176+
# a flag to indicate of triple events are recorded in the stream (see EventTimeBlock)
125177
tripleEventsAreStored: bool
126178

179+
# a flag to indicate of quadruple coincidences are recorded in the stream (see EventTimeBlock)
180+
quadrupleEventsAreStored: bool
181+
127182
# coincidence detection efficiencies
128183
detectionEfficiencies: DetectionEfficiencies

model/TripleEvent.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

python/petsird/helpers/analysis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def parserCreator():
9494
prompt_events = time_block.value.prompt_events[mtype0][
9595
mtype1]
9696
num_prompts += len(prompt_events)
97-
if time_block.value.delayed_events is not None:
97+
if scanner.delayed_events_are_stored:
9898
num_delayeds += len(
9999
time_block.value.delayed_events[mtype0]
100100
[mtype1])

python/petsird/helpers/generator.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,9 +204,12 @@ def get_scanner_info() -> petsird.ScannerInformation:
204204
# Now added the efficiencies
205205
scanner.detection_efficiencies = get_detection_efficiencies(scanner)
206206

207-
scanner.coincidence_policy = petsird.CoincidencePolicy.REJECT_MULTIPLES
208-
scanner.delayed_coincidences_are_stored = False
207+
scanner.coincidence_policy = petsird.CoincidencePolicy.REJECT_HIGHER_MULTIPLES
208+
scanner.single_events_are_stored = False
209+
scanner.prompt_events_are_stored = True
210+
scanner.delayed_events_are_stored = False
209211
scanner.triple_events_are_stored = False
212+
scanner.quadruple_events_are_stored = False
210213

211214
return scanner
212215

0 commit comments

Comments
 (0)