LDMX Software
TrigScintClusterProducer.cxx
1
3
4#include <iterator> // std::next
5#include <map>
6
7namespace trigscint {
8
10 seed_ = ps.getParameter<double>("seed_threshold");
11 minThr_ = ps.getParameter<double>("clustering_threshold");
12 maxWidth_ = ps.getParameter<int>("max_cluster_width");
13 input_collection_ = ps.getParameter<std::string>("input_collection");
14 passName_ = ps.getParameter<std::string>("input_pass_name");
15 output_collection_ = ps.getParameter<std::string>("output_collection");
16 verbose_ = ps.getParameter<int>("verbosity");
17 vertBarStartIdx_ = ps.getParameter<int>("vertical_bar_start_index");
18 timeTolerance_ = ps.getParameter<double>("time_tolerance");
19 padTime_ = ps.getParameter<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: " << minThr_
24 << "\nMax cluster width: " << maxWidth_
25 << "\nExpected pad hit time: " << padTime_
26 << "\nMax hit time delay: " << timeTolerance_
27 << "\nVertical bar start index: " << vertBarStartIdx_
28 << "\nInput collection: " << input_collection_
29 << "\nInput pass name: " << passName_
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_, passName_)};
133
134 if (verbose_) {
135 ldmx_log(debug) << "Got digi collection " << input_collection_ << "_"
136 << passName_ << " with " << digis.size() << " entries ";
137 }
138
139 // TODO remove this once verified that the noise overlap bug is gone
140 bool doDuplicate = true;
141
142 // 1. store all the channel digi content in channel order
143 auto iDigi{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 minThr_) { // cut on a min threshold (for a non-seeding hit to be added
150 // 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 (doDuplicate && hitChannelMap_.find((ID)) != hitChannelMap_.end()) {
158 int idx = ID;
159 std::map<int, int>::iterator itr = hitChannelMap_.find(idx);
160 double oldVal = digis.at(itr->second).getPE();
161 if (verbose_) {
162 ldmx_log(debug) << "Got duplicate digis for channel " << idx
163 << ", with already inserted value " << oldVal
164 << " and new " << digi.getPE();
165 }
166 if (digi.getPE() > oldVal) {
167 hitChannelMap_.erase(itr->first);
168 if (verbose_) {
169 ldmx_log(debug)
170 << "Skipped duplicate digi with smaller value for channel "
171 << idx;
172 }
173 }
174 }
175
176 // don't add in late hits
177 if (digi.getTime() > padTime_ + timeTolerance_) continue;
178
179 hitChannelMap_.insert(std::pair<int, int>(ID, iDigi));
180 // the channel number is the key, the digi list index is the value
181
182 if (verbose_) {
183 ldmx_log(debug) << "Mapping digi hit nb " << iDigi
184 << " with energy = " << digi.getEnergy()
185 << " MeV, nPE = " << digi.getPE() << " > " << minThr_
186 << " to key/channel " << ID;
187 }
188 }
189 iDigi++;
190 }
191
192 // 2. now step through all the channels in the map and cluster the hits
193
194 std::map<int, int>::iterator itr;
195
196 // Create the container to hold the digitized trigger scintillator hits.
197 std::vector<ldmx::TrigScintCluster> trigScintClusters;
198
199 // loop over channels
200 for (itr = hitChannelMap_.begin(); itr != hitChannelMap_.end(); ++itr) {
201 // this hit may have disappeared
202 if (hitChannelMap_.find(itr->first) == hitChannelMap_.end()) {
203 if (verbose_ > 1) {
204 ldmx_log(debug) << "Attempting to use removed hit at channel "
205 << itr->first << "; skipping.";
206 }
207 continue;
208 }
209
210 // i don't like this but for now, erasing elements in the map leads, as it
211 // turns out, to edge cases where i miss out on hits or run into
212 // non-existing indices. so while what i do below means that i don't need to
213 // erase hits, i'd rather find a way to do that and skip this book keeping:
214 bool hasUsed = false;
215 for (const auto &index : v_usedIndices_) {
216 if (index == itr->first) {
217 if (verbose_ > 1) {
218 ldmx_log(warn) << "Attempting to re-use hit at channel " << itr->first
219 << "; skipping.";
220 }
221 hasUsed = true;
222 }
223 }
224 if (hasUsed) continue;
225 if (verbose_ > 1) {
226 ldmx_log(debug) << "\t At hit with channel nb " << itr->first << ".";
227 }
228
229 if (hitChannelMap_.size() ==
230 0) // we removed them all..? shouldn't ever happen
231 {
232 if (verbose_)
233 ldmx_log(warn) << "Time flies, and all clusters have already been "
234 "removed! Unclear how we even got here; interfering "
235 "here to get out of the loop. ";
236 break;
237 }
238
239 ldmx::TrigScintHit digi = (ldmx::TrigScintHit)digis.at(itr->second);
240
241 // skip all until hit a seed
242 if (digi.getPE() >= seed_) {
243 if (verbose_ > 1) {
244 ldmx_log(debug) << "Seeding cluster with channel " << itr->first
245 << "; content " << digi.getPE();
246 }
247
248 // 1. add seeding hit to cluster
249
250 addHit(itr->first, digi);
251
252 if (verbose_ > 1) {
253 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
254 << itr->first << ".";
255 }
256
257 // ----- first look back one step
258
259 // we have added the hit from the neighbouring channel to the list only if
260 // it's above clustering threshold so no check needed now
261 std::map<int, int>::iterator itrBack =
262 hitChannelMap_.find(itr->first - 1);
263
264 bool hasBacked = false;
265
266 if (itrBack !=
267 hitChannelMap_.end()) { // there is an entry for the previous
268 // channel, so it had content above threshold
269 // but it wasn't enough to seed a cluster. so, unambiguous that it
270 // should be added here because it's its only chance to get in.
271
272 // need to check again for backwards hits
273 hasUsed = false;
274 for (const auto &index : v_usedIndices_) {
275 if (index == itrBack->first) {
276 if (verbose_ > 1) {
277 ldmx_log(warn) << "Attempting to re-use hit at channel "
278 << itrBack->first << "; skipping.";
279 }
280 hasUsed = true;
281 }
282 }
283 if (!hasUsed) {
284 digi = (ldmx::TrigScintHit)digis.at(itrBack->second);
285
286 // 2. add seed-1 to cluster
287 addHit(itrBack->first, digi);
288 hasBacked = true;
289
290 if (verbose_ > 1) {
291 ldmx_log(debug) << "Added -1 channel " << itrBack->first
292 << " to cluster; content " << digi.getPE();
293 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
294 << itr->first << ".";
295 }
296
297 } // if seed-1 wasn't used already
298 } // there exists a lower, unused neighbour
299
300 // 3. check next: if seed+1 exists && seed +2 exists,
301 // 3a. if seed-1 is in already, this is a case for a split, at seed. go
302 // directly to check on seed-2, don't add more here. 3b. else. addHit
303 // (seed+1) 3c. if seed+3 exists, this is a split, at seed+1. don't add
304 // more here. 3d. else addHit(seed+2)
305 // 4. if seed+1 and !seed+2 --> go to addHit(seed+1)
306
307 // --- now, step 3, 4: look ahead 1 step from seed
308
309 if (v_addedIndices_.size() < maxWidth_) {
310 // (in principle these don't need to be different iterators, but it
311 // makes the logic easier to follow)
312 std::map<int, int>::iterator itrNeighb =
313 hitChannelMap_.find(itr->first + 1);
314 if (itrNeighb !=
315 hitChannelMap_.end()) { // there is an entry for the next channel,
316 // so it had content above threshold
317 // seed+1 exists
318 // check if there is sth in position seed+2
319 if (hitChannelMap_.find(itrNeighb->first + 1) !=
320 hitChannelMap_.end()) { // a hit with that key exists, so seed+1
321 // and seed+2 exist
322 if (!hasBacked) { // there is no seed-1 in the cluster. room for at
323 // least seed+1, and for seed+2 only if there is
324 // no seed+3
325 // 3b
326 digi = (ldmx::TrigScintHit)digis.at(itrNeighb->second);
327 addHit(itrNeighb->first, digi);
328
329 if (verbose_ > 1) {
330 ldmx_log(debug)
331 << "No -1 hit. Added +1 channel " << itrNeighb->first
332 << " to cluster; content " << digi.getPE();
333 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
334 << itr->first << ".";
335 }
336
337 if (v_addedIndices_.size() < maxWidth_) {
338 if (hitChannelMap_.find(itrNeighb->first + 2) ==
339 hitChannelMap_
340 .end()) { // no seed+3. also no seed-1. so add seed+2
341 // 3d. add seed+2 to the cluster
342 itrNeighb = hitChannelMap_.find(itr->first + 2);
343 digi = (ldmx::TrigScintHit)digis.at(itrNeighb->second);
344 addHit(itrNeighb->first, digi);
345 if (verbose_ > 1) {
346 ldmx_log(debug)
347 << "No +3 hit. Added +2 channel " << itrNeighb->first
348 << " to cluster; content " << digi.getPE();
349 ldmx_log(debug)
350 << "\t itr is pointing at hit with channel nb "
351 << itr->first << ".";
352 }
353 }
354
355 } // if no seed+3 --> added seed+2
356 } // if seed-1 wasn't added
357 } // if seed+2 exists. then already added seed+1.
358 else { // so: if not, then we need to add seed+1 here. (step 4)
359 digi = (ldmx::TrigScintHit)digis.at(
360 itrNeighb->second); // itrNeighb hasn't moved since there was
361 // no seed+2
362 addHit(itrNeighb->first, digi);
363
364 if (verbose_ > 1) {
365 ldmx_log(debug)
366 << "Added +1 channel " << itrNeighb->first
367 << " as last channel to cluster; content " << digi.getPE();
368 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
369 << itr->first << ".";
370 }
371 }
372 } // if seed+1 exists
373 // 5. at this point, if clusterSize is 2 hits and seed+1 didn't exist,
374 // we can afford to walk back one more step and add whatever junk was
375 // there (we know it's not a seed)
376 else if (hasBacked &&
377 hitChannelMap_.find(itrBack->first - 1) !=
378 hitChannelMap_
379 .end()) { // seed-1 has been added, but not seed+1,
380 // and there is a hit in seed-2
381 itrBack = hitChannelMap_.find(itr->first - 2);
382 digi = (ldmx::TrigScintHit)digis.at(itrBack->second);
383 addHit(itrBack->first, digi);
384
385 if (verbose_ > 1) {
386 ldmx_log(debug) << "Added -2 channel " << itrBack->first
387 << " to cluster; content " << digi.getPE();
388 }
389 if (verbose_ > 1) {
390 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
391 << itr->first << ".";
392 }
393
394 } // check if add in seed -2
395
396 } // if adding another hit, going forward, was allowed
397
398 // done adding hits to cluster. calculate centroid
399 centroid_ /= val_; // final weighting step: divide by total
400 centroid_ -= 1; // shift back to actual channel center
401
403
404 if (verbose_ > 1) {
405 ldmx_log(debug) << "Now have " << v_addedIndices_.size()
406 << " hits in the cluster ";
407 }
408 cluster.setSeed(v_addedIndices_.at(0));
409 cluster.setIDs(v_addedIndices_);
410 cluster.setNHits(v_addedIndices_.size());
411 cluster.setCentroid(centroid_);
412 float cx;
413 float cy = centroid_;
414 float cz = -99999; // set to nonsense for now. could be set to module nb
415 if (centroid_ <
416 vertBarStartIdx_) // then in horizontal bars --> we don't know X
417 cx = -1; // set to nonsense in barID space. could translate to x=0 mm
418 else {
419 cx = (int)((centroid_ - vertBarStartIdx_) / 4); // start at 0
420 cy = (int)centroid_ % 4;
421 }
422 cluster.setCentroidXYZ(cx, cy, cz);
423 cluster.setEnergy(valE_);
424 cluster.setPE(val_);
425 cluster.setTime(time_ / val_);
426 cluster.setBeamEfrac(beamE_ / valE_);
427
428 trigScintClusters.push_back(cluster);
429
430 if (verbose_) cluster.Print();
431
432 centroid_ = 0;
433 centroidX_ = -1;
434 centroidY_ = -1;
435 val_ = 0;
436 valE_ = 0;
437 beamE_ = 0;
438 time_ = 0;
439 v_addedIndices_.resize(
440 0); // book keep which channels have already been added to a cluster
441
442 if (verbose_ > 1) {
443 ldmx_log(debug)
444 << "\t Finished processing of seeding hit with channel nb "
445 << itr->first << ".";
446 }
447
448 } // if content enough to seed a cluster
449
450 if (hitChannelMap_.begin() == hitChannelMap_.end()) {
451 if (verbose_)
452 ldmx_log(warn) << "Time flies, and all clusters have already been "
453 "removed! Interfering here to get out of the loop. ";
454 break;
455 }
456 } // over channels
457
458 if (trigScintClusters.size() > 0)
459 event.add(output_collection_, trigScintClusters);
460
461 hitChannelMap_.clear();
462 v_usedIndices_.resize(
463 0); // book keep which channels have already been added to a cluster
464
465 return;
466}
467
469 float ampl = hit.getPE();
470 val_ += ampl;
471 float energy = hit.getEnergy();
472 valE_ += energy;
473
474 centroid_ += (idx + 1) * ampl; // need non-zero weight of channel 0. shifting
475 // centroid back by 1 in the end
476 // this number gets divided by val at the end
477 v_addedIndices_.push_back(idx);
478
479 beamE_ += hit.getBeamEfrac() * energy;
480 if (hit.getTime() > -990.) {
481 time_ += hit.getTime() * ampl;
482 }
483
484 v_usedIndices_.push_back(idx);
485 /* // not working properly, but i'd prefer this type of solution
486 hitChannelMap_.erase( idx ) ;
487 if (verbose_ > 1 ) {
488 ldmx_log(debug) << "Removed used hit " << idx << " from list";
489 }
490 if ( hitChannelMap_.find( idx) != hitChannelMap_.end() )
491 std::cerr << "----- WARNING! Hit still present in map after removal!! ";
492 */
493 if (verbose_ > 1) {
494 ldmx_log(debug) << " In addHit, adding hit at " << idx
495 << " with amplitude " << ampl
496 << ", updating cluster to current centroid "
497 << centroid_ / val_ - 1 << " and energy " << val_
498 << ". index vector now ends with "
499 << v_addedIndices_.back();
500 }
501
502 return;
503}
504
506 ldmx_log(debug) << "Process starts!";
507
508 return;
509}
510
512 ldmx_log(debug) << "Process ends!";
513
514 return;
515}
516
517} // namespace trigscint
518
519DECLARE_PRODUCER_NS(trigscint, TrigScintClusterProducer);
#define DECLARE_PRODUCER_NS(NS, 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:41
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:27
T getParameter(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:89
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 Print(Option_t *option="") const
Print a description of this object.
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.