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