Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
InitUtils.cpp
Go to the documentation of this file.
1#include "InitUtils.hpp"
2
3namespace {
4
5// Compute the fixed per-event record size (in bytes) for a CoMPASS file given
6// the global header's control bits. Only valid when no waveform is present.
7Int_t ComputeCoMPASSRecordSize(UShort_t global_header) {
8 Int_t record_size = 16; // board(2) + channel(2) + timestamp(8) + flags(4)
9 if (global_header & 0x0001)
10 record_size += 2; // energy_ch
11 if (global_header & 0x0002)
12 record_size += 8; // energy_cal
13 if (global_header & 0x0004)
14 record_size += 2; // energy_short
15 return record_size;
16}
17
18// Compute the per-field byte offsets within a fixed-stride CoMPASS record.
19// Offsets to optional fields are set to -1 when not present.
20void ComputeCoMPASSFieldOffsets(UShort_t global_header, Long64_t &off_board,
21 Long64_t &off_channel, Long64_t &off_timestamp,
22 Long64_t &off_energy_ch,
23 Long64_t &off_energy_cal,
24 Long64_t &off_energy_short,
25 Long64_t &off_flags) {
26 Bool_t has_energy_ch = (global_header & 0x0001);
27 Bool_t has_energy_cal = (global_header & 0x0002);
28 Bool_t has_energy_short = (global_header & 0x0004);
29
30 off_board = 0;
31 off_channel = 2;
32 off_timestamp = 4;
33 off_energy_ch = -1;
34 off_energy_cal = -1;
35 off_energy_short = -1;
36
37 Long64_t cursor = 12;
38 if (has_energy_ch) {
39 off_energy_ch = cursor;
40 cursor += 2;
41 }
42 if (has_energy_cal) {
43 off_energy_cal = cursor;
44 cursor += 8;
45 }
46 if (has_energy_short) {
47 off_energy_short = cursor;
48 cursor += 2;
49 }
50 off_flags = cursor;
51}
52
53// Load the data section of a fixed-stride CoMPASS file into a buffer using
54// a single fread. Returns kFALSE and an empty buffer on error.
55Bool_t LoadCoMPASSBulkBuffer(const TString &input_filename,
56 Long64_t header_bytes_to_skip, Int_t record_size,
57 std::vector<char> &buf, Long64_t &n_events,
58 Long64_t &file_size) {
59 buf.clear();
60 n_events = 0;
61 file_size = 0;
62
63 FILE *fp = std::fopen(input_filename.Data(), "rb");
64 if (!fp) {
65 std::cout << "ERROR: Failed to open file for bulk read: " << input_filename
66 << std::endl;
67 return kFALSE;
68 }
69
70 if (std::fseek(fp, 0, SEEK_END) != 0) {
71 std::cout << "ERROR: fseek to end failed on " << input_filename
72 << std::endl;
73 std::fclose(fp);
74 return kFALSE;
75 }
76 Long64_t fsize = Long64_t(std::ftell(fp));
77 if (fsize < 0) {
78 std::cout << "ERROR: ftell failed on " << input_filename << std::endl;
79 std::fclose(fp);
80 return kFALSE;
81 }
82 file_size = fsize;
83
84 Long64_t data_bytes = file_size - header_bytes_to_skip;
85 if (data_bytes < 0)
86 data_bytes = 0;
87
88 Long64_t record_size_l = Long64_t(record_size);
89 if (data_bytes % record_size_l != 0) {
90 std::cout << "WARNING: File data section (" << data_bytes
91 << " bytes) is not a multiple of record size " << record_size
92 << "; truncating to floor." << std::endl;
93 }
94 n_events = data_bytes / record_size_l;
95 Long64_t want = n_events * record_size_l;
96
97 if (std::fseek(fp, long(header_bytes_to_skip), SEEK_SET) != 0) {
98 std::cout << "ERROR: fseek to data start failed on " << input_filename
99 << std::endl;
100 std::fclose(fp);
101 return kFALSE;
102 }
103
104 buf.resize(size_t(want));
105 size_t got = (want > 0) ? std::fread(buf.data(), 1, size_t(want), fp) : 0;
106 std::fclose(fp);
107
108 if (Long64_t(got) != want) {
109 std::cout << "ERROR: Short read on " << input_filename << " (got "
110 << Long64_t(got) << " of " << want << " bytes)" << std::endl;
111 buf.clear();
112 n_events = 0;
113 return kFALSE;
114 }
115
116 return kTRUE;
117}
118
119// Print the same per-file header summary that CoMPASSData::PrintHeader produces
120// for the first event in the slow path. Called once from the bulk-read paths.
121void PrintCoMPASSHeaderSummary(UShort_t global_header) {
122 Bool_t has_energy_ch = (global_header & 0x0001);
123 Bool_t has_energy_cal = (global_header & 0x0002);
124 Bool_t has_energy_short = (global_header & 0x0004);
125
126 std::cout << "CoMPASS event header..." << std::endl;
127 std::cout << "Header: 0x" << std::hex << global_header << std::dec
128 << " (Binary: " << std::bitset<16>(global_header) << ")"
129 << std::endl;
130 std::cout << std::endl;
131 std::cout << "Control bits..." << std::endl;
132 std::cout << "Energy (ch): " << (has_energy_ch ? "YES" : "NO")
133 << std::endl;
134 std::cout << "Energy (cal): " << (has_energy_cal ? "YES" : "NO")
135 << std::endl;
136 std::cout << "Energy (short): " << (has_energy_short ? "YES" : "NO")
137 << std::endl;
138 std::cout << "Waveform: NO" << std::endl;
139}
140
141// Print the summary lines (event counts, warning aggregations, bytes read) that
142// terminate both ConvertCoMPASS* paths. Kept here so the slow and fast paths
143// emit identical output.
144void PrintCoMPASSConversionSummary(
145 Long64_t event_count, Long64_t bytes_read, Bool_t skip_bad_events,
146 Long64_t warning_fake, Long64_t warning_saturated, Long64_t warning_pileup,
147 Long64_t warning_memory_full, Long64_t warning_trigger_lost,
148 Long64_t warning_pll_loss, Long64_t warning_over_temp,
149 Long64_t warning_adc_shutdown) {
150 std::cout << "Conversion complete." << std::endl;
151 std::cout << "Total events processed: " << event_count << std::endl;
152
153 if (warning_fake > 0 || warning_saturated > 0 || warning_pileup > 0) {
154 std::cout << "Events with rejection-quality flags:" << std::endl;
155 if (warning_fake > 0) {
156 std::cout << " Fake events: " << warning_fake;
157 if (skip_bad_events)
158 std::cout << " (rejected)";
159 std::cout << std::endl;
160 }
161 if (warning_saturated > 0) {
162 std::cout << " Saturated: " << warning_saturated;
163 if (skip_bad_events)
164 std::cout << " (rejected)";
165 std::cout << std::endl;
166 }
167 if (warning_pileup > 0) {
168 std::cout << " Pileup: " << warning_pileup;
169 if (skip_bad_events)
170 std::cout << " (rejected)";
171 std::cout << std::endl;
172 }
173 std::cout << std::endl;
174 }
175
176 if (warning_memory_full > 0) {
177 std::cout << "WARNING: " << warning_memory_full
178 << " events with memory full flag" << std::endl;
179 }
180 if (warning_trigger_lost > 0) {
181 std::cout << "WARNING: " << warning_trigger_lost
182 << " events with trigger lost flag" << std::endl;
183 }
184 if (warning_pll_loss > 0) {
185 std::cout << "WARNING: " << warning_pll_loss << " events with PLL lock loss"
186 << std::endl;
187 }
188 if (warning_over_temp > 0) {
189 std::cout << "WARNING: " << warning_over_temp
190 << " events with over temperature" << std::endl;
191 }
192 if (warning_adc_shutdown > 0) {
193 std::cout << "WARNING: " << warning_adc_shutdown
194 << " events with ADC shutdown" << std::endl;
195 }
196
197 std::cout << "Total bytes read: " << bytes_read << std::endl;
198}
199
200// Bulk-read fast path for ConvertCoMPASSBinToHits. Returns the parsed hits
201// vector. The caller must guarantee the file has no waveform records.
202std::pair<std::vector<RawHit>, UShort_t>
203BulkReadCoMPASSHits(const TString &input_filename, UShort_t global_header,
204 UShort_t global_header_override, Bool_t skip_bad_events) {
205 std::vector<RawHit> hits;
206
207 Int_t record_size = ComputeCoMPASSRecordSize(global_header);
208 Long64_t header_bytes_to_skip = (global_header_override != 0) ? 0 : 2;
209
210 std::vector<char> buf;
211 Long64_t n_events = 0;
212 Long64_t file_size = 0;
213 if (!LoadCoMPASSBulkBuffer(input_filename, header_bytes_to_skip, record_size,
214 buf, n_events, file_size)) {
215 return std::make_pair(hits, static_cast<UShort_t>(0));
216 }
217
218 Long64_t off_board = 0, off_channel = 0, off_timestamp = 0;
219 Long64_t off_energy_ch = -1, off_energy_cal = -1, off_energy_short = -1;
220 Long64_t off_flags = 0;
221 ComputeCoMPASSFieldOffsets(global_header, off_board, off_channel,
222 off_timestamp, off_energy_ch, off_energy_cal,
223 off_energy_short, off_flags);
224
225 Long64_t event_count = 0;
226 Long64_t warning_fake = 0;
227 Long64_t warning_saturated = 0;
228 Long64_t warning_pileup = 0;
229 Long64_t warning_memory_full = 0;
230 Long64_t warning_trigger_lost = 0;
231 Long64_t warning_pll_loss = 0;
232 Long64_t warning_over_temp = 0;
233 Long64_t warning_adc_shutdown = 0;
234
235 std::cout << "Reading events..." << std::endl;
236 if (skip_bad_events) {
237 std::cout
238 << "Filtering enabled: skipping fake, saturated, and pileup events"
239 << std::endl;
240 }
241
242 Bool_t header_printed = kFALSE;
243
244 hits.reserve(size_t(n_events));
245
246 const char *ptr = buf.data();
247 Long64_t record_size_l = Long64_t(record_size);
248
249 for (Long64_t i = 0; i < n_events; i++) {
250 if (!header_printed) {
251 PrintCoMPASSHeaderSummary(global_header);
252 header_printed = kTRUE;
253 }
254
255 const char *rec = ptr + i * record_size_l;
256
257 UInt_t flags_val;
258 std::memcpy(&flags_val, rec + off_flags, 4);
259
260 Bool_t is_fake = (flags_val & CoMPASSData::FAKE_EVENT) != 0;
261 Bool_t is_saturated = ((flags_val & CoMPASSData::INPUT_SATURATING) ||
262 (flags_val & CoMPASSData::SATURATION_IN_GATE)) != 0;
263 Bool_t is_pileup = (flags_val & CoMPASSData::PILEUP) != 0;
264
265 // The slow path increments via cascading early-continue when skipping,
266 // so each rejected event contributes to only the first matching counter.
267 // Replicate that exactly for output parity.
268 if (is_fake) {
269 warning_fake++;
270 if (skip_bad_events)
271 continue;
272 }
273 if (is_saturated) {
274 warning_saturated++;
275 if (skip_bad_events)
276 continue;
277 }
278 if (is_pileup) {
279 warning_pileup++;
280 if (skip_bad_events)
281 continue;
282 }
283
284 if (flags_val & CoMPASSData::MEMORY_FULL)
285 warning_memory_full++;
286 if (flags_val & CoMPASSData::TRIGGER_LOST)
287 warning_trigger_lost++;
288 if (flags_val & CoMPASSData::PLL_LOCK_LOSS)
289 warning_pll_loss++;
290 if (flags_val & CoMPASSData::OVER_TEMPERATURE)
291 warning_over_temp++;
292 if (flags_val & CoMPASSData::ADC_SHUTDOWN)
293 warning_adc_shutdown++;
294
295 RawHit hit;
296 std::memcpy(&hit.board, rec + off_board, 2);
297 std::memcpy(&hit.channel, rec + off_channel, 2);
298 std::memcpy(&hit.timestamp, rec + off_timestamp, 8);
299 hit.energy = 0;
300 if (off_energy_ch >= 0)
301 std::memcpy(&hit.energy, rec + off_energy_ch, 2);
302 hit.flags = flags_val;
303 hits.push_back(hit);
304
305 event_count++;
306 }
307
308 PrintCoMPASSConversionSummary(
309 event_count, file_size, skip_bad_events, warning_fake, warning_saturated,
310 warning_pileup, warning_memory_full, warning_trigger_lost,
311 warning_pll_loss, warning_over_temp, warning_adc_shutdown);
312
313 return std::make_pair(hits, global_header);
314}
315
316} // namespace
317
319 const TString &plots_dir,
320 const TString &root_files_dir,
321 Bool_t enable_mt) {
323 gROOT->ForceStyle(kTRUE);
324 gROOT->SetBatch(kTRUE);
325
326 if (enable_mt) {
327 IO::SetThreadSafe(kTRUE);
328 // Detach new histograms from gDirectory so concurrent TFile openings on
329 // other threads don't race on the directory's child list.
330 TH1::AddDirectory(kFALSE);
331 }
332
333 TString resolved_plots_dir = plots_dir;
334 if (resolved_plots_dir.Length() == 0) {
335 std::cout
336 << "WARNING: InitUtils::SetROOTPreferences called without plots_dir; "
337 "defaulting to CWD-relative \"plots\". Pass an absolute path to "
338 "drive output into a project root."
339 << std::endl;
340 resolved_plots_dir = "plots";
341 }
342 PlottingUtils::SetPlotsBaseDir(resolved_plots_dir);
343
344 TString resolved_root_files_dir = root_files_dir;
345 if (resolved_root_files_dir.Length() == 0) {
346 std::cout << "WARNING: InitUtils::SetROOTPreferences called without "
347 "root_files_dir; defaulting to CWD-relative \"root_files\"."
348 << std::endl;
349 resolved_root_files_dir = "root_files";
350 } else {
351 IO::SetRootFilesBaseDir(resolved_root_files_dir);
352 }
353
354 if (gSystem->AccessPathName(PlottingUtils::GetPlotsBaseDir())) {
355 gSystem->mkdir(PlottingUtils::GetPlotsBaseDir(), kTRUE);
356 }
357 if (gSystem->AccessPathName(IO::GetRootFilesBaseDir())) {
358 gSystem->mkdir(IO::GetRootFilesBaseDir(), kTRUE);
359 }
360}
361
362UShort_t InitUtils::ConvertCoMPASSBinToROOT(const TString input_filename,
363 const TString output_name,
364 UShort_t global_header_override,
365 Bool_t skip_bad_events) {
366 const TString base_dir = IO::GetRootFilesBaseDir();
367 if (gSystem->AccessPathName(base_dir)) {
368 gSystem->mkdir(base_dir, kTRUE);
369 }
370
371 if (gSystem->AccessPathName(input_filename)) {
372 std::cout << "ERROR: Input file does not exist: " << input_filename
373 << std::endl;
374 return 0;
375 }
376
377 TString output_subpath = output_name + ".root";
378 TString output_filename = base_dir + "/" + output_subpath;
379
380 CoMPASSReader reader;
381 Bool_t open_success =
382 (global_header_override != 0)
383 ? reader.Open(input_filename.Data(), global_header_override)
384 : reader.Open(input_filename.Data());
385
386 if (!open_success) {
387 std::cout << "ERROR: Failed to open CoMPASS binary file" << std::endl;
388 return 0;
389 }
390
391 UShort_t global_header = reader.GetGlobalHeader();
392
393 Bool_t has_energy_ch = (global_header & 0x0001);
394 Bool_t has_energy_cal = (global_header & 0x0002);
395 Bool_t has_energy_short = (global_header & 0x0004);
396 Bool_t has_waveform = (global_header & 0x0008);
397
398 TFile *outfile = nullptr;
399 TTree *tree = nullptr;
400 UShort_t board = 0, channel = 0, energy = 0, energy_short = 0;
401 ULong64_t timestamp = 0;
402 Double_t energy_cal = 0.0;
403 UInt_t flags = 0, num_samples = 0;
404 UChar_t waveform_code = 0;
405 TArrayS *samples = nullptr;
406
407 {
408 IO::ScopedRootLock setup_guard;
409
410 outfile = IO::OpenForWriting(output_subpath);
411 if (!outfile || outfile->IsZombie()) {
412 std::cout << "ERROR: Could not create output file " << output_filename
413 << std::endl;
414 reader.Close();
415 return 0;
416 }
417
418 tree = new TTree("Data_R", "CoMPASS Binary Data");
419
420 tree->Branch("Board", &board, "Board/s");
421 tree->Branch("Channel", &channel, "Channel/s");
422 tree->Branch("Timestamp", &timestamp, "Timestamp/l");
423
424 if (has_energy_ch) {
425 tree->Branch("Energy", &energy, "Energy/s");
426 std::cout << "Energy type: Channel (ADC counts)" << std::endl;
427 } else if (has_energy_cal) {
428 tree->Branch("Energy", &energy_cal, "Energy/D");
429 std::cout << "Energy type: Calibrated (keV/MeV)" << std::endl;
430 }
431
432 if (has_energy_short) {
433 tree->Branch("EnergyShort", &energy_short, "EnergyShort/s");
434 }
435
436 tree->Branch("Flags", &flags, "Flags/i");
437
438 if (has_waveform) {
439 samples = new TArrayS();
440 tree->Branch("WaveformCode", &waveform_code, "WaveformCode/b");
441 tree->Branch("NumSamples", &num_samples, "NumSamples/i");
442 tree->Branch("Samples", &samples);
443 }
444 }
445
446 Long64_t event_count = 0;
447 Long64_t warning_fake = 0;
448 Long64_t warning_saturated = 0;
449 Long64_t warning_pileup = 0;
450 Long64_t warning_memory_full = 0;
451 Long64_t warning_trigger_lost = 0;
452 Long64_t warning_pll_loss = 0;
453 Long64_t warning_over_temp = 0;
454 Long64_t warning_adc_shutdown = 0;
455 Long64_t bytes_read_for_summary = 0;
456
457 std::cout << "Reading events..." << std::endl;
458 if (skip_bad_events) {
459 std::cout
460 << "Filtering enabled: skipping fake, saturated, and pileup events"
461 << std::endl;
462 }
463
464 if (!has_waveform) {
465 // Fast bulk-read path: fixed-stride records, one fread + memcpy parse.
466 std::cout
467 << "Using bulk-read fast path (fixed-stride records, no waveform)."
468 << std::endl;
469 reader.Close();
470
471 Int_t record_size = ComputeCoMPASSRecordSize(global_header);
472 Long64_t header_bytes_to_skip = (global_header_override != 0) ? 0 : 2;
473
474 std::vector<char> buf;
475 Long64_t n_events = 0;
476 Long64_t file_size = 0;
477 Bool_t load_ok =
478 LoadCoMPASSBulkBuffer(input_filename, header_bytes_to_skip, record_size,
479 buf, n_events, file_size);
480 if (!load_ok) {
481 IO::ScopedRootLock teardown_guard;
482 outfile->Close();
483 delete outfile;
484 return 0;
485 }
486 bytes_read_for_summary = file_size;
487
488 Long64_t off_board = 0, off_channel = 0, off_timestamp = 0;
489 Long64_t off_energy_ch = -1, off_energy_cal = -1, off_energy_short = -1;
490 Long64_t off_flags = 0;
491 ComputeCoMPASSFieldOffsets(global_header, off_board, off_channel,
492 off_timestamp, off_energy_ch, off_energy_cal,
493 off_energy_short, off_flags);
494
495 Bool_t header_printed = kFALSE;
496
497 const char *ptr = buf.data();
498 Long64_t record_size_l = Long64_t(record_size);
499
500 for (Long64_t i = 0; i < n_events; i++) {
501 if (!header_printed) {
502 PrintCoMPASSHeaderSummary(global_header);
503 header_printed = kTRUE;
504 }
505
506 const char *rec = ptr + i * record_size_l;
507
508 UInt_t flags_val;
509 std::memcpy(&flags_val, rec + off_flags, 4);
510
511 Bool_t is_fake = (flags_val & CoMPASSData::FAKE_EVENT) != 0;
512 Bool_t is_saturated =
513 ((flags_val & CoMPASSData::INPUT_SATURATING) ||
514 (flags_val & CoMPASSData::SATURATION_IN_GATE)) != 0;
515 Bool_t is_pileup = (flags_val & CoMPASSData::PILEUP) != 0;
516
517 if (is_fake) {
518 warning_fake++;
519 if (skip_bad_events)
520 continue;
521 }
522 if (is_saturated) {
523 warning_saturated++;
524 if (skip_bad_events)
525 continue;
526 }
527 if (is_pileup) {
528 warning_pileup++;
529 if (skip_bad_events)
530 continue;
531 }
532
533 if (flags_val & CoMPASSData::MEMORY_FULL)
534 warning_memory_full++;
535 if (flags_val & CoMPASSData::TRIGGER_LOST)
536 warning_trigger_lost++;
537 if (flags_val & CoMPASSData::PLL_LOCK_LOSS)
538 warning_pll_loss++;
539 if (flags_val & CoMPASSData::OVER_TEMPERATURE)
540 warning_over_temp++;
541 if (flags_val & CoMPASSData::ADC_SHUTDOWN)
542 warning_adc_shutdown++;
543
544 std::memcpy(&board, rec + off_board, 2);
545 std::memcpy(&channel, rec + off_channel, 2);
546 std::memcpy(&timestamp, rec + off_timestamp, 8);
547 if (off_energy_ch >= 0)
548 std::memcpy(&energy, rec + off_energy_ch, 2);
549 if (off_energy_cal >= 0)
550 std::memcpy(&energy_cal, rec + off_energy_cal, 8);
551 if (off_energy_short >= 0)
552 std::memcpy(&energy_short, rec + off_energy_short, 2);
553 flags = flags_val;
554
555 tree->Fill();
556 event_count++;
557 }
558 } else {
559 // Slow path: waveform records are variable-length; keep the existing
560 // CoMPASSReader-based loop.
561 while (reader.ReadEvent()) {
562 const CoMPASSData &event = reader.GetCurrentEvent();
563 if (event_count == 0) {
564 event.PrintHeader();
565 }
566
567 if (event.isFakeEvent()) {
568 warning_fake++;
569 if (skip_bad_events)
570 continue;
571 }
572 if (event.isInputSaturating() || event.hasSaturation()) {
573 warning_saturated++;
574 if (skip_bad_events)
575 continue;
576 }
577 if (event.isPileup()) {
578 warning_pileup++;
579 if (skip_bad_events)
580 continue;
581 }
582
583 if (event.hasMemoryFull())
584 warning_memory_full++;
585 if (event.hasTriggerLost())
586 warning_trigger_lost++;
587 if (event.hasPLLLockLoss())
588 warning_pll_loss++;
589 if (event.isOverTemperature())
590 warning_over_temp++;
591 if (event.isADCShutdown())
592 warning_adc_shutdown++;
593
594 board = event.board;
595 channel = event.channel;
596 timestamp = event.timestamp;
597 flags = event.flags;
598
599 if (has_energy_ch) {
600 energy = event.energy_ch;
601 }
602 if (has_energy_cal) {
603 energy_cal = event.energy_cal;
604 }
605 if (has_energy_short) {
606 energy_short = event.energy_short_ch;
607 }
608 if (has_waveform) {
609 waveform_code = event.waveform_code;
610 num_samples = event.num_samples;
611 *samples = event.samples;
612 }
613
614 tree->Fill();
615 event_count++;
616 }
617 bytes_read_for_summary = reader.GetBytesRead();
618 }
619
620 PrintCoMPASSConversionSummary(
621 event_count, bytes_read_for_summary, skip_bad_events, warning_fake,
622 warning_saturated, warning_pileup, warning_memory_full,
623 warning_trigger_lost, warning_pll_loss, warning_over_temp,
624 warning_adc_shutdown);
625
626 {
627 IO::ScopedRootLock teardown_guard;
628 outfile->cd();
629 tree->Write("", TObject::kOverwrite);
630 outfile->Close();
631 reader.Close();
632 delete outfile;
633 }
634
635 std::cout << "Output saved to: " << output_filename << std::endl;
636
637 return global_header;
638}
639
640std::pair<std::vector<RawHit>, UShort_t>
641InitUtils::ConvertCoMPASSBinToHits(const TString input_filename,
642 UShort_t global_header_override,
643 Bool_t skip_bad_events) {
644 std::vector<RawHit> hits;
645
646 if (gSystem->AccessPathName(input_filename)) {
647 std::cout << "ERROR: Input file does not exist: " << input_filename
648 << std::endl;
649 return std::make_pair(hits, static_cast<UShort_t>(0));
650 }
651
652 CoMPASSReader reader;
653 Bool_t open_success =
654 (global_header_override != 0)
655 ? reader.Open(input_filename.Data(), global_header_override)
656 : reader.Open(input_filename.Data());
657
658 if (!open_success) {
659 std::cout << "ERROR: Failed to open CoMPASS binary file" << std::endl;
660 return std::make_pair(hits, static_cast<UShort_t>(0));
661 }
662
663 UShort_t global_header = reader.GetGlobalHeader();
664 Bool_t has_energy_ch = (global_header & 0x0001);
665 Bool_t has_waveform = (global_header & 0x0008);
666
667 if (!has_energy_ch) {
668 std::cout << "WARNING: File has no channel-energy field; RawHit.energy "
669 "will be zero for every hit."
670 << std::endl;
671 }
672
673 if (!has_waveform) {
674 // Fast bulk-read path: single fread of the data section, then memcpy-parse.
675 std::cout
676 << "Using bulk-read fast path (fixed-stride records, no waveform)."
677 << std::endl;
678 reader.Close();
679 return BulkReadCoMPASSHits(input_filename, global_header,
680 global_header_override, skip_bad_events);
681 }
682
683 Long64_t event_count = 0;
684 Long64_t warning_fake = 0;
685 Long64_t warning_saturated = 0;
686 Long64_t warning_pileup = 0;
687 Long64_t warning_memory_full = 0;
688 Long64_t warning_trigger_lost = 0;
689 Long64_t warning_pll_loss = 0;
690 Long64_t warning_over_temp = 0;
691 Long64_t warning_adc_shutdown = 0;
692
693 std::cout << "Reading events..." << std::endl;
694 if (skip_bad_events) {
695 std::cout
696 << "Filtering enabled: skipping fake, saturated, and pileup events"
697 << std::endl;
698 }
699
700 while (reader.ReadEvent()) {
701 const CoMPASSData &event = reader.GetCurrentEvent();
702 if (event_count == 0) {
703 event.PrintHeader();
704 }
705
706 if (event.isFakeEvent()) {
707 warning_fake++;
708 if (skip_bad_events)
709 continue;
710 }
711 if (event.isInputSaturating() || event.hasSaturation()) {
712 warning_saturated++;
713 if (skip_bad_events)
714 continue;
715 }
716 if (event.isPileup()) {
717 warning_pileup++;
718 if (skip_bad_events)
719 continue;
720 }
721
722 if (event.hasMemoryFull())
723 warning_memory_full++;
724 if (event.hasTriggerLost())
725 warning_trigger_lost++;
726 if (event.hasPLLLockLoss())
727 warning_pll_loss++;
728 if (event.isOverTemperature())
729 warning_over_temp++;
730 if (event.isADCShutdown())
731 warning_adc_shutdown++;
732
733 RawHit hit;
734 hit.board = event.board;
735 hit.channel = event.channel;
736 hit.energy = event.energy_ch;
737 hit.timestamp = event.timestamp;
738 hit.flags = event.flags;
739 hits.push_back(hit);
740
741 event_count++;
742 }
743
744 std::cout << "Conversion complete." << std::endl;
745 std::cout << "Total events processed: " << event_count << std::endl;
746
747 if (warning_fake > 0 || warning_saturated > 0 || warning_pileup > 0) {
748 std::cout << "Events with rejection-quality flags:" << std::endl;
749 if (warning_fake > 0) {
750 std::cout << " Fake events: " << warning_fake;
751 if (skip_bad_events)
752 std::cout << " (rejected)";
753 std::cout << std::endl;
754 }
755 if (warning_saturated > 0) {
756 std::cout << " Saturated: " << warning_saturated;
757 if (skip_bad_events)
758 std::cout << " (rejected)";
759 std::cout << std::endl;
760 }
761 if (warning_pileup > 0) {
762 std::cout << " Pileup: " << warning_pileup;
763 if (skip_bad_events)
764 std::cout << " (rejected)";
765 std::cout << std::endl;
766 }
767 std::cout << std::endl;
768 }
769
770 if (warning_memory_full > 0) {
771 std::cout << "WARNING: " << warning_memory_full
772 << " events with memory full flag" << std::endl;
773 }
774 if (warning_trigger_lost > 0) {
775 std::cout << "WARNING: " << warning_trigger_lost
776 << " events with trigger lost flag" << std::endl;
777 }
778 if (warning_pll_loss > 0) {
779 std::cout << "WARNING: " << warning_pll_loss << " events with PLL lock loss"
780 << std::endl;
781 }
782 if (warning_over_temp > 0) {
783 std::cout << "WARNING: " << warning_over_temp
784 << " events with over temperature" << std::endl;
785 }
786 if (warning_adc_shutdown > 0) {
787 std::cout << "WARNING: " << warning_adc_shutdown
788 << " events with ADC shutdown" << std::endl;
789 }
790
791 std::cout << "Total bytes read: " << reader.GetBytesRead() << std::endl;
792
793 reader.Close();
794
795 return std::make_pair(hits, global_header);
796}
797
798Bool_t InitUtils::ConvertWavedumpBinToROOT(const TString input_filename,
799 const TString output_name,
800 Bool_t corrections_enabled) {
801 const TString base_dir = IO::GetRootFilesBaseDir();
802 if (gSystem->AccessPathName(base_dir)) {
803 gSystem->mkdir(base_dir, kTRUE);
804 }
805
806 if (gSystem->AccessPathName(input_filename)) {
807 std::cout << "ERROR: Input file does not exist: " << input_filename
808 << std::endl;
809 return kFALSE;
810 }
811
812 TString output_subpath = output_name + "_raw.root";
813 TString output_filename = base_dir + "/" + output_subpath;
814
815 WaveDump742Reader reader(corrections_enabled);
816
817 if (!reader.Open(input_filename.Data())) {
818 std::cout << "ERROR: Failed to open WaveDump binary file" << std::endl;
819 return kFALSE;
820 }
821
822 std::cout << "Corrections: " << (corrections_enabled ? "enabled" : "disabled")
823 << std::endl;
824
825 TFile *outfile = nullptr;
826 TTree *tree = nullptr;
827 UInt_t channel_br = 0, event_counter = 0, trigger_time_tag = 0;
828 TArrayS *samples = nullptr;
829
830 {
831 IO::ScopedRootLock setup_guard;
832
833 outfile = IO::OpenForWriting(output_subpath);
834 if (!outfile || outfile->IsZombie()) {
835 std::cout << "ERROR: Could not create output file " << output_filename
836 << std::endl;
837 reader.Close();
838 return kFALSE;
839 }
840
841 tree = new TTree("Data_R", "WaveDump 742 Binary Data");
842
843 tree->Branch("Channel", &channel_br, "Channel/i");
844 tree->Branch("EventCounter", &event_counter, "EventCounter/i");
845 tree->Branch("TriggerTimeTag", &trigger_time_tag, "TriggerTimeTag/i");
846 samples = new TArrayS();
847 tree->Branch("Samples", &samples);
848 }
849
850 Long64_t event_count = 0;
851
852 std::cout << "Reading events..." << std::endl;
853
854 while (reader.ReadEvent()) {
855 const WaveDump742Data &event = reader.GetCurrentEvent();
856
857 channel_br = event.channel;
858 event_counter = event.event_counter;
859 trigger_time_tag = event.group_trigger_time_tag;
860 *samples = event.samples;
861
862 tree->Fill();
863 event_count++;
864 }
865
866 std::cout << "Conversion complete." << std::endl;
867 std::cout << "Total events processed: " << event_count << std::endl;
868 std::cout << "Samples per event: "
869 << (event_count > 0 ? samples->GetSize() : 0) << std::endl;
870 std::cout << "Total bytes read: " << reader.GetBytesRead() << std::endl;
871
872 {
873 IO::ScopedRootLock teardown_guard;
874 outfile->cd();
875 tree->Write("", TObject::kOverwrite);
876 outfile->Close();
877 reader.Close();
878 delete outfile;
879 }
880
881 std::cout << "Output saved to: " << output_filename << std::endl;
882
883 return kTRUE;
884}
885
886Bool_t InitUtils::ConvertSOLBinToROOT(const TString input_filename,
887 const TString output_name) {
888 const TString base_dir = IO::GetRootFilesBaseDir();
889 if (gSystem->AccessPathName(base_dir)) {
890 gSystem->mkdir(base_dir, kTRUE);
891 }
892
893 if (gSystem->AccessPathName(input_filename)) {
894 std::cout << "ERROR: Input file does not exist: " << input_filename
895 << std::endl;
896 return kFALSE;
897 }
898
899 TString output_subpath = output_name + ".root";
900 TString output_filename = base_dir + "/" + output_subpath;
901
902 SOLReader reader;
903 if (!reader.Open(input_filename.Data())) {
904 std::cout << "ERROR: Failed to open SOL binary file" << std::endl;
905 return kFALSE;
906 }
907
908 TFile *outfile = nullptr;
909 TTree *tree = nullptr;
910
911 // Per-row variables
912 Short_t channel_br = 0;
913 Short_t energy_br = 0;
914 Short_t energy_short_br = 0;
915 Long64_t timestamp_br = 0;
916 Short_t fine_timestamp_br = 0;
917 Short_t flags_high_br = 0;
918 Short_t flags_low_br = 0;
919 Short_t data_type_br = 0;
920 Char_t is_psd_br = 0;
921 Char_t down_sampling_br = 0;
922 Char_t board_fail_br = 0;
923 Char_t flush_br = 0;
924 Short_t trigger_thr_br = 0;
925 Long64_t event_size_br = 0;
926 Int_t agg_counter_br = 0;
927 Long64_t block_id_br = 0;
928 Int_t trace_len_br = 0;
929 Char_t ana_probe_type0_br = 0xFF;
930 Char_t ana_probe_type1_br = 0xFF;
931 Char_t dig_probe_type0_br = 0xFF;
932 Char_t dig_probe_type1_br = 0xFF;
933 Char_t dig_probe_type2_br = 0xFF;
934 Char_t dig_probe_type3_br = 0xFF;
935
936 // Variable-length trace arrays
937 TArrayI *trace0 = new TArrayI();
938 TArrayI *trace1 = new TArrayI();
939 TArrayC *dig0 = new TArrayC();
940 TArrayC *dig1 = new TArrayC();
941 TArrayC *dig2 = new TArrayC();
942 TArrayC *dig3 = new TArrayC();
943
944 {
945 IO::ScopedRootLock setup_guard;
946
947 outfile = IO::OpenForWriting(output_subpath);
948 if (!outfile || outfile->IsZombie()) {
949 std::cout << "ERROR: Could not create output file " << output_filename
950 << std::endl;
951 reader.Close();
952 delete trace0;
953 delete trace1;
954 delete dig0;
955 delete dig1;
956 delete dig2;
957 delete dig3;
958 return kFALSE;
959 }
960
961 tree = new TTree("Data_R", "SOLARIS Binary Data");
962
963 tree->Branch("Channel", &channel_br, "Channel/s");
964 tree->Branch("Energy", &energy_br, "Energy/s");
965 tree->Branch("EnergyShort", &energy_short_br, "EnergyShort/s");
966 tree->Branch("Timestamp", &timestamp_br, "Timestamp/l");
967 tree->Branch("FineTimestamp", &fine_timestamp_br, "FineTimestamp/s");
968 tree->Branch("FlagsHigh", &flags_high_br, "FlagsHigh/s");
969 tree->Branch("FlagsLow", &flags_low_br, "FlagsLow/s");
970 tree->Branch("DataType", &data_type_br, "DataType/s");
971 tree->Branch("IsPSD", &is_psd_br, "IsPSD/C");
972 tree->Branch("DownSampling", &down_sampling_br, "DownSampling/C");
973 tree->Branch("BoardFail", &board_fail_br, "BoardFail/C");
974 tree->Branch("Flush", &flush_br, "Flush/C");
975 tree->Branch("TriggerThr", &trigger_thr_br, "TriggerThr/s");
976 tree->Branch("EventSize", &event_size_br, "EventSize/l");
977 tree->Branch("AggCounter", &agg_counter_br, "AggCounter/I");
978 tree->Branch("BlockID", &block_id_br, "BlockID/l");
979 tree->Branch("TraceLen", &trace_len_br, "TraceLen/I");
980 tree->Branch("AnaProbeType0", &ana_probe_type0_br, "AnaProbeType0/C");
981 tree->Branch("AnaProbeType1", &ana_probe_type1_br, "AnaProbeType1/C");
982 tree->Branch("DigProbeType0", &dig_probe_type0_br, "DigProbeType0/C");
983 tree->Branch("DigProbeType1", &dig_probe_type1_br, "DigProbeType1/C");
984 tree->Branch("DigProbeType2", &dig_probe_type2_br, "DigProbeType2/C");
985 tree->Branch("DigProbeType3", &dig_probe_type3_br, "DigProbeType3/C");
986 tree->Branch("Trace0", &trace0);
987 tree->Branch("Trace1", &trace1);
988 tree->Branch("Dig0", &dig0);
989 tree->Branch("Dig1", &dig1);
990 tree->Branch("Dig2", &dig2);
991 tree->Branch("Dig3", &dig3);
992 }
993
994 Long64_t block_count = 0;
995 Long64_t blocks_with_traces = 0;
996 Long64_t blocks_without_traces = 0;
997
998 std::cout << "Reading SOL blocks..." << std::endl;
999
1000 while (reader.ReadEvent()) {
1001 const SOLData &event = reader.GetCurrentEvent();
1002
1003 channel_br = static_cast<Short_t>(event.channel);
1004 energy_br = static_cast<Short_t>(event.energy);
1005 energy_short_br = static_cast<Short_t>(event.energy_short);
1006 timestamp_br = event.timestamp;
1007 fine_timestamp_br = static_cast<Short_t>(event.fine_timestamp);
1008 flags_high_br = static_cast<Short_t>(event.flags_high);
1009 flags_low_br = static_cast<Short_t>(event.flags_low);
1010 data_type_br = static_cast<Short_t>(event.data_type);
1011 is_psd_br = static_cast<Char_t>(event.is_psd);
1012 down_sampling_br = static_cast<Char_t>(event.down_sampling);
1013 board_fail_br = static_cast<Char_t>(event.board_fail);
1014 flush_br = static_cast<Char_t>(event.flush);
1015 trigger_thr_br = static_cast<Short_t>(event.trigger_thr);
1016 event_size_br = event.event_size;
1017 agg_counter_br = static_cast<Int_t>(event.agg_counter);
1018 block_id_br = event.block_id;
1019 trace_len_br = static_cast<Int_t>(event.trace_len);
1020 ana_probe_type0_br = static_cast<Char_t>(event.ana_probe_type[0]);
1021 ana_probe_type1_br = static_cast<Char_t>(event.ana_probe_type[1]);
1022 dig_probe_type0_br = static_cast<Char_t>(event.dig_probe_type[0]);
1023 dig_probe_type1_br = static_cast<Char_t>(event.dig_probe_type[1]);
1024 dig_probe_type2_br = static_cast<Char_t>(event.dig_probe_type[2]);
1025 dig_probe_type3_br = static_cast<Char_t>(event.dig_probe_type[3]);
1026
1027 if (event.hasTraces()) {
1028 if (event.data_type == SOLData::ALL) {
1029 UInt_t n_samples = event.getSamples();
1030 const Int_t *a0 = event.getAnalog0();
1031 const Int_t *a1 = event.getAnalog1();
1032 const UChar_t *d0 = event.getDigital(0);
1033 const UChar_t *d1 = event.getDigital(1);
1034 const UChar_t *d2 = event.getDigital(2);
1035 const UChar_t *d3 = event.getDigital(3);
1036
1037 trace0->Set(static_cast<Int_t>(n_samples));
1038 for (UInt_t i = 0; i < n_samples; i++) {
1039 trace0->SetAt(a0[i], static_cast<Int_t>(i));
1040 }
1041
1042 trace1->Set(static_cast<Int_t>(n_samples));
1043 for (UInt_t i = 0; i < n_samples; i++) {
1044 trace1->SetAt(a1[i], static_cast<Int_t>(i));
1045 }
1046
1047 dig0->Set(static_cast<Int_t>(n_samples));
1048 for (UInt_t i = 0; i < n_samples; i++) {
1049 dig0->SetAt(static_cast<Short_t>(d0[i]), static_cast<Int_t>(i));
1050 }
1051 dig1->Set(static_cast<Int_t>(n_samples));
1052 for (UInt_t i = 0; i < n_samples; i++) {
1053 dig1->SetAt(static_cast<Short_t>(d1[i]), static_cast<Int_t>(i));
1054 }
1055 dig2->Set(static_cast<Int_t>(n_samples));
1056 for (UInt_t i = 0; i < n_samples; i++) {
1057 dig2->SetAt(static_cast<Short_t>(d2[i]), static_cast<Int_t>(i));
1058 }
1059 dig3->Set(static_cast<Int_t>(n_samples));
1060 for (UInt_t i = 0; i < n_samples; i++) {
1061 dig3->SetAt(static_cast<Short_t>(d3[i]), static_cast<Int_t>(i));
1062 }
1063 } else {
1064 // OneTrace format
1065 UInt_t n_samples = event.getSamples();
1066 const Int_t *ot = event.getOneTrace();
1067 trace0->Set(static_cast<Int_t>(n_samples));
1068 for (UInt_t i = 0; i < n_samples; i++) {
1069 trace0->SetAt(ot[i], static_cast<Int_t>(i));
1070 }
1071 trace1->Set(0);
1072 dig0->Set(0);
1073 dig1->Set(0);
1074 dig2->Set(0);
1075 dig3->Set(0);
1076 }
1077 blocks_with_traces++;
1078 } else {
1079 trace0->Set(0);
1080 trace1->Set(0);
1081 dig0->Set(0);
1082 dig1->Set(0);
1083 dig2->Set(0);
1084 dig3->Set(0);
1085 blocks_without_traces++;
1086 }
1087
1088 tree->Fill();
1089 block_count++;
1090
1091 // Progress reporting every 10000 blocks
1092 if (block_count % 10000 == 0) {
1093 std::cout << " Processed " << block_count << " blocks..." << std::endl;
1094 }
1095 }
1096
1097 std::cout << "Conversion complete." << std::endl;
1098 std::cout << "Total blocks processed: " << block_count << std::endl;
1099 std::cout << "Blocks with traces: " << blocks_with_traces << std::endl;
1100 std::cout << "Blocks without traces: " << blocks_without_traces << std::endl;
1101 std::cout << "Total bytes read: " << reader.GetBytesRead() << std::endl;
1102
1103 {
1104 IO::ScopedRootLock teardown_guard;
1105 outfile->cd();
1106 tree->Write("", TObject::kOverwrite);
1107 outfile->Close();
1108 reader.Close();
1109 delete outfile;
1110 }
1111
1112 delete trace0;
1113 delete trace1;
1114 delete dig0;
1115 delete dig1;
1116 delete dig2;
1117 delete dig3;
1118
1119 std::cout << "Output saved to: " << output_filename << std::endl;
1120
1121 return kTRUE;
1122}
1123
1124std::pair<std::vector<SOLHit>, Long64_t>
1125InitUtils::ConvertSOLBinToHits(const TString input_filename) {
1126 std::vector<SOLHit> hits;
1127
1128 if (gSystem->AccessPathName(input_filename)) {
1129 std::cout << "ERROR: Input file does not exist: " << input_filename
1130 << std::endl;
1131 return std::make_pair(hits, static_cast<Long64_t>(0));
1132 }
1133
1134 SOLReader reader;
1135 if (!reader.Open(input_filename.Data())) {
1136 std::cout << "ERROR: Failed to open SOL binary file" << std::endl;
1137 return std::make_pair(hits, static_cast<Long64_t>(0));
1138 }
1139
1140 Long64_t block_count = 0;
1141 Long64_t blocks_with_traces = 0;
1142 Long64_t blocks_without_traces = 0;
1143
1144 std::cout << "Reading SOL blocks..." << std::endl;
1145
1146 while (reader.ReadEvent()) {
1147 SOLHit hit = reader.ToHit();
1148
1149 if (reader.GetCurrentEvent().hasTraces()) {
1150 blocks_with_traces++;
1151 } else {
1152 blocks_without_traces++;
1153 }
1154
1155 hits.push_back(hit);
1156 block_count++;
1157 }
1158
1159 std::cout << "Conversion complete." << std::endl;
1160 std::cout << "Total blocks processed: " << block_count << std::endl;
1161 std::cout << "Blocks with traces: " << blocks_with_traces << std::endl;
1162 std::cout << "Blocks without traces: " << blocks_without_traces << std::endl;
1163 std::cout << "Total bytes read: " << reader.GetBytesRead() << std::endl;
1164
1165 reader.Close();
1166
1167 return std::make_pair(hits, block_count);
1168}
PlotSaveFormat
Output file format for saved figures.
Long64_t GetBytesRead() const
Bytes consumed so far, for progress reporting.
virtual Bool_t Open(const char *fname)
Open a binary file for reading.
virtual void Close()
Close the file. Safe to call when nothing is open.
One decoded CoMPASS event, with header-bit and status-flag accessors.
static const UInt_t PLL_LOCK_LOSS
static const UInt_t MEMORY_FULL
UShort_t board
Digitiser board id.
static const UInt_t SATURATION_IN_GATE
static const UInt_t INPUT_SATURATING
static const UInt_t TRIGGER_LOST
static const UInt_t PILEUP
static const UInt_t FAKE_EVENT
static const UInt_t ADC_SHUTDOWN
static const UInt_t OVER_TEMPERATURE
Cursor over a CoMPASS binary file.
const CoMPASSData & GetCurrentEvent() const
The event most recently read.
Bool_t ReadEvent() override
Decode the next event into the current-event object.
UShort_t GetGlobalHeader() const
The global header in force, read from the file or overridden.
Bool_t Open(const char *fname) override
Open a file and read its global header.
RAII guard engaging the same lock used by the open helpers.
Definition IOUtils.hpp:113
static Bool_t ConvertSOLBinToROOT(const TString input_filename, const TString output_name)
Convert a SOLARIS DAQ (SOL) binary file to a ROOT tree.
static UShort_t ConvertCoMPASSBinToROOT(const TString input_filename, const TString output_name, UShort_t global_header_override, Bool_t skip_bad_events=kFALSE)
Convert a CoMPASS binary file to a ROOT tree.
static std::pair< std::vector< SOLHit >, Long64_t > ConvertSOLBinToHits(const TString input_filename)
Read a SOL binary file into memory as lightweight hits.
static Bool_t ConvertWavedumpBinToROOT(const TString input_filename, const TString output_name, Bool_t corrections_enabled=kTRUE)
Convert a WaveDump binary file (DT5742 family) to a ROOT tree.
static std::pair< std::vector< RawHit >, UShort_t > ConvertCoMPASSBinToHits(const TString input_filename, UShort_t global_header_override=0, Bool_t skip_bad_events=kFALSE)
Read a CoMPASS binary file into memory, with no ROOT file I/O.
static void SetROOTPreferences(PlotSaveFormat save_format=PlotSaveFormat::kPNG, const TString &plots_dir="", const TString &root_files_dir="", Bool_t enable_mt=kTRUE)
Configure the ROOT environment and pin all output paths.
static void SetPlotsBaseDir(const TString &dir)
Set the base directory that saved figures are written under.
static void SetStylePreferences(PlotSaveFormat save_format=PlotSaveFormat::kPNG)
Install the global ROOT style and choose the output format.
static TString GetPlotsBaseDir()
Current base directory for saved figures, without trailing slash.
One decoded SOL block, optionally carrying its traces.
Bool_t hasTraces() const
Whether this block carries traces.
ULong64_t timestamp
Cursor over a SOLARIS DAQ .sol file.
SOLHit ToHit() const
Copy the current block's header fields into a standalone SOLHit.
const SOLData & GetCurrentEvent() const
The block most recently read.
Bool_t ReadEvent() override
Decode the next record into the derived reader's current event.
One decoded WaveDump event from a DT5742-family digitiser.
UInt_t channel
Channel index.
Cursor over a WaveDump DT5742 binary file.
Bool_t ReadEvent() override
Decode the next record into the derived reader's current event.
const WaveDump742Data & GetCurrentEvent() const
The event most recently read.
TString GetRootFilesBaseDir()
Current base directory for relative subpaths, without trailing slash.
Definition IOUtils.cpp:37
void SetRootFilesBaseDir(const TString &dir)
Set the base directory that relative subpaths resolve against.
Definition IOUtils.cpp:30
void SetThreadSafe(Bool_t enabled=kTRUE)
Enable ROOT thread safety and serialise file opening.
Definition IOUtils.cpp:39
TFile * OpenForWriting(const TString &subpath, const TString mode="RECREATE")
Open a ROOT file for writing, creating parent directories first.
Definition IOUtils.cpp:68
Minimal CoMPASS hit: header fields only, no waveform.
UInt_t flags
CoMPASS status bits; see CoMPASSData's constants.
UShort_t energy
Energy in ADC channel units.
UShort_t board
Digitiser board id.
UShort_t channel
Channel index on that board.
ULong64_t timestamp
Acquisition timestamp, in picoseconds.
Lightweight SOL block: header fields only, traces stripped.