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