LDMX Software
TrigScintClusterProducer.cxx
1
3
4#include <map>
5
6namespace trigscint {
7
9 seed_ = ps.get<double>("seed_threshold");
10 min_thr_ = ps.get<double>("clustering_threshold");
11 max_width_ = ps.get<int>("max_cluster_width");
12 ampl_weighting_ = ps.get<bool>("ampl_weighting");
13 input_collection_ = ps.get<std::string>("input_collection");
14 pass_name_ = ps.get<std::string>("input_pass_name");
15 output_collection_ = ps.get<std::string>("output_collection");
16 verbose_ = ps.get<int>("verbosity");
17 vert_bar_start_idx_ = ps.get<int>("vertical_bar_start_index");
18 time_tolerance_ = ps.get<double>("time_tolerance");
19 pad_time_ = ps.get<double>("pad_time");
20 if (verbose_) {
21 ldmx_log(info) << "In TrigScintClusterProducer: configure done!";
22 ldmx_log(info) << "Got parameters: \nSeed threshold: " << seed_
23 << "\nClustering threshold: " << min_thr_
24 << "\nMax cluster width: " << max_width_
25 << "\nAmplitude weighting: " << ampl_weighting_
26 << "\nExpected pad hit time: " << pad_time_
27 << "\nMax hit time delay: " << time_tolerance_
28 << "\nVertical bar start index: " << vert_bar_start_idx_
29 << "\nInput collection: " << input_collection_
30 << "\nInput pass name: " << pass_name_
31 << "\nOutput collection: " << output_collection_
32 << "\nVerbosity: " << verbose_;
33 }
34
35 return;
36}
37
39 // parameters.
40 // a cluster seeding threshold
41 // a clustering threshold -- a lower boundary for being added at all (zero
42 // suppression)
43 // a maximum cluster width
44 //
45 // steps.
46 // 1. get an input collection of digi hits. at most one entry per channel.
47 // 2. access them by channel number
48 // 3. clustering:
49 // a. add first hit > seedThr to cluster(ling) . store content as
50 // localMax. track size of cluster (1) b. if not in beginning of array:
51 // add cell before while content < add next hit first hit > seedThr to
52 // cluster(ling) b. while content < add next hit first hit > seedThr to
53 // cluster(ling)
54
55 /*
56
57 The trigger pad geometry considered for this clustering algorithm is:
58
59
60 | | | | | |
61 | 0 | 2 | 4 | 6 | 8 | ... | 48|
62
63 | 1 | 3 | 5 | 7 | 9 | ... | 49|
64 | | | | | |
65
66 with hits in channels after digi looking something like this
67
68
69 ampl: _ _ _
70 | |_ | | | |
71 ----| | |---------------| |-----------------| |------- cluster seed
72 threshold | | |_ | |_ _ | |_
73 _| | | | _| | | | _| | | _
74 | | | | | vs | | | | | vs | | | | | |
75
76
77 | | |
78 split | seeds keep | disregard keep! | just move on
79 | next cl. | (later seed | (no explicit splitting)
80 might pick
81 it up)
82
83
84 The idea being that while there could be good reasons for an electron to
85 touch three pads in a row, there is no good reason for it to cross four.
86 This is noise, or, the start of an adjacent cluster. In any case, 4 is not a
87 healthy cluster. Proximity to a seed governs which below-seed channels to
88 include. By always starting in one end but going back (at most two
89 channels), this algo guarantees symmetric treatment on both sides of the
90 seed.
91
92
93 //Procedure: keep going until there is a seed. walk back at most 2 steps
94 // add all the hits. clusters of up to 3 is fine.
95 // if the cluster is > 3, then we need to do something.
96 // if it's == 4, we'd want to split in the middle if there are two potential
97 seeds. retain only the first half, cases are (seed = s, n - no/noise)
98 // nsns , nssn, snsn, ssnn. nnss won't happen (max 1 step back from s,
99 unless there is nothing in front)
100 // all these are ok to split like this bcs even if ssnn--> ss and some small
101 nPE is lost, that's probably pretty negligible wrt the centroid position,
102 with two seeds in one cluster
103
104 // if it's > 4, cases are
105 // nsnsn, nsnss, nssnn, nssns, snsnn, snsns, ssnnn, ssnns.
106 // these are also all ok to just truncate after 2. and then the same check
107 outlined above will happen to the next chunk.
108
109 // so in short we can
110 // 1. seed --> addHit
111 // 2. walk back once --> addHit
112 // 3. check next: if seed+1 exists && seed +2 exists,
113 // 3a. if seed-1 is in already, stop here.
114 // 3b. else if seed+3 exists, stop here.
115 // 3c. else addHit(seed+1), addHit(seed+2)
116 // 4. if seed+1 and !seed+2 --> addHit(seed+1)
117 // 5. at this point, if clusterSize is 2 hits and seed+1 didn't exist, we
118 can afford to walk back one more step and add whatever junk was there (we
119 know it's not a seed)
120
121
122 */
123
124 if (verbose_) {
125 ldmx_log(debug)
126 << "TrigScintClusterProducer: produce() starts! Event number: "
127 << event.getEventHeader().getEventNumber();
128 }
129
130 // looper over digi hits and aggregate energy depositions for each detID
131
132 const auto digis{
133 event.getCollection<ldmx::TrigScintHit>(input_collection_, pass_name_)};
134
135 if (verbose_) {
136 ldmx_log(debug) << "Got digi collection " << input_collection_ << "_"
137 << pass_name_ << " with " << digis.size() << " entries ";
138 }
139
140 // TODO remove this once verified that the noise overlap bug is gone
141 bool do_duplicate = true;
142
143 // 1. store all the channel digi content in channel order
144 auto i_digi{0};
145 for (const auto& digi : digis) {
146 // these are unordered hits, and this collection is zero-suppressed
147 // map the index of the digi to the channel index
148
149 if (digi.getPE() >
150 min_thr_) { // cut on a min threshold (for a non-seeding hit to be
151 // added to seeded clusters) already here
152
153 int id = digi.getBarID();
154
155 // first check if there is a (pure) noise hit at this channel, and
156 // replace it in that case. this is a protection against a problem that
157 // shouldn't be there in the first place.
158 if (do_duplicate &&
159 hit_channel_map_.find((id)) != hit_channel_map_.end()) {
160 int idx = id;
161 std::map<int, int>::iterator itr = hit_channel_map_.find(idx);
162 double old_val = digis.at(itr->second).getPE();
163 if (verbose_) {
164 ldmx_log(debug) << "Got duplicate digis for channel " << idx
165 << ", with already inserted value " << old_val
166 << " and new " << digi.getPE();
167 }
168 if (digi.getPE() > old_val) {
169 hit_channel_map_.erase(itr->first);
170 if (verbose_) {
171 ldmx_log(debug)
172 << "Skipped duplicate digi with smaller value for channel "
173 << idx;
174 }
175 }
176 }
177
178 // don't add in late hits
179 if (digi.getTime() > pad_time_ + time_tolerance_) {
180 i_digi++;
181 continue;
182 }
183
184 hit_channel_map_.insert(std::pair<int, int>(id, i_digi));
185 // the channel number is the key, the digi list index is the value
186
187 if (verbose_) {
188 ldmx_log(debug) << "Mapping digi hit nb " << i_digi
189 << " with energy = " << digi.getEnergy()
190 << " MeV, nPE = " << digi.getPE() << " > " << min_thr_
191 << " to key/channel " << id;
192 }
193 }
194 i_digi++;
195 }
196
197 // 2. now step through all the channels in the map and cluster the hits
198
199 std::map<int, int>::iterator itr;
200
201 // Create the container to hold the digitized trigger scintillator hits.
202 std::vector<ldmx::TrigScintCluster> trig_scint_clusters;
203
204 // loop over channels
205 for (itr = hit_channel_map_.begin(); itr != hit_channel_map_.end(); ++itr) {
206 // this hit may have disappeared
207 if (hit_channel_map_.find(itr->first) == hit_channel_map_.end()) {
208 if (verbose_ > 1) {
209 ldmx_log(debug) << "Attempting to use removed hit at channel "
210 << itr->first << "; skipping.";
211 }
212 continue;
213 }
214
215 // i don't like this but for now, erasing elements in the map leads, as it
216 // turns out, to edge cases where i miss out on hits or run into
217 // non-existing indices. so while what i do below means that i don't need to
218 // erase hits, i'd rather find a way to do that and skip this book keeping:
219 bool has_used = false;
220 for (const auto& index : v_used_indices_) {
221 if (index == itr->first) {
222 if (verbose_ > 1) {
223 ldmx_log(warn) << "Attempting to re-use hit at channel " << itr->first
224 << "; skipping.";
225 }
226 has_used = true;
227 }
228 }
229 if (has_used) continue;
230 if (verbose_ > 1) {
231 ldmx_log(debug) << "\t At hit with channel nb " << itr->first << ".";
232 }
233
234 if (hit_channel_map_.size() ==
235 0) // we removed them all..? shouldn't ever happen
236 {
237 if (verbose_)
238 ldmx_log(warn) << "Time flies, and all clusters have already been "
239 "removed! Unclear how we even got here; interfering "
240 "here to get out of the loop. ";
241 break;
242 }
243
244 ldmx::TrigScintHit digi = (ldmx::TrigScintHit)digis.at(itr->second);
245
246 // skip all until hit a seed
247 if (digi.getPE() >= seed_) {
248 if (verbose_ > 1) {
249 ldmx_log(debug) << "Seeding cluster with channel " << itr->first
250 << "; content " << digi.getPE();
251 }
252
253 // 1. add seeding hit to cluster
254
255 addHit(itr->first, digi);
256
257 if (verbose_ > 1) {
258 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
259 << itr->first << ".";
260 }
261
262 // ----- first look back one step
263
264 // we have added the hit from the neighbouring channel to the list only if
265 // it's above clustering threshold so no check needed now
266 std::map<int, int>::iterator itr_back =
267 hit_channel_map_.find(itr->first - 1);
268
269 bool has_backed = false;
270
271 if (itr_back !=
272 hit_channel_map_
273 .end()) { // there is an entry for the previous
274 // channel, so it had content above threshold
275 // but it wasn't enough to seed a cluster. so, unambiguous that it
276 // should be added here because it's its only chance to get in.
277
278 // need to check again for backwards hits
279 has_used = false;
280 for (const auto& index : v_used_indices_) {
281 if (index == itr_back->first) {
282 if (verbose_ > 1) {
283 ldmx_log(warn) << "Attempting to re-use hit at channel "
284 << itr_back->first << "; skipping.";
285 }
286 has_used = true;
287 }
288 }
289 if (!has_used) {
290 digi = (ldmx::TrigScintHit)digis.at(itr_back->second);
291
292 // 2. add seed-1 to cluster
293 addHit(itr_back->first, digi);
294 has_backed = true;
295
296 if (verbose_ > 1) {
297 ldmx_log(debug) << "Added -1 channel " << itr_back->first
298 << " to cluster; content " << digi.getPE();
299 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
300 << itr->first << ".";
301 }
302
303 } // if seed-1 wasn't used already
304 } // there exists a lower, unused neighbour
305
306 // 3. check next: if seed+1 exists && seed +2 exists,
307 // 3a. if seed-1 is in already, this is a case for a split, at seed. go
308 // directly to check on seed-2, don't add more here. 3b. else. addHit
309 // (seed+1) 3c. if seed+3 exists, this is a split, at seed+1. don't add
310 // more here. 3d. else addHit(seed+2)
311 // 4. if seed+1 and !seed+2 --> go to addHit(seed+1)
312
313 // --- now, step 3, 4: look ahead 1 step from seed
314
315 if (v_added_indices_.size() < max_width_) {
316 // (in principle these don't need to be different iterators, but it
317 // makes the logic easier to follow)
318 std::map<int, int>::iterator itr_neighb =
319 hit_channel_map_.find(itr->first + 1);
320 if (itr_neighb !=
321 hit_channel_map_
322 .end()) { // there is an entry for the next channel,
323 // so it had content above threshold
324 // seed+1 exists
325 // check if there is sth in position seed+2
326 if (hit_channel_map_.find(itr_neighb->first + 1) !=
327 hit_channel_map_.end()) { // a hit with that key exists, so
328 // seed+1 and seed+2 exist
329 if (!has_backed) { // there is no seed-1 in the cluster. room for
330 // at least seed+1, and for seed+2 only if there
331 // is no seed+3
332 // 3b
333 digi = (ldmx::TrigScintHit)digis.at(itr_neighb->second);
334 addHit(itr_neighb->first, digi);
335
336 if (verbose_ > 1) {
337 ldmx_log(debug)
338 << "No -1 hit. Added +1 channel " << itr_neighb->first
339 << " to cluster; content " << digi.getPE();
340 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
341 << itr->first << ".";
342 }
343
344 if (v_added_indices_.size() < max_width_) {
345 if (hit_channel_map_.find(itr_neighb->first + 2) ==
346 hit_channel_map_
347 .end()) { // no seed+3. also no seed-1. so add seed+2
348 // 3d. add seed+2 to the cluster
349 itr_neighb = hit_channel_map_.find(itr->first + 2);
350 digi = (ldmx::TrigScintHit)digis.at(itr_neighb->second);
351 addHit(itr_neighb->first, digi);
352 if (verbose_ > 1) {
353 ldmx_log(debug)
354 << "No +3 hit. Added +2 channel " << itr_neighb->first
355 << " to cluster; content " << digi.getPE();
356 ldmx_log(debug)
357 << "\t itr is pointing at hit with channel nb "
358 << itr->first << ".";
359 }
360 }
361
362 } // if no seed+3 --> added seed+2
363 } // if seed-1 wasn't added
364 } // if seed+2 exists. then already added seed+1.
365 else { // so: if not, then we need to add seed+1 here. (step 4)
366 digi = (ldmx::TrigScintHit)digis.at(
367 itr_neighb->second); // itrNeighb hasn't moved since there was
368 // no seed+2
369 addHit(itr_neighb->first, digi);
370
371 if (verbose_ > 1) {
372 ldmx_log(debug)
373 << "Added +1 channel " << itr_neighb->first
374 << " as last channel to cluster; content " << digi.getPE();
375 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
376 << itr->first << ".";
377 }
378 }
379 } // if seed+1 exists
380 // 5. at this point, if clusterSize is 2 hits and seed+1 didn't exist,
381 // we can afford to walk back one more step and add whatever junk was
382 // there (we know it's not a seed)
383 else if (has_backed &&
384 hit_channel_map_.find(itr_back->first - 1) !=
385 hit_channel_map_
386 .end()) { // seed-1 has been added, but not seed+1,
387 // and there is a hit in seed-2
388 itr_back = hit_channel_map_.find(itr->first - 2);
389 digi = (ldmx::TrigScintHit)digis.at(itr_back->second);
390 addHit(itr_back->first, digi);
391
392 if (verbose_ > 1) {
393 ldmx_log(debug) << "Added -2 channel " << itr_back->first
394 << " to cluster; content " << digi.getPE();
395 }
396 if (verbose_ > 1) {
397 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
398 << itr->first << ".";
399 }
400
401 } // check if add in seed -2
402
403 } // if adding another hit, going forward, was allowed
404
405 // done adding hits to cluster. calculate centroid
406
407 centroid_ /=
408 sumw_; // final weighting step: divide by total amplitude sum
409 centroid_ -= 1; // shift back to actual channel center
410
412
413 if (verbose_ > 1) {
414 ldmx_log(debug) << "Now have " << v_added_indices_.size()
415 << " hits in the cluster ";
416 }
417 cluster.setSeed(v_added_indices_.at(0));
418 cluster.setIDs(v_added_indices_);
419 cluster.setNHits(v_added_indices_.size());
420 cluster.setCentroid(centroid_);
421 float cx;
422 float cy = centroid_;
423 float cz = -99999; // set to nonsense for now. could be set to module nb
424 // then in horizontal bars --> we don't know X
425 if (centroid_ < vert_bar_start_idx_) {
426 // set to nonsense in barID space. could translate to x=0 mm
427 cx = -1;
428 }
429
430 else {
431 cx = (int)((centroid_ - vert_bar_start_idx_) / 4); // start at 0
432 cy = (int)centroid_ % 4;
433 }
434 cluster.setCentroidXYZ(cx, cy, cz);
435 cluster.setEnergy(val_e_);
436 cluster.setPE(val_);
437 cluster.setTime(time_ / val_);
438 cluster.setBeamEfrac(beam_e_ / val_e_);
439
440 trig_scint_clusters.push_back(cluster);
441
442 ldmx_log(trace) << cluster;
443
444 centroid_ = 0;
445 centroid_x_ = -1;
446 centroid_y_ = -1;
447 val_ = 0;
448 val_e_ = 0;
449 beam_e_ = 0;
450 time_ = 0;
451 sumw_ = 0;
452 // book keep which channels have already been added to a cluster
453 v_added_indices_.resize(0);
454
455 if (verbose_ > 1) {
456 ldmx_log(debug)
457 << "\t Finished processing of seeding hit with channel nb "
458 << itr->first << ".";
459 }
460
461 } // if content enough to seed a cluster
462
463 if (hit_channel_map_.begin() == hit_channel_map_.end()) {
464 if (verbose_)
465 ldmx_log(warn) << "Time flies, and all clusters have already been "
466 "removed! Interfering here to get out of the loop. ";
467 break;
468 }
469 } // over channels
470
471 event.add(output_collection_, trig_scint_clusters);
472
473 hit_channel_map_.clear();
474 // book keep which channels have already been added to a cluster
475 v_used_indices_.resize(0);
476
477 return;
478}
479
481 float ampl = hit.getPE();
482 float w = 1;
483 if (ampl_weighting_) { // if choosing to PE-weight centroid positions
484 w = ampl;
485 }
486
487 float energy = hit.getEnergy();
488 val_e_ += energy;
489
490 val_ += ampl;
491 centroid_ += (idx + 1) * w; // need non-zero weight of channel 0. shifting
492 // centroid back by 1 in the end
493 // this number gets divided by val at the end
494
495 sumw_ += w;
496
497 v_added_indices_.push_back(idx);
498
499 beam_e_ += hit.getBeamEfrac() * energy;
500 if (hit.getTime() > -990.) {
501 time_ += hit.getTime() * ampl;
502 }
503
504 v_used_indices_.push_back(idx);
505 /* // not working properly, but i'd prefer this type of solution
506 hit_channel_map_.erase( idx ) ;
507 if (verbose_ > 1 ) {
508 ldmx_log(debug) << "Removed used hit " << idx << " from list";
509 }
510 if ( hit_channel_map_.find( idx) != hit_channel_map_.end() )
511 std::cerr << "----- WARNING! Hit still present in map after removal!! ";
512 */
513 if (verbose_ > 1) {
514 ldmx_log(debug) << " In addHit, adding hit at " << idx
515 << " with amplitude " << ampl
516 << ", updating cluster to current centroid "
517 << centroid_ / val_ - 1 << " and energy " << val_
518 << ". index vector now ends with "
519 << v_added_indices_.back();
520 }
521
522 return;
523}
524
526 ldmx_log(debug) << "Process starts!";
527
528 return;
529}
530
532 ldmx_log(debug) << "Process ends!";
533
534 return;
535}
536
537} // namespace trigscint
538
#define DECLARE_PRODUCER(CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Clustering of trigger scintillator hits.
Implements an event buffer system for storing event data.
Definition Event.h:40
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
float getEnergy() const
Get the calorimetric energy of the hit, corrected for sampling factors [MeV].
float getTime() const
Get the time of the hit [ns].
Stores cluster information from the trigger scintillator pads.
void setIDs(std::vector< unsigned int > &hitIDs)
The channel numbers of hits forming the cluster.
void setNHits(int nHits)
The number of hits forming the cluster.
void setCentroidXYZ(double x, double y, double z)
The cluster centroid in x,y,z.
void setEnergy(double energy)
Set the cluster energy.
void setCentroid(double centroid)
void setPE(float PE)
Set the cluster photoelectron count (PE)
void setBeamEfrac(float e)
Set beam energy fraction of hit.
void setTime(float t)
Set time of hit.
float getPE() const
Get the hit pe.
float getBeamEfrac() const
Get the beam energy fraction.
void onProcessStart() override
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
void produce(framework::Event &event) override
Process the event and put new data products into it.
void onProcessEnd() override
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
virtual void addHit(uint idx, ldmx::TrigScintHit hit)
add a hit at index idx to a cluster
void configure(framework::config::Parameters &ps) override
Callback for the EventProcessor to configure itself from the given set of parameters.