LDMX Software
TestBeamClusterProducer.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 maxChannelID_ = ps.getParameter<int>("max_channel_nb");
14 input_collection_ = ps.getParameter<std::string>("input_collection");
15 passName_ = ps.getParameter<std::string>("input_pass_name");
16 output_collection_ = ps.getParameter<std::string>("output_collection");
17 doCleanHits_ = ps.getParameter<bool>("doCleanHits");
18 verbose_ = ps.getParameter<int>("verbosity");
19
20 timeTolerance_ = ps.getParameter<double>("time_tolerance");
21 padTime_ = ps.getParameter<double>("pad_time");
22 if (verbose_) {
23 ldmx_log(info) << "In TestBeamClusterProducer: configure done!";
24 ldmx_log(info) << "Got parameters: \nSeed threshold: " << seed_
25 << "\nClustering threshold: " << minThr_
26 << "\nMax cluster width: " << maxWidth_
27 << "\nExpected pad hit time: " << padTime_
28 << "\nMax hit time delay: " << timeTolerance_
29 << "\n\t doCleanHits = " << doCleanHits_
30 << "\nInput collection: " << input_collection_
31 << "\nInput pass name: " << passName_
32 << "\nOutput collection: " << output_collection_
33 << "\nVerbosity: " << verbose_;
34 }
35
36 return;
37}
38
40 // parameters.
41 // a cluster seeding threshold
42 // a clustering threshold -- a lower boundary for being added at all (zero
43 // suppression) -- tentative a maximum cluster width
44 //
45
46 // steps.
47 // 1. get an input collection of digi hits. at most one entry per channel.
48 // 2. access them by channel number
49 // 3. clustering:
50 // a. add first hit > seedThr to cluster(ling) . store content as
51 // localMax. track size of cluster (1) b. if not in beginning of array:
52 // add cell before while content < add next hit first hit > seedThr to
53 // cluster(ling) b. while content < add next hit first hit > seedThr to
54 // cluster(ling)
55
56 /*
57
58
59
60 //Procedure: keep going until there is a seed. walk back at most 2 steps
61 // add all the hits. clusters of up to 3 is fine.
62 // if the cluster is > 3, then we need to do something.
63 // if it's == 4, we'd want to split in the middle if there are two potential
64 seeds. retain only the first half, cases are (seed = s, n - no/noise)
65 // nsns , nssn, snsn, ssnn. nnss won't happen (max 1 step back from s,
66 unless there is nothing in front)
67 // all these are ok to split like this bcs even if ssnn--> ss and some small
68 nPE is lost, that's probably pretty negligible wrt the centroid position,
69 with two seeds in one cluster
70
71 // if it's > 4, cases are
72 // nsnsn, nsnss, nssnn, nssns, snsnn, snsns, ssnnn, ssnns.
73 // these are also all ok to just truncate after 2. and then the same check
74 outlined above will happen to the next chunk.
75
76 // so in short we can
77 // 1. seed --> addHit
78 // 2. walk back once --> addHit
79 // 3. check next: if seed+1 exists && seed +2 exists,
80 // 3a. if seed-1 is in already, stop here.
81 // 3b. else if seed+3 exists, stop here.
82 // 3c. else addHit(seed+1), addHit(seed+2)
83 // 4. if seed+1 and !seed+2 --> addHit(seed+1)
84 // 5. at this point, if clusterSize is 2 hits and seed+1 didn't exist, we
85 can afford to walk back one more step and add whatever junk was there (we
86 know it's not a seed)
87
88
89 */
90
91 if (verbose_) {
92 ldmx_log(debug)
93 << "TestBeamClusterProducer: produce() starts! Event number: "
94 << event.getEventHeader().getEventNumber();
95 }
96
97 // looper over digi hits and aggregate energy depositions for each detID
98
99 const auto digis{event.getCollection<trigscint::TestBeamHit>(
100 input_collection_, passName_)};
101
102 if (verbose_) {
103 ldmx_log(debug) << "Got digi collection " << input_collection_ << "_"
104 << passName_ << " with " << digis.size() << " entries ";
105 }
106
107 // TODO remove this once verified that the noise overlap bug is gone
108 bool doDuplicate = true;
109
110 // 1. store all the channel digi content in channel order
111 auto iDigi{0};
112 for (const auto &digi : digis) {
113 // these are unordered hits, and this collection is zero-suppressed
114 // map the index of the digi to the channel index
115 ldmx_log(debug) << "Digi has PE count " << digi.getPE() << " and energy "
116 << digi.getEnergy();
117
118 if (doCleanHits_ && digi.getQualityFlag() && digi.getQualityFlag() != 4) {
119 // allow for long pulse hits
120 ldmx_log(debug) << "Skipping hit with non-zero quality flag "
121 << digi.getQualityFlag();
122 continue;
123 }
124
125 if (digi.getPE() >
126 minThr_) { // cut on a min threshold (for a non-seeding hit to be added
127 // to seeded clusters) already here
128
129 int ID = digi.getBarID();
130 if (ID >
131 maxChannelID_) { // test beam has some uninstrumented channels (could
132 // also consider setting these to 0 in hit producer)
133 ldmx_log(debug) << "Skipping channel with bar ID = " << ID << " > "
134 << maxChannelID_ << " (max instrumented nb)";
135 continue;
136 }
137 // first check if there is a (pure) noise hit at this channel, and
138 // replace it in that case. this is a protection against a problem that
139 // shouldn't be there in the first place.
140 if (doDuplicate && hitChannelMap_.find((ID)) != hitChannelMap_.end()) {
141 int idx = ID;
142 std::map<int, int>::iterator itr = hitChannelMap_.find(idx);
143 double oldVal = digis.at(itr->second).getPE();
144 if (verbose_) {
145 ldmx_log(debug) << "Got duplicate digis for channel " << idx
146 << ", with already inserted value " << oldVal
147 << " and new " << digi.getPE();
148 }
149 if (digi.getPE() > oldVal) {
150 hitChannelMap_.erase(itr->first);
151 if (verbose_) {
152 ldmx_log(debug)
153 << "Skipped duplicate digi with smaller value for channel "
154 << idx;
155 }
156 }
157 }
158
159 // don't add in late hits
160 if (digi.getTime() > padTime_ + timeTolerance_) continue;
161
162 hitChannelMap_.insert(std::pair<int, int>(ID, iDigi));
163 // the channel number is the key, the digi list index is the value
164
165 if (verbose_) {
166 ldmx_log(debug) << "Mapping digi hit nb " << iDigi
167 << " with energy = " << digi.getEnergy()
168 << " MeV, nPE = " << digi.getPE() << " > " << minThr_
169 << " to key/channel " << ID;
170 }
171 }
172 iDigi++;
173 }
174
175 // 2. now step through all the channels in the map and cluster the hits
176
177 std::map<int, int>::iterator itr;
178
179 // Create the container to hold the digitized trigger scintillator hits.
180 std::vector<ldmx::TrigScintCluster> trigScintClusters;
181
182 // loop over channels
183 for (itr = hitChannelMap_.begin(); itr != hitChannelMap_.end(); ++itr) {
184 // this hit may have disappeared
185 if (hitChannelMap_.find(itr->first) == hitChannelMap_.end()) {
186 if (verbose_ > 1) {
187 ldmx_log(debug) << "Attempting to use removed hit at channel "
188 << itr->first << "; skipping.";
189 }
190 continue;
191 }
192
193 // i don't like this but for now, erasing elements in the map leads, as it
194 // turns out, to edge cases where i miss out on hits or run into
195 // non-existing indices. so while what i do below means that i don't need to
196 // erase hits, i'd rather find a way to do that and skip this book keeping:
197 bool hasUsed = false;
198 for (const auto &index : v_usedIndices_) {
199 if (index == itr->first) {
200 if (verbose_ > 1) {
201 ldmx_log(warn) << "Attempting to re-use hit at channel " << itr->first
202 << "; skipping.";
203 }
204 hasUsed = true;
205 }
206 }
207 if (hasUsed) continue;
208 if (verbose_ > 1) {
209 ldmx_log(debug) << "\t At hit with channel nb " << itr->first << ".";
210 }
211
212 if (hitChannelMap_.size() ==
213 0) // we removed them all..? shouldn't ever happen
214 {
215 if (verbose_)
216 ldmx_log(warn) << "Time flies, and all clusters have already been "
217 "removed! Unclear how we even got here; interfering "
218 "here to get out of the loop. ";
219 break;
220 }
221
222 trigscint::TestBeamHit digi = (trigscint::TestBeamHit)digis.at(itr->second);
223
224 // skip all until hit a seed
225 if (digi.getPE() >= seed_) {
226 if (verbose_ > 1) {
227 ldmx_log(debug) << "Seeding cluster with channel " << itr->first
228 << "; content " << digi.getPE();
229 }
230
231 // 1. add seeding hit to cluster
232
233 addHit(itr->first, digi);
234
235 if (verbose_ > 1) {
236 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
237 << itr->first << ".";
238 }
239
240 // ----- first look back one step
241
242 // we have added the hit from the neighbouring channel to the list only if
243 // it's above clustering threshold so no check needed now
244 std::map<int, int>::iterator itrBack =
245 hitChannelMap_.find(itr->first - 1);
246
247 bool hasBacked = false;
248
249 if (itrBack !=
250 hitChannelMap_.end()) { // there is an entry for the previous
251 // channel, so it had content above threshold
252 // but it wasn't enough to seed a cluster. so, unambiguous that it
253 // should be added here because it's its only chance to get in.
254
255 // need to check again for backwards hits
256 hasUsed = false;
257 for (const auto &index : v_usedIndices_) {
258 if (index == itrBack->first) {
259 if (verbose_ > 1) {
260 ldmx_log(warn) << "Attempting to re-use hit at channel "
261 << itrBack->first << "; skipping.";
262 }
263 hasUsed = true;
264 }
265 }
266 if (!hasUsed) {
267 digi = (trigscint::TestBeamHit)digis.at(itrBack->second);
268
269 // 2. add seed-1 to cluster
270 addHit(itrBack->first, digi);
271 hasBacked = true;
272
273 if (verbose_ > 1) {
274 ldmx_log(debug) << "Added -1 channel " << itrBack->first
275 << " to cluster; content " << digi.getPE();
276 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
277 << itr->first << ".";
278 }
279
280 } // if seed-1 wasn't used already
281 } // there exists a lower, unused neighbour
282
283 // 3. check next: if seed+1 exists && seed +2 exists,
284 // 3a. if seed-1 is in already, this is a case for a split, at seed. go
285 // directly to check on seed-2, don't add more here. 3b. else. addHit
286 // (seed+1) 3c. if seed+3 exists, this is a split, at seed+1. don't add
287 // more here. 3d. else addHit(seed+2)
288 // 4. if seed+1 and !seed+2 --> go to addHit(seed+1)
289
290 // --- now, step 3, 4: look ahead 1 step from seed
291
292 if (v_addedIndices_.size() < maxWidth_) {
293 // (in principle these don't need to be different iterators, but it
294 // makes the logic easier to follow)
295 std::map<int, int>::iterator itrNeighb =
296 hitChannelMap_.find(itr->first + 1);
297 if (itrNeighb !=
298 hitChannelMap_.end()) { // there is an entry for the next channel,
299 // so it had content above threshold
300 // seed+1 exists
301 // check if there is sth in position seed+2
302 if (hitChannelMap_.find(itrNeighb->first + 1) !=
303 hitChannelMap_.end()) { // a hit with that key exists, so seed+1
304 // and seed+2 exist
305 if (!hasBacked) { // there is no seed-1 in the cluster. room for at
306 // least seed+1, and for seed+2 only if there is
307 // no seed+3
308 // 3b
309 digi = (trigscint::TestBeamHit)digis.at(itrNeighb->second);
310 addHit(itrNeighb->first, digi);
311
312 if (verbose_ > 1) {
313 ldmx_log(debug)
314 << "No -1 hit. Added +1 channel " << itrNeighb->first
315 << " to cluster; content " << digi.getPE();
316 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
317 << itr->first << ".";
318 }
319
320 if (v_addedIndices_.size() < maxWidth_) {
321 if (hitChannelMap_.find(itrNeighb->first + 2) ==
322 hitChannelMap_
323 .end()) { // no seed+3. also no seed-1. so add seed+2
324 // 3d. add seed+2 to the cluster
325 itrNeighb = hitChannelMap_.find(itr->first + 2);
326 digi = (trigscint::TestBeamHit)digis.at(itrNeighb->second);
327 addHit(itrNeighb->first, digi);
328 if (verbose_ > 1) {
329 ldmx_log(debug)
330 << "No +3 hit. Added +2 channel " << itrNeighb->first
331 << " to cluster; content " << digi.getPE();
332 ldmx_log(debug)
333 << "\t itr is pointing at hit with channel nb "
334 << itr->first << ".";
335 }
336 }
337
338 } // if no seed+3 --> added seed+2
339 } // if seed-1 wasn't added
340 } // if seed+2 exists. then already added seed+1.
341 else { // so: if not, then we need to add seed+1 here. (step 4)
342 digi = (trigscint::TestBeamHit)digis.at(
343 itrNeighb->second); // itrNeighb hasn't moved since there was
344 // no seed+2
345 addHit(itrNeighb->first, digi);
346
347 if (verbose_ > 1) {
348 ldmx_log(debug)
349 << "Added +1 channel " << itrNeighb->first
350 << " as last channel to cluster; content " << digi.getPE();
351 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
352 << itr->first << ".";
353 }
354 }
355 } // if seed+1 exists
356 // 5. at this point, if clusterSize is 2 hits and seed+1 didn't exist,
357 // we can afford to walk back one more step and add whatever junk was
358 // there (we know it's not a seed)
359 else if (hasBacked &&
360 hitChannelMap_.find(itrBack->first - 1) !=
361 hitChannelMap_
362 .end()) { // seed-1 has been added, but not seed+1,
363 // and there is a hit in seed-2
364 itrBack = hitChannelMap_.find(itr->first - 2);
365 digi = (trigscint::TestBeamHit)digis.at(itrBack->second);
366 addHit(itrBack->first, digi);
367
368 if (verbose_ > 1) {
369 ldmx_log(debug) << "Added -2 channel " << itrBack->first
370 << " to cluster; content " << digi.getPE();
371 }
372 if (verbose_ > 1) {
373 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
374 << itr->first << ".";
375 }
376
377 } // check if add in seed -2
378
379 } // if adding another hit, going forward, was allowed
380
381 // done adding hits to cluster. calculate centroid
382 centroid_ /= val_; // final weighting step: divide by total
383 centroid_ -= 1; // shift back to actual channel center
384
386
387 if (verbose_ > 1) {
388 ldmx_log(debug) << "Now have " << v_addedIndices_.size()
389 << " hits in the cluster ";
390 }
391 cluster.setSeed(v_addedIndices_.at(0));
392 cluster.setIDs(v_addedIndices_);
393 cluster.setNHits(v_addedIndices_.size());
394 cluster.setCentroid(centroid_);
395 cluster.setEnergy(valE_);
396 cluster.setPE(val_);
397 cluster.setTime(time_ / val_);
398 cluster.setBeamEfrac(beamE_ / valE_);
399
400 trigScintClusters.push_back(cluster);
401
402 if (verbose_) cluster.Print();
403
404 centroid_ = 0;
405 val_ = 0;
406 valE_ = 0;
407 beamE_ = 0;
408 time_ = 0;
409 v_addedIndices_.resize(
410 0); // book keep which channels have already been added to a cluster
411
412 if (verbose_ > 1) {
413 ldmx_log(debug)
414 << "\t Finished processing of seeding hit with channel nb "
415 << itr->first << ".";
416 }
417
418 } // if content enough to seed a cluster
419
420 if (hitChannelMap_.begin() == hitChannelMap_.end()) {
421 if (verbose_)
422 ldmx_log(warn) << "Time flies, and all clusters have already been "
423 "removed! Interfering here to get out of the loop. ";
424 break;
425 }
426 } // over channels
427
428 if (trigScintClusters.size() > 0)
429 event.add(output_collection_, trigScintClusters);
430
431 hitChannelMap_.clear();
432 v_usedIndices_.resize(
433 0); // book keep which channels have already been added to a cluster
434
435 return;
436}
437
439 float ampl = hit.getPE();
440 val_ += ampl;
441 float energy = hit.getEnergy();
442 valE_ += energy;
443
444 centroid_ += (idx + 1) * ampl; // need non-zero weight of channel 0. shifting
445 // centroid back by 1 in the end
446 // this number gets divided by val at the end
447 v_addedIndices_.push_back(idx);
448
449 beamE_ += hit.getBeamEfrac() * energy;
450 if (hit.getTime() > -990.) {
451 time_ += hit.getTime() * ampl;
452 }
453
454 v_usedIndices_.push_back(idx);
455 /* // not working properly, but i'd prefer this type of solution
456 hitChannelMap_.erase( idx ) ;
457 if (verbose_ > 1 ) {
458 ldmx_log(debug) << "Removed used hit " << idx << " from list";
459 }
460 if ( hitChannelMap_.find( idx) != hitChannelMap_.end() )
461 std::cerr << "----- WARNING! Hit still present in map after removal!! ";
462 */
463 if (verbose_ > 1) {
464 ldmx_log(debug) << " In addHit, adding hit at " << idx
465 << " with amplitude " << ampl
466 << ", updating cluster to current centroid "
467 << centroid_ / val_ - 1 << " and energy " << val_
468 << ". index vector now ends with "
469 << v_addedIndices_.back();
470 }
471
472 return;
473}
474
476 ldmx_log(debug) << "Process starts!";
477
478 return;
479}
480
482 ldmx_log(debug) << "Process ends!";
483
484 return;
485}
486
487} // namespace trigscint
488
489DECLARE_PRODUCER_NS(trigscint, TestBeamClusterProducer);
#define DECLARE_PRODUCER_NS(NS, CLASS)
Macro which allows the framework to construct a producer given its name during configuration.
Clustering of TS testbeam 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 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.
virtual void configure(framework::config::Parameters &ps)
Callback for the EventProcessor to configure itself from the given set of parameters.
virtual void produce(framework::Event &event)
Process the event and put new data products into it.
bool doCleanHits_
boolean indicating whether we want to apply quality criteria from hit reconstruction
virtual void onProcessStart()
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
virtual void addHit(uint idx, trigscint::TestBeamHit hit)
add a hit at index idx to a cluster
virtual void onProcessEnd()
Callback for the EventProcessor to take any necessary action when the processing of events finishes,...
This class represents the linearised QIE output from the trigger scintillator, in charge (fC).
Definition TestBeamHit.h:24