1010
1111GateDigitizerPileupActor::GateDigitizerPileupActor (py::dict &user_info)
1212 : GateVDigitizerWithOutputActor(user_info, false ) {
13-
14- // Actions
1513 fActions .insert (" EndOfEventAction" );
14+ fActions .insert (" EndOfRunAction" );
1615}
1716
1817GateDigitizerPileupActor::~GateDigitizerPileupActor () = default ;
1918
2019void GateDigitizerPileupActor::InitializeUserInfo (py::dict &user_info) {
2120 GateVDigitizerWithOutputActor::InitializeUserInfo (user_info);
21+
22+ // Get time window parameter in ns.
23+ fTimeWindow = 1000.0 ; // default value
24+ if (py::len (user_info) > 0 && user_info.contains (" time_window" )) {
25+ fTimeWindow = DictGetDouble (user_info, " time_window" );
26+ }
27+ fGroupVolumeDepth = -1 ;
28+ }
29+
30+ void GateDigitizerPileupActor::SetGroupVolumeDepth (const int depth) {
31+ fGroupVolumeDepth = depth;
2232}
2333
2434void GateDigitizerPileupActor::DigitInitialize (
2535 const std::vector<std::string> &attributes_not_in_filler) {
36+ // We handle all attributes explicitly by storing their values,
37+ // because we need to keep singles across consecutive events.
2638 auto a = attributes_not_in_filler;
39+ for (const auto &att_name : fInputDigiCollection ->GetDigiAttributeNames ()) {
40+ a.push_back (att_name);
41+ }
42+
2743 GateVDigitizerWithOutputActor::DigitInitialize (a);
44+
45+ // Get output attribute pointers
46+ fOutputEdepAttribute =
47+ fOutputDigiCollection ->GetDigiAttribute (" TotalEnergyDeposit" );
48+ fOutputGlobalTimeAttribute =
49+ fOutputDigiCollection ->GetDigiAttribute (" GlobalTime" );
50+ fOutputVolumeIDAttribute =
51+ fOutputDigiCollection ->GetDigiAttribute (" PreStepUniqueVolumeID" );
52+
53+ // Set up pointers to track specific attributes
54+ auto &lr = fThreadLocalVDigitizerData .Get ();
55+ auto &l = fThreadLocalData .Get ();
56+
57+ lr.fInputIter = fInputDigiCollection ->NewIterator ();
58+ lr.fInputIter .TrackAttribute (" TotalEnergyDeposit" , &l.edep );
59+ lr.fInputIter .TrackAttribute (" GlobalTime" , &l.time );
60+ lr.fInputIter .TrackAttribute (" PreStepUniqueVolumeID" , &l.volID );
2861}
2962
3063void GateDigitizerPileupActor::BeginOfRunAction (const G4Run *run) {
3164 GateVDigitizerWithOutputActor::BeginOfRunAction (run);
3265}
3366
34- void GateDigitizerPileupActor::EndOfEventAction (const G4Event * /* unused*/ ) {
67+ void GateDigitizerPileupActor::StoreAttributeValues (
68+ threadLocalT::PileupGroup &group, size_t index) {
69+ // Clear previous values
70+ group.stored_attributes .clear ();
71+
72+ // Store values from all attributes in the input collection
73+ for (auto *att : fInputDigiCollection ->GetDigiAttributes ()) {
74+ auto name = att->GetDigiAttributeName ();
75+ auto type = att->GetDigiAttributeType ();
76+
77+ // Skip the attributes we handle explicitly
78+ if (name == " TotalEnergyDeposit" || name == " GlobalTime" ||
79+ name == " PreStepUniqueVolumeID" ) {
80+ continue ;
81+ }
82+
83+ // Store value based on type
84+ switch (type) {
85+ case ' D' : // double
86+ group.stored_attributes [name] = att->GetDValues ()[index];
87+ break ;
88+ case ' I' : // int
89+ group.stored_attributes [name] = att->GetIValues ()[index];
90+ break ;
91+ case ' L' : // int64_t
92+ group.stored_attributes [name] = att->GetLValues ()[index];
93+ break ;
94+ case ' S' : // string
95+ group.stored_attributes [name] = att->GetSValues ()[index];
96+ break ;
97+ case ' 3' : // G4ThreeVector
98+ group.stored_attributes [name] = att->Get3Values ()[index];
99+ break ;
100+ case ' U' : // GateUniqueVolumeID::Pointer
101+ group.stored_attributes [name] = att->GetUValues ()[index];
102+ break ;
103+ }
104+ }
105+ }
106+
107+ void GateDigitizerPileupActor::EndOfEventAction (const G4Event *) {
108+ ProcessPileup ();
109+
110+ // Create output singles from piled-up groups of input singles.
111+
112+ auto &l = fThreadLocalData .Get ();
113+
114+ for (auto &[volume_id, groups] : l.volume_groups ) {
115+ if (!groups.empty ()) {
116+ // Handle all groups, except the last one, because the last group may
117+ // still get contributions from upcoming events.
118+ for (auto it = groups.begin (); it != std::prev (groups.end ()); ++it) {
119+ FillAttributeValues (*it);
120+ }
121+ groups.erase (groups.begin (), std::prev (groups.end ()));
122+ }
123+ }
124+ }
125+
126+ void GateDigitizerPileupActor::ProcessPileup () {
127+
35128 auto &lr = fThreadLocalVDigitizerData .Get ();
129+ auto &l = fThreadLocalData .Get ();
36130 auto &iter = lr.fInputIter ;
131+
37132 iter.GoToBegin ();
38133 while (!iter.IsAtEnd ()) {
39- auto &i = lr.fInputIter .fIndex ;
40- lr.fDigiAttributeFiller ->Fill (i);
134+
135+ const auto current_time = *l.time ;
136+ const auto current_edep = *l.edep ;
137+ const auto current_vol =
138+ l.volID ->get ()->GetIdUpToDepthAsHash (fGroupVolumeDepth );
139+ const auto current_index = iter.fIndex ;
140+
141+ bool added_to_existing_group = false ;
142+ auto &groups = l.volume_groups [current_vol];
143+ if (groups.size () > 0 ) {
144+ auto &group = groups.back ();
145+ if (std::abs (current_time - group.first_time ) <= fTimeWindow ) {
146+ // Accumulate deposited energy of all singles in the same time window.
147+ group.total_edep += current_edep;
148+ // Keep the attributes of the highest energy single.
149+ if (current_edep > group.highest_edep ) {
150+ group.highest_edep = current_edep;
151+ group.time = current_time;
152+ // Store all other attribute values from this single.
153+ StoreAttributeValues (group, current_index);
154+ }
155+ added_to_existing_group = true ;
156+ }
157+ }
158+
159+ // If not added to an existing group, create a new group.
160+ if (!added_to_existing_group) {
161+ typename threadLocalT::PileupGroup new_group;
162+ new_group.total_edep = current_edep;
163+ new_group.highest_edep = current_edep;
164+ new_group.first_time = current_time;
165+ new_group.time = current_time;
166+ new_group.volume_id = *l.volID ;
167+ // Store all other attribute values from this single
168+ StoreAttributeValues (new_group, current_index);
169+ groups.push_back (new_group);
170+ }
171+
41172 iter++;
42173 }
43174}
175+
176+ void GateDigitizerPileupActor::EndOfRunAction (const G4Run *) {
177+
178+ auto &l = fThreadLocalData .Get ();
179+
180+ // Output any unfinished groups that are still present.
181+ for (auto &[volume_id, groups] : l.volume_groups ) {
182+ if (!groups.empty ()) {
183+ for (auto &group : groups) {
184+ FillAttributeValues (group);
185+ }
186+ groups.erase (groups.begin (), groups.end ());
187+ }
188+ }
189+
190+ // Make sure everything is output into the root file.
191+ fOutputDigiCollection ->FillToRootIfNeeded (true );
192+ }
193+
194+ void GateDigitizerPileupActor::FillAttributeValues (
195+ const threadLocalT::PileupGroup &group) {
196+
197+ fOutputEdepAttribute ->FillDValue (group.total_edep );
198+ fOutputGlobalTimeAttribute ->FillDValue (group.time );
199+ fOutputVolumeIDAttribute ->FillUValue (group.volume_id );
200+
201+ // Fill all other stored attributes
202+ for (const auto &[name, value] : group.stored_attributes ) {
203+ auto *att = fOutputDigiCollection ->GetDigiAttribute (name);
204+ std::visit (
205+ [att](auto &&arg) {
206+ using T = std::decay_t <decltype (arg)>;
207+ if constexpr (std::is_same_v<T, double >) {
208+ att->FillDValue (arg);
209+ } else if constexpr (std::is_same_v<T, int >) {
210+ att->FillIValue (arg);
211+ } else if constexpr (std::is_same_v<T, int64_t >) {
212+ att->FillLValue (arg);
213+ } else if constexpr (std::is_same_v<T, std::string>) {
214+ att->FillSValue (arg);
215+ } else if constexpr (std::is_same_v<T, G4ThreeVector>) {
216+ att->Fill3Value (arg);
217+ } else if constexpr (std::is_same_v<T, GateUniqueVolumeID::Pointer>) {
218+ att->FillUValue (arg);
219+ }
220+ },
221+ value);
222+ }
223+ }
0 commit comments