LDMX Software
TestBeamClusterProducer.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 max_channel_id_ = ps.get<int>("max_channel_nb");
14 input_collection_ = ps.get<std::string>("input_collection");
15 pass_name_ = ps.get<std::string>("input_pass_name");
16 output_collection_ = ps.get<std::string>("output_collection");
17 do_clean_hits_ = ps.get<bool>("doCleanHits");
18 verbose_ = ps.get<int>("verbosity");
19
20 time_tolerance_ = ps.get<double>("time_tolerance");
21 pad_time_ = ps.get<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: " << min_thr_
26 << "\nMax cluster width: " << max_width_
27 << "\nExpected pad hit time: " << pad_time_
28 << "\nMax hit time delay: " << time_tolerance_
29 << "\n\t doCleanHits = " << do_clean_hits_
30 << "\nInput collection: " << input_collection_
31 << "\nInput pass name: " << pass_name_
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) << "produce() starts! Event number: "
93 << event.getEventHeader().getEventNumber();
94 }
95
96 // looper over digi hits and aggregate energy depositions for each detID
97
98 const auto digis{event.getCollection<trigscint::TestBeamHit>(
99 input_collection_, pass_name_)};
100
101 if (verbose_) {
102 ldmx_log(debug) << "Got digi collection " << input_collection_ << "_"
103 << pass_name_ << " with " << digis.size() << " entries ";
104 }
105
106 // TODO remove this once verified that the noise overlap bug is gone
107 bool do_duplicate = true;
108
109 // 1. store all the channel digi content in channel order
110 auto i_digi{0};
111 for (const auto &digi : digis) {
112 // these are unordered hits, and this collection is zero-suppressed
113 // map the index of the digi to the channel index
114 ldmx_log(debug) << "Digi has PE count " << digi.getPE() << " and energy "
115 << digi.getEnergy();
116
117 if (do_clean_hits_ && digi.getQualityFlag() && digi.getQualityFlag() != 4) {
118 // allow for long pulse hits
119 ldmx_log(debug) << "Skipping hit with non-zero quality flag "
120 << digi.getQualityFlag();
121 continue;
122 }
123 // cut on a min threshold (for a non-seeding hit to be added
124 // to seeded clusters) already here
125 if (digi.getPE() > min_thr_) {
126 int id = digi.getBarID();
127 if (id > max_channel_id_) { // test beam has some uninstrumented channels
128 // (could also consider setting these to 0 in
129 // hit producer)
130 ldmx_log(debug) << "Skipping channel with bar ID = " << id << " > "
131 << max_channel_id_ << " (max instrumented nb)";
132 continue;
133 }
134 // first check if there is a (pure) noise hit at this channel, and
135 // replace it in that case. this is a protection against a problem that
136 // shouldn't be there in the first place.
137 if (do_duplicate &&
138 hit_channel_map_.find((id)) != hit_channel_map_.end()) {
139 int idx = id;
140 std::map<int, int>::iterator itr = hit_channel_map_.find(idx);
141 double old_val = digis.at(itr->second).getPE();
142 if (verbose_) {
143 ldmx_log(debug) << "Got duplicate digis for channel " << idx
144 << ", with already inserted value " << old_val
145 << " and new " << digi.getPE();
146 }
147 if (digi.getPE() > old_val) {
148 hit_channel_map_.erase(itr->first);
149 if (verbose_) {
150 ldmx_log(debug)
151 << "Skipped duplicate digi with smaller value for channel "
152 << idx;
153 }
154 }
155 }
156
157 // don't add in late hits
158 if (digi.getTime() > pad_time_ + time_tolerance_) continue;
159
160 hit_channel_map_.insert(std::pair<int, int>(id, i_digi));
161 // the channel number is the key, the digi list index is the value
162
163 if (verbose_) {
164 ldmx_log(debug) << "Mapping digi hit nb " << i_digi
165 << " with energy = " << digi.getEnergy()
166 << " MeV, nPE = " << digi.getPE() << " > " << min_thr_
167 << " to key/channel " << id;
168 }
169 }
170 i_digi++;
171 }
172
173 // 2. now step through all the channels in the map and cluster the hits
174
175 std::map<int, int>::iterator itr;
176
177 // Create the container to hold the digitized trigger scintillator hits.
178 std::vector<ldmx::TrigScintCluster> trig_scint_clusters;
179
180 // loop over channels
181 for (itr = hit_channel_map_.begin(); itr != hit_channel_map_.end(); ++itr) {
182 // this hit may have disappeared
183 if (hit_channel_map_.find(itr->first) == hit_channel_map_.end()) {
184 if (verbose_ > 1) {
185 ldmx_log(debug) << "Attempting to use removed hit at channel "
186 << itr->first << "; skipping.";
187 }
188 continue;
189 }
190
191 // i don't like this but for now, erasing elements in the map leads, as it
192 // turns out, to edge cases where i miss out on hits or run into
193 // non-existing indices. so while what i do below means that i don't need to
194 // erase hits, i'd rather find a way to do that and skip this book keeping:
195 bool has_used = false;
196 for (const auto &index : v_used_indices_) {
197 if (index == itr->first) {
198 if (verbose_ > 1) {
199 ldmx_log(warn) << "Attempting to re-use hit at channel " << itr->first
200 << "; skipping.";
201 }
202 has_used = true;
203 }
204 }
205 if (has_used) continue;
206 if (verbose_ > 1) {
207 ldmx_log(debug) << "\t At hit with channel nb " << itr->first << ".";
208 }
209
210 if (hit_channel_map_.size() ==
211 0) // we removed them all..? shouldn't ever happen
212 {
213 if (verbose_)
214 ldmx_log(warn) << "Time flies, and all clusters have already been "
215 "removed! Unclear how we even got here; interfering "
216 "here to get out of the loop. ";
217 break;
218 }
219
220 trigscint::TestBeamHit digi = (trigscint::TestBeamHit)digis.at(itr->second);
221
222 // skip all until hit a seed
223 if (digi.getPE() >= seed_) {
224 if (verbose_ > 1) {
225 ldmx_log(debug) << "Seeding cluster with channel " << itr->first
226 << "; content " << digi.getPE();
227 }
228
229 // 1. add seeding hit to cluster
230
231 addHit(itr->first, digi);
232
233 if (verbose_ > 1) {
234 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
235 << itr->first << ".";
236 }
237
238 // ----- first look back one step
239
240 // we have added the hit from the neighbouring channel to the list only if
241 // it's above clustering threshold so no check needed now
242 std::map<int, int>::iterator itr_back =
243 hit_channel_map_.find(itr->first - 1);
244
245 bool has_backed = false;
246
247 if (itr_back !=
248 hit_channel_map_
249 .end()) { // there is an entry for the previous
250 // channel, so it had content above threshold
251 // but it wasn't enough to seed a cluster. so, unambiguous that it
252 // should be added here because it's its only chance to get in.
253
254 // need to check again for backwards hits
255 has_used = false;
256 for (const auto &index : v_used_indices_) {
257 if (index == itr_back->first) {
258 if (verbose_ > 1) {
259 ldmx_log(warn) << "Attempting to re-use hit at channel "
260 << itr_back->first << "; skipping.";
261 }
262 has_used = true;
263 }
264 }
265 if (!has_used) {
266 digi = (trigscint::TestBeamHit)digis.at(itr_back->second);
267
268 // 2. add seed-1 to cluster
269 addHit(itr_back->first, digi);
270 has_backed = true;
271
272 if (verbose_ > 1) {
273 ldmx_log(debug) << "Added -1 channel " << itr_back->first
274 << " to cluster; content " << digi.getPE();
275 ldmx_log(debug) << "\t itr is pointing at hit with channel nb "
276 << itr->first << ".";
277 }
278
279 } // if seed-1 wasn't used already
280 } // there exists a lower, unused neighbour
281
282 // 3. check next: if seed+1 exists && seed +2 exists,
283 // 3a. if seed-1 is in already, this is a case for a split, at seed. go
284 // directly to check on seed-2, don't add more here. 3b. else. addHit
285 // (seed+1) 3c. if seed+3 exists, this is a split, at seed+1. don't add
286 // more here. 3d. else addHit(seed+2)
287 // 4. if seed+1 and !seed+2 --> go to addHit(seed+1)
288
289 // --- now, step 3, 4: look ahead 1 step from seed
290
291 if (v_added_indices_.size() < max_width_) {
292 // (in principle these don't need to be different iterators, but it
293 // makes the logic easier to follow)
294 std::map<int, int>::iterator itr_neighb =
295 hit_channel_map_.find(itr->first + 1);
296 if (itr_neighb !=
297 hit_channel_map_
298 .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 (hit_channel_map_.find(itr_neighb->first + 1) !=
303 hit_channel_map_.end()) { // a hit with that key exists, so
304 // seed+1 and seed+2 exist
305 if (!has_backed) { // there is no seed-1 in the cluster. room for
306 // at least seed+1, and for seed+2 only if there
307 // is no seed+3
308 // 3b
309 digi = (trigscint::TestBeamHit)digis.at(itr_neighb->second);
310 addHit(itr_neighb->first, digi);
311
312 if (verbose_ > 1) {
313 ldmx_log(debug)
314 << "No -1 hit. Added +1 channel " << itr_neighb->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_added_indices_.size() < max_width_) {
321 if (hit_channel_map_.find(itr_neighb->first + 2) ==
322 hit_channel_map_
323 .end()) { // no seed+3. also no seed-1. so add seed+2
324 // 3d. add seed+2 to the cluster
325 itr_neighb = hit_channel_map_.find(itr->first + 2);
326 digi = (trigscint::TestBeamHit)digis.at(itr_neighb->second);
327 addHit(itr_neighb->first, digi);
328 if (verbose_ > 1) {
329 ldmx_log(debug)
330 << "No +3 hit. Added +2 channel " << itr_neighb->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 itr_neighb->second); // itrNeighb hasn't moved since there was
344 // no seed+2
345 addHit(itr_neighb->first, digi);
346
347 if (verbose_ > 1) {
348 ldmx_log(debug)
349 << "Added +1 channel " << itr_neighb->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 (has_backed &&
360 hit_channel_map_.find(itr_back->first - 1) !=
361 hit_channel_map_
362 .end()) { // seed-1 has been added, but not seed+1,
363 // and there is a hit in seed-2
364 itr_back = hit_channel_map_.find(itr->first - 2);
365 digi = (trigscint::TestBeamHit)digis.at(itr_back->second);
366 addHit(itr_back->first, digi);
367
368 if (verbose_ > 1) {
369 ldmx_log(debug) << "Added -2 channel " << itr_back->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_added_indices_.size()
389 << " hits in the cluster ";
390 }
391 cluster.setSeed(v_added_indices_.at(0));
392 cluster.setIDs(v_added_indices_);
393 cluster.setNHits(v_added_indices_.size());
394 cluster.setCentroid(centroid_);
395 cluster.setEnergy(val_e_);
396 cluster.setPE(val_);
397 cluster.setTime(time_ / val_);
398 cluster.setBeamEfrac(beam_e_ / val_e_);
399
400 trig_scint_clusters.push_back(cluster);
401
402 ldmx_log(trace) << cluster;
403
404 centroid_ = 0;
405 val_ = 0;
406 val_e_ = 0;
407 beam_e_ = 0;
408 time_ = 0;
409 v_added_indices_.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 (hit_channel_map_.begin() == hit_channel_map_.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 (trig_scint_clusters.size() > 0)
429 event.add(output_collection_, trig_scint_clusters);
430
431 hit_channel_map_.clear();
432 v_used_indices_.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 val_e_ += 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_added_indices_.push_back(idx);
448
449 beam_e_ += hit.getBeamEfrac() * energy;
450 if (hit.getTime() > -990.) {
451 time_ += hit.getTime() * ampl;
452 }
453
454 v_used_indices_.push_back(idx);
455 /* // not working properly, but i'd prefer this type of solution
456 hit_channel_map_.erase( idx ) ;
457 if (verbose_ > 1 ) {
458 ldmx_log(debug) << "Removed used hit " << idx << " from list";
459 }
460 if ( hit_channel_map_.find( idx) != hit_channel_map_.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_added_indices_.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
#define DECLARE_PRODUCER(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: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 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.
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.
virtual void onProcessStart()
Callback for the EventProcessor to take any necessary action when the processing of events starts,...
bool do_clean_hits_
boolean indicating whether we want to apply quality criteria from hit reconstruction
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