MUSIC unknown
Analysis for the MUSIC active-target ionization chamber
Loading...
Searching...
No Matches
EventBuilder.cpp
Go to the documentation of this file.
1#include "EventBuilder.hpp"
2#include "EventsSummary.hpp"
3
5 for (Int_t k = 0; k < 18; k++) {
6 e.leftdE[k] = 0;
7 e.rightdE[k] = 0;
8 e.totaldE[k] = 0;
9 }
10 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++)
11 e.hits[k] = 0;
12 e.cathode = -1;
13 e.grid = 0;
14 e.flags_or = 0;
15 e.had_cathode = kFALSE;
16 e.ref_ts = 0;
17}
18
20 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++) {
21 p.timestamps[k] = 0;
22 p.energies[k] = 0;
23 p.flags[k] = 0;
24 }
25}
26
27Bool_t EventBuilder::ShouldKeepHit(ULong64_t cand_ts, ULong64_t prev_ts,
28 UShort_t cand_energy, UShort_t prev_energy,
29 ULong64_t ref_ts, DedupStrategy strategy) {
30 if (strategy == kSMALLEST_ENERGY)
31 return cand_energy < prev_energy;
32 if (strategy == kLARGEST_ENERGY)
33 return cand_energy > prev_energy;
34 if (strategy == kLATEST_TIMESTAMP)
35 return cand_ts > prev_ts;
36
37 ULong64_t cand_dt = (cand_ts > ref_ts) ? cand_ts - ref_ts : ref_ts - cand_ts;
38 ULong64_t prev_dt = (prev_ts > ref_ts) ? prev_ts - ref_ts : ref_ts - prev_ts;
39 return cand_dt < prev_dt;
40}
41
43 SlotMap sm;
45 for (std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
47 it != Constants::ActiveChannelMap().end(); ++it) {
48 Int_t board = it->first.first;
49 Int_t channel = it->first.second;
50 if (board < 0 || board >= Constants::ActiveNBoards())
51 continue;
52 if (channel < 0 || channel >= Constants::ActiveNChannels())
53 continue;
54 Int_t idx = board * Constants::ActiveNChannels() + channel;
55 const TString &name = it->second;
56 if (name == "Strip0") {
58 } else if (name == "Strip17") {
60 } else if (name == "Cathode") {
62 } else if (name == "Grid") {
64 } else if (name.Length() >= 2 && (name[0] == 'L' || name[0] == 'R')) {
65 TString numstr = name(1, name.Length() - 1);
66 if (numstr.IsDigit()) {
67 Int_t s = numstr.Atoi();
68 if (s >= 1 && s <= 16)
69 sm[idx] = (name[0] == 'L') ? s : (s + 16);
70 }
71 }
72 }
73 return sm;
74}
75
77 ULong64_t ref_ts, Int_t slot, UShort_t energy,
78 ULong64_t timestamp, UInt_t flags,
79 DedupStrategy strategy) {
80 // Slots are 0..ARR_SLOT_GRID by construction (BuildSlotMap); guard
81 // defensively so an out-of-range slot can never index the arrays below.
82 if (slot < 0 || slot >= Constants::N_ARR_SLOTS)
83 return;
84 e.flags_or |= flags;
85
86 Bool_t wins;
87 if (e.hits[slot] == 0) {
88 wins = kTRUE;
89 } else if (pc) {
90 wins = ShouldKeepHit(timestamp, pc->timestamps[slot], energy,
91 pc->energies[slot], ref_ts, strategy);
92 } else {
93 wins = kFALSE;
94 }
95
96 if (wins) {
98 e.totaldE[0] = energy;
99 else if (slot <= 16)
100 e.leftdE[slot] = energy;
101 else if (slot <= 32)
102 e.rightdE[slot - 16] = energy;
103 else if (slot == Constants::ARR_SLOT_STRIP_17)
104 e.totaldE[17] = energy;
105 else if (slot == Constants::ARR_SLOT_CATHODE)
106 e.cathode = energy;
107 else if (slot == Constants::ARR_SLOT_GRID)
108 e.grid = energy;
109 if (pc) {
110 pc->timestamps[slot] = timestamp;
111 pc->energies[slot] = energy;
112 pc->flags[slot] = flags;
113 }
114 }
115 if (slot == Constants::ARR_SLOT_CATHODE)
116 e.had_cathode = kTRUE;
117 e.hits[slot]++;
118}
119
121 if (e.totaldE[0] == 0)
122 return kFALSE;
123 for (Int_t strip = 1; strip <= 14; strip += 2) {
124 if (e.leftdE[strip] == 0)
125 return kFALSE;
126 }
127 for (Int_t strip = 2; strip <= 15; strip += 2) {
128 if (e.rightdE[strip] == 0)
129 return kFALSE;
130 }
131 return kTRUE;
132}
133
134void EventBuilder::GetFlagSummary(const EventState &e, Bool_t &has_fake,
135 Bool_t &has_saturation, Bool_t &has_pileup) {
136 has_fake = (e.flags_or & CoMPASSData::FAKE_EVENT) != 0;
137 has_saturation = ((e.flags_or & CoMPASSData::SATURATION_IN_GATE) ||
138 (e.flags_or & CoMPASSData::INPUT_SATURATING)) != 0;
139 has_pileup = (e.flags_or & CoMPASSData::PILEUP) != 0;
140}
141
158 // Per-required-channel miss counts over all events: how often each
159 // completeness-required long strip / guard strip was zero. miss_long is
160 // indexed by strip 1..16 (counts L==0 for odd strips, R==0 for even).
161 Long64_t miss_long[17];
162 Long64_t miss_strip0;
163 Long64_t miss_strip17;
164};
165
166void FinalizeEvent(EventState &e, PerChannelData *pc, TTree *output_tree,
167 UShort_t *left_0_17_branch, UShort_t *rightdE_branch,
168 UShort_t *hits_branch, Short_t &cathode_branch,
169 Short_t &grid_branch, UInt_t &flags_or_branch,
170 ULong64_t &seed_ts_branch, SummaryHistograms &hSum,
171 EventCounters &c, Long64_t event_idx = -1,
172 std::vector<TGraph *> *sample_traces = nullptr,
173 Long64_t sample_stride = 0, Int_t *n_sampled = nullptr) {
174 for (Int_t s = 1; s < 17; s++)
175 e.totaldE[s] = e.leftdE[s] + e.rightdE[s];
176
177 // Collect sample traces for overlay plot
178 if (sample_traces && event_idx >= 0 && sample_stride > 0 &&
179 event_idx % sample_stride == 0 &&
180 Int_t(sample_traces->size()) < Constants::cfg.SAVE_SAMPLE_TRACES) {
181 Double_t total[18];
182 for (Int_t s = 0; s < 18; s++)
183 total[s] = Double_t(e.totaldE[s]);
184 sample_traces->push_back(EventsSummary::BuildTraceFromTotals(total));
185 if (n_sampled)
186 (*n_sampled)++;
187 }
188
189 if (e.had_cathode)
191 if (e.hits[34] > 1) {
193 c.dropped_cathode_hits_total += (e.hits[34] - 1);
194 }
195 Bool_t any_anode_multi = kFALSE;
196 for (Int_t k = 0; k < 34; k++) {
197 if (e.hits[k] > 1) {
198 any_anode_multi = kTRUE;
199 c.dropped_anode_hits_total += (e.hits[k] - 1);
200 }
201 }
202 if (any_anode_multi)
204
205 Bool_t is_complete = EventBuilder::CheckEventComplete(e);
206 c.total_events++;
207
208 // Per-required-channel miss tally (over all events): which completeness
209 // channel was zero. Long side = L for odd strips, R for even strips.
210 if (e.totaldE[0] == 0)
211 c.miss_strip0++;
212 if (e.totaldE[17] == 0)
213 c.miss_strip17++;
214 for (Int_t s = 1; s <= 16; s++) {
215 Int_t v = (s % 2 == 1) ? e.leftdE[s] : e.rightdE[s];
216 if (v == 0)
217 c.miss_long[s]++;
218 }
219
220 Bool_t has_fake = kFALSE, has_saturation = kFALSE, has_pileup = kFALSE;
221 EventBuilder::GetFlagSummary(e, has_fake, has_saturation, has_pileup);
222 Bool_t has_any_flag = has_fake || has_saturation || has_pileup;
223
224 if (is_complete) {
225 Bool_t reject = Constants::cfg.REJECT_FLAGGED_EVENTS && has_any_flag;
226 if (Constants::ActiveDedupStrategy() == kDISCARD && any_anode_multi)
227 reject = kTRUE;
228 if (!reject) {
229 for (Int_t k = 0; k < 18; k++) {
230 left_0_17_branch[k] = UShort_t(e.leftdE[k]);
231 rightdE_branch[k] = UShort_t(e.rightdE[k]);
232 }
233 // Guard strips are single-ended: their energy lives in totaldE[0]/[17]
234 // (leftdE/rightdE are 0 there). Park them in the left array at 0/17 so
235 // the recompute total[s] = left_0_17[s] + rightdE[s] reproduces them too.
236 left_0_17_branch[0] = UShort_t(e.totaldE[0]);
237 left_0_17_branch[17] = UShort_t(e.totaldE[17]);
238 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++)
239 hits_branch[k] = UShort_t(e.hits[k]);
240 cathode_branch = Short_t(e.cathode);
241 grid_branch = Short_t(e.grid);
242 flags_or_branch = e.flags_or;
243 seed_ts_branch = e.ref_ts;
244 output_tree->Fill();
245
246 for (Int_t s = 0; s < 18; s++)
247 hSum.h_music->Fill(Double_t(s), Double_t(e.totaldE[s]));
248
249 for (Int_t s = 1; s <= 16; s++) {
250 const Bool_t lIsLong = (s % 2) != 0;
251 hSum.h2_long_vs_short[s]->Fill(
252 Double_t(lIsLong ? e.rightdE[s] : e.leftdE[s]),
253 Double_t(lIsLong ? e.leftdE[s] : e.rightdE[s]));
254 }
255
256 if (hSum.h1_cathode && e.had_cathode)
257 hSum.h1_cathode->Fill(Double_t(e.cathode));
258
259 if (hSum.h1_strip17)
260 hSum.h1_strip17->Fill(Double_t(e.totaldE[17]));
261
262 if (hSum.h2_strip0_vs_grid)
263 hSum.h2_strip0_vs_grid->Fill(Double_t(e.grid), Double_t(e.totaldE[0]));
264
265 if (hSum.h1_strip0)
266 hSum.h1_strip0->Fill(Double_t(e.totaldE[0]));
267
268 if (hSum.h1_grid)
269 hSum.h1_grid->Fill(Double_t(e.grid));
270
271 Int_t mult = 0;
272 for (Int_t k = 0; k < Constants::N_ARR_SLOTS; k++)
273 mult += e.hits[k];
274 hSum.h_mult->Fill(Double_t(mult));
275 } else {
277 }
278 c.complete_events++;
279 if (has_fake)
281 if (has_saturation)
283 if (has_pileup)
285 } else {
287 if (has_fake)
289 if (has_saturation)
291 if (has_pileup)
293 }
294}
295
296Bool_t EventBuilder::BuildEventsFromSortedHits(const std::vector<RawHit> &hits,
297 const SlotMap &slot_map,
298 const TString &output_name,
299 const TString &file_label) {
300 TString output_filepath = output_name + ".root";
301 TFile *output_file = IO::OpenForWriting(output_filepath);
302 if (!output_file || output_file->IsZombie()) {
303 std::cerr << "Error opening output: " << output_filepath << std::endl;
304 return kFALSE;
305 }
306
307 // ADC energies are 14-bit unsigned at the source; store them as UShort_t, not
308 // Int_t. Index 0/17 of left_0_17_dE hold the single-ended guard strips
309 // (Strip0/Strip17), indices 1..16 the left ends of the split anodes; the
310 // right ends live in rightdE (which is 0 at 0/17). The full per-strip deposit
311 // is recomputed as left_0_17_dE[s] + rightdE[s] on read -- it holds for all
312 // 18 indices since rightdE is 0 at the guards -- so no TotaldE branch is
313 // stored.
314 UShort_t left_0_17_dE[18], rightdE[18];
315 UShort_t hits_arr[36];
316 // 14-bit ADC (<=16383), so Short_t holds every value with room to spare while
317 // preserving Cathode's -1 "no cathode hit" sentinel (Grid is non-negative but
318 // shares the type for symmetry). Unsplit anode/guard values are non-negative,
319 // hence the UShort_t arrays above.
320 Short_t cathode, grid;
321 UInt_t flags_or;
322 ULong64_t seed_ts;
323
324 TTree *output_tree = new TTree("events", "MUSIC events");
325 output_tree->Branch("Left_0_17_dE", left_0_17_dE, "Left_0_17_dE[18]/s");
326 output_tree->Branch("RightdE", rightdE, "RightdE[18]/s");
327 output_tree->Branch("Hits", hits_arr, "Hits[36]/s");
328 output_tree->Branch("Cathode", &cathode, "Cathode/S");
329 output_tree->Branch("Grid", &grid, "Grid/S");
330 output_tree->Branch("FlagsOR", &flags_or, "FlagsOR/i");
331 output_tree->Branch("SeedTs", &seed_ts, "SeedTs/l");
332
333 // Large baskets + disable auto-flush so ZSTD compresses in big chunks
334 // instead of many small basket flushes during Fill().
335 output_tree->SetAutoFlush(0);
336 for (Int_t b = 0; b < output_tree->GetListOfBranches()->GetEntries(); b++) {
337 TBranch *branch =
338 static_cast<TBranch *>(output_tree->GetListOfBranches()->At(b));
339 branch->SetBasketSize(128 * 1024 * 1024);
340 }
341
343 cfg.unit_label = "ADC";
344 cfg.strip_e_min = Constants::ActiveStripEMinAdc();
345 cfg.strip_e_max = Constants::ActiveStripEMaxAdc();
346 cfg.odd_even_split = kTRUE;
347 cfg.left_odd_max = Constants::ActiveLeftOddMaxAdc();
348 cfg.left_even_max = Constants::ActiveLeftEvenMaxAdc();
349 cfg.right_odd_max = Constants::ActiveRightOddMaxAdc();
350 cfg.right_even_max = Constants::ActiveRightEvenMaxAdc();
351 cfg.cathode_max = Constants::ActiveCathodeMaxAdc();
352 cfg.strip17_max = Constants::ActiveStrip17MaxAdc();
353 cfg.grid_max = Constants::ActiveGridMaxAdc();
354 cfg.strip0_max = Constants::ActiveStrip0MaxAdc();
355 cfg.music_energy_bins = 2000;
356
358 CreateSummaryHistograms(hSum, cfg);
359
360 // Sample traces for overlay plot
361 std::vector<TGraph *> sample_traces;
362 Long64_t sample_stride = 0;
363 Int_t n_sampled = 0;
364 if (Constants::cfg.SAVE_SAMPLE_TRACES > 0) {
365 // Rough estimate of event count: each complete event has ~20 hits
366 // (18 strips + cathode + grid). Stride is in event units, not hit units.
367 Long64_t est_events = Long64_t(hits.size()) / 20;
368 sample_stride = est_events / Long64_t(Constants::cfg.SAVE_SAMPLE_TRACES);
369 if (sample_stride < 1)
370 sample_stride = 1;
371 }
372
373 // Determine operating mode: reference-channel or blind time-window.
374 Bool_t ref_mode = Constants::ActiveReferenceChannel() != "NONE";
375
376 // Resolve reference channel name to slot ID by scanning active channel map.
377 Int_t ref_slot = -1;
378 if (ref_mode) {
379 for (std::map<std::pair<Int_t, Int_t>, TString>::const_iterator it =
381 it != Constants::ActiveChannelMap().end(); ++it) {
382 if (it->second == Constants::ActiveReferenceChannel()) {
383 Int_t board = it->first.first;
384 Int_t channel = it->first.second;
385 if (board >= 0 && board < Constants::ActiveNBoards() && channel >= 0 &&
386 channel < Constants::ActiveNChannels()) {
387 ref_slot = slot_map[board * Constants::ActiveNChannels() + channel];
388 break;
389 }
390 }
391 }
392 if (ref_slot < 0) {
393 std::cerr << "FATAL: reference channel '"
395 << "' not found in channel map. Cannot build events."
396 << std::endl;
397 output_file->Close();
398 delete output_file;
399 for (Int_t s = 1; s <= 16; s++)
400 delete hSum.h2_long_vs_short[s];
401 delete hSum.h_music;
402 delete hSum.h_mult;
403 delete hSum.h1_cathode;
404 delete hSum.h1_strip17;
405 delete hSum.h2_strip0_vs_grid;
406 delete hSum.h1_strip0;
407 delete hSum.h1_grid;
408 for (Int_t i = 0; i < Int_t(sample_traces.size()); i++)
409 delete sample_traces[i];
410 return kFALSE;
411 }
412 }
413
414 Long64_t n_entries = Long64_t(hits.size());
415
416 struct PendingHit {
417 Int_t slot;
418 UShort_t energy;
419 ULong64_t timestamp;
420 UInt_t flags;
421 };
422
423 EventState cur_event;
424 PerChannelData cur_per_channel;
425 ULong64_t cur_ref_ts = 0;
426 Bool_t have_cur = kFALSE;
427
428 std::vector<PendingHit> pending;
429 pending.reserve(4096);
430
431 EventCounters cnt;
432 cnt.total_events = 0;
433 cnt.complete_events = 0;
434 cnt.complete_with_fake = 0;
436 cnt.complete_with_pileup = 0;
437 cnt.complete_rejected = 0;
438 cnt.incomplete_events = 0;
439 cnt.incomplete_with_fake = 0;
442 cnt.events_with_cathode = 0;
447 for (Int_t s = 0; s < 17; s++)
448 cnt.miss_long[s] = 0;
449 cnt.miss_strip0 = 0;
450 cnt.miss_strip17 = 0;
451 Int_t &total_events = cnt.total_events;
452 Int_t &complete_events = cnt.complete_events;
453 Int_t &complete_with_fake = cnt.complete_with_fake;
454 Int_t &complete_with_saturation = cnt.complete_with_saturation;
455 Int_t &complete_with_pileup = cnt.complete_with_pileup;
456 Int_t &complete_rejected = cnt.complete_rejected;
457 Int_t &incomplete_events = cnt.incomplete_events;
458 Int_t &incomplete_with_fake = cnt.incomplete_with_fake;
459 Int_t &incomplete_with_saturation = cnt.incomplete_with_saturation;
460 Int_t &incomplete_with_pileup = cnt.incomplete_with_pileup;
461 Long64_t &events_with_cathode = cnt.events_with_cathode;
462 Long64_t &events_with_multi_cathode = cnt.events_with_multi_cathode;
463 Long64_t &events_with_multi_anode_hit = cnt.events_with_multi_anode_hit;
464 Long64_t &dropped_anode_hits_total = cnt.dropped_anode_hits_total;
465 Long64_t &dropped_cathode_hits_total = cnt.dropped_cathode_hits_total;
466
467 Int_t n_ref = 0;
468 ULong64_t first_ref_ts = 0;
469 ULong64_t last_ref_ts = 0;
470 Int_t emptyChannelMapEvents = 0;
471 Long64_t cathode_hits_total = 0;
472 Long64_t dropped_outside_window = 0;
473 Long64_t event_idx = 0;
474
475 ULong64_t window_ps = ULong64_t(Constants::ActiveEventTimeWindowUs() * 1.0e6);
477 PerChannelData *pc_cur = &cur_per_channel;
478
479 std::cout << "[" << file_label << "] Streaming pass over " << n_entries
480 << " sorted hits (reference: "
482 << ", window: " << Constants::ActiveEventTimeWindowUs() << " us)"
483 << std::endl;
484
485 for (Long64_t i = 0; i < n_entries; i++) {
486 const RawHit &h = hits[i];
487
488 if (h.board >= Constants::ActiveNBoards() ||
489 h.channel >= Constants::ActiveNChannels()) {
490 emptyChannelMapEvents++;
491 continue;
492 }
493 Int_t slot = slot_map[h.board * Constants::ActiveNChannels() + h.channel];
494 if (slot < 0) {
495 emptyChannelMapEvents++;
496 continue;
497 }
498
499 if (slot == Constants::ARR_SLOT_CATHODE)
500 cathode_hits_total++;
501
502 if (ref_mode) {
503 // --- Reference-channel mode ---
504 // Reference hit seeds a new event; non-reference hits queue until
505 // next reference hit (or end of stream).
506 if (slot == ref_slot) {
507 // Grid ADC window filter: skip reference hits outside the accepted
508 // range so they don't seed an event.
509 if (Double_t(h.energy) < Constants::ActiveReferenceChannelMinAdc() ||
510 Double_t(h.energy) > Constants::ActiveReferenceChannelMaxAdc()) {
511 continue;
512 }
513 if (n_ref == 0)
514 first_ref_ts = h.timestamp;
515 last_ref_ts = h.timestamp;
516 n_ref++;
517
518 if (have_cur) {
519 // Flush pending hits that fall within the current window.
520 for (Int_t p = 0; p < Int_t(pending.size()); p++) {
521 if (pending[p].timestamp - cur_ref_ts <= window_ps) {
522 AssignHit(cur_event, pc_cur, cur_ref_ts, pending[p].slot,
523 pending[p].energy, pending[p].timestamp,
524 pending[p].flags, dedup_strat);
525 } else {
526 dropped_outside_window++;
527 }
528 }
529 pending.clear();
530
531 // Finalize the completed event.
532 FinalizeEvent(cur_event, pc_cur, output_tree, left_0_17_dE, rightdE,
533 hits_arr, cathode, grid, flags_or, seed_ts, hSum, cnt,
534 event_idx, &sample_traces, sample_stride, &n_sampled);
535 event_idx++;
536 }
537
538 // Seed the new event from this reference hit, and store its energy in
539 // the event it seeds. Assigning it to the outgoing event instead puts a
540 // different ion's reference amplitude in every event, uncorrelated with
541 // the anode signals it sits beside -- and the beam gate cuts on it.
542 // Time-window mode below already assigns the seeding hit to its own
543 // event.
544 ResetEventState(cur_event);
545 ResetPerChannelData(cur_per_channel);
546 cur_ref_ts = h.timestamp;
547 cur_event.ref_ts = cur_ref_ts;
548 have_cur = kTRUE;
549 AssignHit(cur_event, pc_cur, cur_ref_ts, ref_slot, h.energy,
550 h.timestamp, h.flags, dedup_strat);
551 } else {
552 if (have_cur && h.timestamp - cur_ref_ts <= window_ps) {
553 PendingHit ph;
554 ph.slot = slot;
555 ph.energy = h.energy;
556 ph.timestamp = h.timestamp;
557 ph.flags = h.flags;
558 pending.push_back(ph);
559 } else if (have_cur) {
560 dropped_outside_window++;
561 }
562 }
563 } else {
564 // --- Time-window mode (REFERENCE_CHANNEL == "NONE") ---
565 // First unassigned hit opens a window; all hits within window_ps
566 // belong to that event. Dedup still applies, ref_ts = window anchor.
567 if (!have_cur) {
568 ResetEventState(cur_event);
569 ResetPerChannelData(cur_per_channel);
570 cur_ref_ts = h.timestamp;
571 cur_event.ref_ts = cur_ref_ts;
572 have_cur = kTRUE;
573 AssignHit(cur_event, pc_cur, cur_ref_ts, slot, h.energy, h.timestamp,
574 h.flags, dedup_strat);
575 } else if (h.timestamp - cur_ref_ts <= window_ps) {
576 AssignHit(cur_event, pc_cur, cur_ref_ts, slot, h.energy, h.timestamp,
577 h.flags, dedup_strat);
578 } else {
579 // Window exceeded — finalize and start new window.
580 FinalizeEvent(cur_event, pc_cur, output_tree, left_0_17_dE, rightdE,
581 hits_arr, cathode, grid, flags_or, seed_ts, hSum, cnt,
582 event_idx, &sample_traces, sample_stride, &n_sampled);
583 event_idx++;
584 ResetEventState(cur_event);
585 ResetPerChannelData(cur_per_channel);
586 cur_ref_ts = h.timestamp;
587 cur_event.ref_ts = cur_ref_ts;
588 AssignHit(cur_event, pc_cur, cur_ref_ts, slot, h.energy, h.timestamp,
589 h.flags, dedup_strat);
590 }
591 }
592
593 if (i % 10000000 == 0)
594 std::cout << " Stream progress: " << i << "/" << n_entries << std::endl;
595 }
596
597 // Finalize the last event.
598 if (have_cur) {
599 if (ref_mode) {
600 for (Int_t p = 0; p < Int_t(pending.size()); p++) {
601 if (pending[p].timestamp - cur_ref_ts <= window_ps) {
602 AssignHit(cur_event, pc_cur, cur_ref_ts, pending[p].slot,
603 pending[p].energy, pending[p].timestamp, pending[p].flags,
604 dedup_strat);
605 } else {
606 dropped_outside_window++;
607 }
608 }
609 pending.clear();
610 }
611 FinalizeEvent(cur_event, pc_cur, output_tree, left_0_17_dE, rightdE,
612 hits_arr, cathode, grid, flags_or, seed_ts, hSum, cnt,
613 event_idx, &sample_traces, sample_stride, &n_sampled);
614 event_idx++;
615 }
616
617 std::cout << "Found " << n_ref << " " << Constants::ActiveReferenceChannel()
618 << " hits." << std::endl;
619
620 if (ref_mode && n_ref == 0) {
621 std::cerr << "No " << Constants::ActiveReferenceChannel()
622 << " hits in file, skipping." << std::endl;
623 output_file->Close();
624 delete output_file;
625 for (Int_t s = 1; s <= 16; s++)
626 delete hSum.h2_long_vs_short[s];
627 delete hSum.h_music;
628 delete hSum.h_mult;
629 delete hSum.h1_cathode;
630 delete hSum.h1_strip17;
631 delete hSum.h2_strip0_vs_grid;
632 delete hSum.h1_strip0;
633 delete hSum.h1_grid;
634 return kFALSE;
635 }
636
637 Double_t span_s = (last_ref_ts > first_ref_ts)
638 ? Double_t(last_ref_ts - first_ref_ts) / 1e12
639 : 0.0;
640 Double_t ref_rate_hz = (span_s > 0.0) ? Double_t(n_ref) / span_s : 0.0;
641
642 output_file->cd();
643 TParameter<Double_t>("grid_rate_hz", ref_rate_hz).Write();
644 output_tree->Write("events", TObject::kOverwrite);
645 hSum.h_mult->Write("", TObject::kOverwrite);
646
647 {
648 std::lock_guard<std::mutex> lock(g_plot_mutex);
649 TString subdir = "events_summary/" + file_label;
650 SaveAndDeleteSummaryHistograms(hSum, output_file, subdir, "");
651
652 // Sample traces overlay
653 if (!sample_traces.empty()) {
654 EventsSummary::SaveSampleTraces(sample_traces, "sample_traces", subdir,
656 "Energy [ADC]");
657 }
658 }
659
660 output_file->Close();
661 delete output_file;
662
663 if (emptyChannelMapEvents != 0)
664 std::cout << "Observed " << emptyChannelMapEvents
665 << " hits with empty entry in channel map." << std::endl;
666
667 if (dropped_outside_window > 0)
668 std::cout << "Dropped " << dropped_outside_window
669 << " hits outside coincidence window." << std::endl;
670
671 std::cout << Constants::ActiveReferenceChannel() << " hits total: " << n_ref
672 << std::endl;
674 << " trigger rate: " << ref_rate_hz << " Hz over " << span_s << " s"
675 << std::endl;
676 std::cout << "Cathode hits total: " << cathode_hits_total << " (cathode/"
678 << (n_ref > 0 ? Double_t(cathode_hits_total) / n_ref : 0.0) << ")"
679 << std::endl;
680 std::cout << "Total events: " << total_events << std::endl;
681 if (total_events > 0) {
682 std::cout << "Events with cathode hit: " << events_with_cathode << " ("
683 << (100.0 * events_with_cathode / total_events) << "%)"
684 << std::endl;
685 std::cout << "Events with multiple cathode hits: "
686 << events_with_multi_cathode << " ("
687 << (100.0 * events_with_multi_cathode / total_events)
688 << "% of all, "
689 << (events_with_cathode > 0
690 ? 100.0 * events_with_multi_cathode / events_with_cathode
691 : 0.0)
692 << "% of cathode events)" << std::endl;
693 std::cout << "Events with multi-hit on any anode: "
694 << events_with_multi_anode_hit << " ("
695 << (100.0 * events_with_multi_anode_hit / total_events) << "%)"
696 << std::endl;
697 std::cout << "Dropped hits (dedup strategy): anode="
698 << dropped_anode_hits_total
699 << ", cathode=" << dropped_cathode_hits_total << std::endl;
700 }
701 std::cout << "Complete events: " << complete_events << " ("
702 << (100.0 * complete_events / total_events) << "%)" << std::endl;
703 std::cout << "Incomplete events: " << incomplete_events << " ("
704 << (100.0 * incomplete_events / total_events) << "%)" << std::endl;
705 if (total_events > 0) {
706 std::cout << "Per-channel miss rate (% of all events where the "
707 "completeness-required channel was zero):"
708 << std::endl;
709 std::cout << " Strip0=" << (100.0 * cnt.miss_strip0 / total_events)
710 << "% Strip17=" << (100.0 * cnt.miss_strip17 / total_events)
711 << "%" << std::endl;
712 for (Int_t s = 1; s <= 16; s++) {
713 const Char_t *side = (s % 2 == 1) ? "L" : "R";
714 std::cout << " " << side << s << "="
715 << (100.0 * cnt.miss_long[s] / total_events) << "%";
716 if (s % 4 == 0)
717 std::cout << std::endl;
718 }
719 }
720 if (Constants::cfg.REJECT_FLAGGED_EVENTS) {
721 Int_t stored = complete_events - complete_rejected;
722 std::cout << "Stored events (REJECT_FLAGGED_EVENTS=true): " << stored
723 << " ("
724 << (complete_events > 0 ? 100.0 * stored / complete_events : 0.0)
725 << "% of complete; " << complete_rejected << " rejected)"
726 << std::endl;
727 }
728 if (complete_events > 0) {
729 std::cout << "Complete events with rejection-quality flags:" << std::endl;
730 std::cout << " Fake events: " << complete_with_fake << " ("
731 << (100.0 * complete_with_fake / complete_events) << "%)"
732 << std::endl;
733 std::cout << " Saturated: " << complete_with_saturation << " ("
734 << (100.0 * complete_with_saturation / complete_events) << "%)"
735 << std::endl;
736 std::cout << " Pileup: " << complete_with_pileup << " ("
737 << (100.0 * complete_with_pileup / complete_events) << "%)"
738 << std::endl;
739 }
740 if (incomplete_events > 0) {
741 std::cout << "Incomplete events with rejection-quality flags:" << std::endl;
742 std::cout << " Fake events: " << incomplete_with_fake << " ("
743 << (100.0 * incomplete_with_fake / incomplete_events) << "%)"
744 << std::endl;
745 std::cout << " Saturated: " << incomplete_with_saturation << " ("
746 << (100.0 * incomplete_with_saturation / incomplete_events)
747 << "%)" << std::endl;
748 std::cout << " Pileup: " << incomplete_with_pileup << " ("
749 << (100.0 * incomplete_with_pileup / incomplete_events) << "%)"
750 << std::endl;
751 }
752
753 std::cout << "Events saved to: " << output_filepath << std::endl;
754 return kTRUE;
755}
DedupStrategy
How to resolve more than one hit on the same channel within an event.
@ kDISCARD
Discard the entire event if any anode channel has a repeated hit.
@ kLATEST_TIMESTAMP
Keep the last hit to arrive.
@ kLARGEST_ENERGY
Keep the highest-energy hit.
@ kSMALLEST_ENERGY
Keep the lowest-energy hit.
void FinalizeEvent(EventState &e, PerChannelData *pc, TTree *output_tree, UShort_t *left_0_17_branch, UShort_t *rightdE_branch, UShort_t *hits_branch, Short_t &cathode_branch, Short_t &grid_branch, UInt_t &flags_or_branch, ULong64_t &seed_ts_branch, SummaryHistograms &hSum, EventCounters &c, Long64_t event_idx=-1, std::vector< TGraph * > *sample_traces=nullptr, Long64_t sample_stride=0, Int_t *n_sampled=nullptr)
void CreateSummaryHistograms(SummaryHistograms &h, const SummaryHistConfig &cfg)
Allocate every histogram in the set.
void SaveAndDeleteSummaryHistograms(SummaryHistograms &h, TFile *out_file, const TString &subdir, const TString &plot_suffix)
Write the set to file, save the plots, then free every histogram.
std::mutex g_plot_mutex
Serialises all plotting and canvas work.
Definition FileSet.cpp:3
static Bool_t CheckEventComplete(const EventState &e)
Whether an event has everything required to be written out.
static SlotMap BuildSlotMap()
Build the board/channel to slot lookup for this dataset.
static void ResetEventState(EventState &e)
Clear an event back to its empty state, ready to be seeded.
static Bool_t ShouldKeepHit(ULong64_t cand_ts, ULong64_t prev_ts, UShort_t cand_energy, UShort_t prev_energy, ULong64_t ref_ts, DedupStrategy strategy)
Decide which of two hits on the same slot survives.
static void GetFlagSummary(const EventState &e, Bool_t &has_fake, Bool_t &has_saturation, Bool_t &has_pileup)
Unpack the OR-ed status flags into the conditions that matter.
static void ResetPerChannelData(PerChannelData &p)
Clear the per-slot detail alongside a reset event.
static Bool_t BuildEventsFromSortedHits(const std::vector< RawHit > &hits, const SlotMap &slot_map, const TString &output_name, const TString &file_label)
Build every event in a subfile and write them to a ROOT tree.
std::vector< Int_t > SlotMap
Board/channel to array slot lookup, indexed as BuildSlotMap() defines.
static void AssignHit(EventState &e, PerChannelData *pc, ULong64_t ref_ts, Int_t slot, UShort_t energy, ULong64_t timestamp, UInt_t flags, DedupStrategy strategy)
Place one hit into an event, resolving any collision.
static void SaveSampleTraces(const std::vector< TGraph * > &traces, const TString &save_name, const TString &subdir, Double_t y_min, Double_t y_max, const char *y_title)
Draw a set of traces overlaid on one frame and save it.
static TGraph * BuildTraceFromTotals(const Double_t *total)
Build a trace graph from one event's per-strip totals.
const Int_t ARR_SLOT_CATHODE
Cathode signal.
DedupStrategy ActiveDedupStrategy()
How repeated hits on one slot are resolved.
Double_t ActiveReferenceChannelMaxAdc()
Upper energy gate on the seed channel, in ADC.
Double_t ActiveStrip17MaxAdc()
Strip 17 full scale, in ADC.
const TString & ActiveReferenceChannel()
Channel whose hits seed events.
Double_t ActiveLeftEvenMaxAdc()
Left-side ceiling for even strips, in ADC.
Double_t ActiveStripEMaxAdc()
Upper bound of the per-strip energy range, in ADC.
const Int_t ARR_SLOT_GRID
Frisch grid signal.
Double_t ActiveEventTimeWindowUs()
Coincidence window for event building, in microseconds.
Double_t ActiveStripEMinAdc()
Lower bound of the per-strip energy range, in ADC.
const Int_t N_ARR_SLOTS
Length of the per-event channel arrays.
const Int_t ARR_SLOT_STRIP_17
Last anode strip. Strips run contiguously from ARR_SLOT_STRIP_0 to here.
Double_t ActiveRightEvenMaxAdc()
Right-side ceiling for even strips, in ADC.
const std::map< std::pair< Int_t, Int_t >, TString > & ActiveChannelMap()
The channel map in force.
Double_t ActiveLeftOddMaxAdc()
Left-side ceiling for odd strips, in ADC.
Double_t ActiveStrip0MaxAdc()
Strip 0 full scale, in ADC.
Double_t ActiveRightOddMaxAdc()
Right-side ceiling for odd strips, in ADC.
Double_t ActiveCathodeMaxAdc()
Cathode full scale, in ADC.
Double_t ActiveReferenceChannelMinAdc()
Lower energy gate on the seed channel, in ADC.
Int_t ActiveNBoards()
Boards in the active epoch's setup.
const Int_t ARR_SLOT_STRIP_0
First anode strip, occupying the low end of the array.
Double_t ActiveGridMaxAdc()
Grid full scale, in ADC.
const DatasetConfig & cfg
The active dataset's configuration, flat block.
Int_t ActiveNChannels()
Channels per board.
Long64_t dropped_cathode_hits_total
Int_t incomplete_with_pileup
Long64_t events_with_multi_cathode
Long64_t miss_long[17]
Int_t complete_with_pileup
Long64_t events_with_multi_anode_hit
Int_t complete_with_saturation
Int_t incomplete_with_saturation
Int_t incomplete_with_fake
Long64_t dropped_anode_hits_total
Long64_t miss_strip0
Long64_t events_with_cathode
Long64_t miss_strip17
One built event: the per-slot energies and the flags seen while building it.
Int_t leftdE[18]
Left-side energy per anode strip, in ADC.
UInt_t flags_or
Bitwise OR of the CoMPASS status flags of every hit in the event, so a single test covers pileup or s...
Int_t totaldE[18]
Summed energy per anode strip, in ADC.
Int_t rightdE[18]
Right-side energy per anode strip, in ADC.
Int_t hits[Constants::N_ARR_SLOTS]
Hit count per slot; see SlotLayout.hpp.
Bool_t had_cathode
Whether a cathode hit was seen at all.
ULong64_t ref_ts
Timestamp of the reference grid hit that seeded this event.
Int_t cathode
Cathode energy, in ADC.
Int_t grid
Frisch grid energy, in ADC.
Raw per-slot detail for one event, kept alongside EventState.
ULong64_t timestamps[Constants::N_ARR_SLOTS]
Hit timestamp per slot.
UInt_t flags[Constants::N_ARR_SLOTS]
Status flags per slot.
UShort_t energies[Constants::N_ARR_SLOTS]
Hit energy per slot, ADC.
Axis ranges and labels for building a SummaryHistograms set.
The standard diagnostic histogram set for a subfile.
TH2F * h_music
Energy against strip index: the MUSIC plot.
TH1F * h1_strip0
Strip 0 energy.
TH1F * h1_cathode
Cathode energy.
TH2F * h2_strip0_vs_grid
Strip 0 against grid energy.
TH1F * h_mult
Hit multiplicity per event.
TH1F * h1_strip17
Strip 17 energy.
TH2F * h2_long_vs_short[18]
Long against short gate, per strip.
TH1F * h1_grid
Grid energy.