Analysis-Utilities 26.9.9
C++/ROOT utilities for nuclear measurement data analysis
Loading...
Searching...
No Matches
BinaryUtils.cpp
Go to the documentation of this file.
1#include "BinaryUtils.hpp"
2#include <cstring>
3
5 : header(0), board(0), channel(0), timestamp(0), energy_ch(0),
7 num_samples(0) {
8 std::cout << "This version of CoMPASS binary conversion is based on manual "
9 "revision 25.1..."
10 << std::endl;
11}
12
14 switch (waveform_code) {
15 case INPUT:
16 return "Input";
17 case RC_CR:
18 return "RC-CR (DPP-PHA)";
19 case RC_CR2:
20 return "RC-CR2 (DPP-PHA)";
21 case TRAPEZOID:
22 return "Trapezoid (DPP-PHA)";
23 case BASELINE:
24 return "Baseline";
25 case THRESHOLD:
26 return "Threshold";
27 case CFD:
28 return "CFD (DPP-PSD)";
30 return "Trapezoid-Baseline (DPP-PHA)";
31 case FAST_TRIANGLE:
32 return "Fast Triangle (x27xx DPP-PHA)";
33 case SMOOTHED_INPUT:
34 return "Smoothed Input (DPP-PSD)";
35 default:
36 return "Unknown";
37 }
38}
39
40std::vector<TString> CoMPASSData::getActiveFlags() const {
41 std::vector<TString> active_flags;
42
43 if (hasDeadtime())
44 active_flags.push_back("Deadtime");
46 active_flags.push_back("Timestamp Rollover");
48 active_flags.push_back("Timestamp Reset (Ext)");
49 if (isFakeEvent())
50 active_flags.push_back("Fake Event");
51 if (hasMemoryFull())
52 active_flags.push_back("Memory Full");
53 if (hasTriggerLost())
54 active_flags.push_back("Trigger Lost");
55 if (hasNTriggersLost())
56 active_flags.push_back("N Triggers Lost");
57 if (hasSaturation())
58 active_flags.push_back("Saturation");
59 if (has1024Triggers())
60 active_flags.push_back("1024 Triggers");
61 if (isFirstAfterBusy())
62 active_flags.push_back("First After Busy");
64 active_flags.push_back("Input Saturating");
66 active_flags.push_back("N Triggers Counted");
68 active_flags.push_back("Not Matched Time Filter");
69 if (hasFineTimestamp())
70 active_flags.push_back("Fine Timestamp");
71 if (isPileup())
72 active_flags.push_back("Pile-up");
73 if (hasPLLLockLoss())
74 active_flags.push_back("PLL Lock Loss");
76 active_flags.push_back("Over Temperature");
77 if (isADCShutdown())
78 active_flags.push_back("ADC Shutdown");
79
80 return active_flags;
81}
82
84 std::cout << "CoMPASS event header..." << std::endl;
85 std::cout << "Header: 0x" << std::hex << header << std::dec
86 << " (Binary: " << std::bitset<16>(header) << ")" << std::endl;
87
88 std::cout << std::endl;
89 std::cout << "Control bits..." << std::endl;
90 std::cout << "Energy (ch): " << (hasEnergyCh() ? "YES" : "NO")
91 << std::endl;
92 std::cout << "Energy (cal): " << (hasEnergyCal() ? "YES" : "NO")
93 << std::endl;
94 std::cout << "Energy (short): " << (hasEnergyShort() ? "YES" : "NO")
95 << std::endl;
96 std::cout << "Waveform: " << (hasWaveform() ? "YES" : "NO")
97 << std::endl;
98}
99
101 std::cout << "Flags (0x" << std::hex << flags << std::dec << ")" << std::endl;
102 std::cout << "Binary: " << std::bitset<32>(flags) << std::endl;
103
104 std::vector<TString> active = getActiveFlags();
105 if (active.empty()) {
106 std::cout << "No flags set" << std::endl;
107 } else {
108 std::cout << "Active flags:" << std::endl;
109 Int_t n_flags = active.size();
110 for (Int_t flag = 0; flag < n_flags; flag++) {
111 std::cout << " - " << active.at(flag).Data() << std::endl;
112 }
113 }
114}
115
117 if (hasWaveform()) {
118 std::cout << "Waveform..." << std::endl;
119 std::cout << "Code: " << static_cast<Int_t>(waveform_code) << " ("
120 << getWaveformCodeName().Data() << ")" << std::endl;
121 std::cout << "Samples: " << num_samples << std::endl;
122 if (samples.GetSize() > 0) {
123 std::cout << "First 5 samples: ";
124 for (Int_t i = 0; i < TMath::Min(5, samples.GetSize()); i++) {
125 std::cout << samples[i] << " ";
126 }
127 std::cout << std::endl;
128 }
129 }
130}
131
132void CoMPASSData::Print() const {
133 PrintHeader();
134
135 if (hasEnergyCh()) {
136 std::cout << "Energy (ch): " << energy_ch << std::endl;
137 }
138 if (hasEnergyCal()) {
139 std::cout << "Energy (cal): " << energy_cal << " keV/MeV" << std::endl;
140 }
141 if (hasEnergyShort()) {
142 std::cout << "Energy (short): " << energy_short_ch << std::endl;
143 }
144
145 PrintFlags();
147}
148
149Bool_t CoMPASSReader::Open(const char *fname) {
150 if (!BinaryReader::Open(fname)) {
151 return kFALSE;
152 }
153
154 file.read(reinterpret_cast<char *>(&global_header), sizeof(UShort_t));
155 bytes_read += sizeof(UShort_t);
156
157 if ((global_header & 0xCAE0) != 0xCAE0) {
158 std::cerr << "WARNING: Header does not match 0xCAEx pattern: 0x" << std::hex
159 << global_header << std::dec << std::endl;
160 }
161
162 std::cout << "CoMPASS file opened: " << fname << std::endl;
163
164 return kTRUE;
165}
166
167Bool_t CoMPASSReader::Open(const char *fname, UShort_t header_override) {
168 if (!BinaryReader::Open(fname)) {
169 return kFALSE;
170 }
171
172 if (header_override != 0) {
173 global_header = header_override;
174 std::cout << "CoMPASS continuation file opened: " << fname << std::endl;
175 std::cout << "Using provided global header: 0x" << std::hex << global_header
176 << std::dec << std::endl;
177 std::cout << "Control bits: " << std::bitset<4>(global_header & 0x000F)
178 << std::endl;
179 } else {
180 file.read(reinterpret_cast<char *>(&global_header), sizeof(UShort_t));
181 bytes_read += sizeof(UShort_t);
182
183 if ((global_header & 0xCAE0) != 0xCAE0) {
184 std::cerr << "WARNING: Header does not match 0xCAEx pattern: 0x"
185 << std::hex << global_header << std::dec << std::endl;
186 }
187
188 std::cout << "CoMPASS file opened: " << fname << std::endl;
189 std::cout << "Global header: 0x" << std::hex << global_header << std::dec
190 << std::endl;
191 std::cout << "Control bits: " << std::bitset<4>(global_header & 0x000F)
192 << std::endl;
193 }
194
195 return kTRUE;
196}
197
199 if (IsEOF()) {
200 return kFALSE;
201 }
202
203 current_event.header = global_header;
204
205 file.read(reinterpret_cast<char *>(&current_event.board), sizeof(UShort_t));
206 file.read(reinterpret_cast<char *>(&current_event.channel), sizeof(UShort_t));
207 file.read(reinterpret_cast<char *>(&current_event.timestamp),
208 sizeof(ULong64_t));
209 bytes_read += sizeof(UShort_t) * 2 + sizeof(ULong64_t);
210
211 if (current_event.hasEnergyCh()) {
212 file.read(reinterpret_cast<char *>(&current_event.energy_ch),
213 sizeof(UShort_t));
214 bytes_read += sizeof(UShort_t);
215 }
216
217 if (current_event.hasEnergyCal()) {
218 file.read(reinterpret_cast<char *>(&current_event.energy_cal),
219 sizeof(Double_t));
220 bytes_read += sizeof(Double_t);
221 }
222
223 if (current_event.hasEnergyShort()) {
224 file.read(reinterpret_cast<char *>(&current_event.energy_short_ch),
225 sizeof(UShort_t));
226 bytes_read += sizeof(UShort_t);
227 }
228
229 file.read(reinterpret_cast<char *>(&current_event.flags), sizeof(UInt_t));
230 bytes_read += sizeof(UInt_t);
231
232 if (current_event.hasWaveform()) {
233 file.read(reinterpret_cast<char *>(&current_event.waveform_code),
234 sizeof(UChar_t));
235 file.read(reinterpret_cast<char *>(&current_event.num_samples),
236 sizeof(UInt_t));
237 bytes_read += sizeof(UChar_t) + sizeof(UInt_t);
238
239 // Sanity-cap num_samples; a corrupt header can read garbage and trigger
240 // a multi-GB allocation in TArrayS::Set.
241 const UInt_t kMaxSamples = 1u << 20;
242 if (current_event.num_samples > kMaxSamples) {
243 std::cerr << "ERROR: implausible num_samples "
244 << current_event.num_samples << " at byte " << bytes_read
245 << " (treating as truncated)" << std::endl;
246 return kFALSE;
247 }
248
249 current_event.samples.Set(current_event.num_samples);
250 std::vector<UShort_t> sample_buf(current_event.num_samples);
251 file.read(reinterpret_cast<char *>(sample_buf.data()),
252 current_event.num_samples * sizeof(UShort_t));
253 if (file.fail()) {
254 std::cerr << "WARNING: Incomplete waveform at byte " << bytes_read
255 << " (truncated file, event discarded)" << std::endl;
256 return kFALSE;
257 }
258 for (UInt_t i = 0; i < current_event.num_samples; i++) {
259 current_event.samples.SetAt(static_cast<Short_t>(sample_buf[i]), i);
260 }
261 bytes_read += current_event.num_samples * sizeof(UShort_t);
262 }
263
264 return kTRUE;
265}
266
268 : event_size(0), board_id(0), pattern(0), channel(0), event_counter(0),
270 std::cout << "This version of wavedump binary conversion for 742 family "
271 "digitizers is based on manual "
272 "revision 21"
273 << std::endl;
274}
275
277 std::cout << "WaveDump 742 Event..." << std::endl;
278 std::cout << "Event size: " << event_size << std::endl;
279 std::cout << "Board ID: " << board_id << std::endl;
280 std::cout << "Channel: " << channel << std::endl;
281 std::cout << "Event counter: " << event_counter << std::endl;
282 std::cout << "Group trigger tag: " << group_trigger_time_tag << std::endl;
283 std::cout << "DC offset: " << dc_offset << std::endl;
284 std::cout << "Start cell: " << start_index_cell << std::endl;
285 std::cout << "Samples: " << samples.GetSize() << std::endl;
286}
287
289 if (IsEOF()) {
290 return kFALSE;
291 }
292
293 UInt_t headers[8];
294 file.read(reinterpret_cast<char *>(headers), 8 * sizeof(UInt_t));
295 if (file.fail()) {
296 if (file.gcount() > 0) {
297 std::cerr << "WARNING: Incomplete event header at byte " << bytes_read
298 << " (" << file.gcount() << " of " << 8 * sizeof(UInt_t)
299 << " header bytes read, truncated file)" << std::endl;
300 }
301 return kFALSE;
302 }
303 bytes_read += 8 * sizeof(UInt_t);
304
305 current_event.event_size = headers[0];
306 current_event.board_id = headers[1];
307 current_event.pattern = headers[2];
308 current_event.channel = headers[3];
309 current_event.event_counter = headers[4];
310 current_event.group_trigger_time_tag = headers[5];
311 current_event.dc_offset = headers[6];
312 current_event.start_index_cell = headers[7];
313
314 // event_size is in bytes and includes the 8-word (32-byte) header
315 UInt_t header_bytes = 8 * sizeof(UInt_t);
316 if (current_event.event_size <= header_bytes) {
317 std::cerr << "ERROR: Invalid event_size " << current_event.event_size
318 << " at event " << current_event.event_counter << std::endl;
319 return kFALSE;
320 }
321
322 UInt_t sample_bytes = current_event.event_size - header_bytes;
323 UInt_t sample_size = sample_bytes / sizeof(UInt_t);
324
325 current_event.samples.Set(sample_size);
326
327 if (corrections_enabled) {
328 std::vector<Float_t> float_samples(sample_size);
329 file.read(reinterpret_cast<char *>(float_samples.data()), sample_bytes);
330 if (file.fail()) {
331 std::cerr << "WARNING: Incomplete event at byte " << bytes_read
332 << " (truncated file, event " << current_event.event_counter
333 << " discarded)" << std::endl;
334 return kFALSE;
335 }
336 for (UInt_t i = 0; i < sample_size; i++) {
337 current_event.samples.SetAt(static_cast<Short_t>(float_samples[i]), i);
338 }
339 } else {
340 std::vector<UInt_t> int_samples(sample_size);
341 file.read(reinterpret_cast<char *>(int_samples.data()), sample_bytes);
342 if (file.fail()) {
343 std::cerr << "WARNING: Incomplete event at byte " << bytes_read
344 << " (truncated file, event " << current_event.event_counter
345 << " discarded)" << std::endl;
346 return kFALSE;
347 }
348 for (UInt_t i = 0; i < sample_size; i++) {
349 current_event.samples.SetAt(static_cast<Short_t>(int_samples[i] & 0xFFF),
350 i);
351 }
352 }
353
354 bytes_read += sample_bytes;
355
356 return kTRUE;
357}
358
359// --- SOLData ---
360
362 : block_header(0), channel(0), energy(0), energy_short(0), timestamp(0),
364 is_psd(kFALSE), down_sampling(0), board_fail(0), flush(0), trigger_thr(0),
366 ana_probe_type[0] = 0xFF;
367 ana_probe_type[1] = 0xFF;
368 dig_probe_type[0] = 0xFF;
369 dig_probe_type[1] = 0xFF;
370 dig_probe_type[2] = 0xFF;
371 dig_probe_type[3] = 0xFF;
372}
373
375 switch (data_type) {
376 case ALL:
377 return "ALL (full traces)";
378 case OneTrace:
379 return "OneTrace";
380 case NoTrace:
381 return "NoTrace";
382 case Minimum:
383 return "Minimum";
384 case MiniWithFineTime:
385 return "MiniWithFineTime";
386 case Raw:
387 return "Raw (FPGA)";
388 default:
389 return TString::Format("Unknown (0x%x)", data_type);
390 }
391}
392
393void SOLData::Print() const {
394 std::cout << "SOL block..." << std::endl;
395 std::cout << "Block header: 0x" << std::hex << block_header << std::dec
396 << std::endl;
397 std::cout << "Data type: " << static_cast<Int_t>(data_type) << " ("
398 << getDataTypeName().Data() << ")" << std::endl;
399 std::cout << "PSD: " << (is_psd ? "YES" : "NO") << std::endl;
400 std::cout << "Channel: " << static_cast<Int_t>(channel) << std::endl;
401 std::cout << "Energy: " << energy << std::endl;
402 if (is_psd) {
403 std::cout << "Energy (short): " << energy_short << std::endl;
404 }
405 std::cout << "Timestamp: " << timestamp << " ns" << std::endl;
406 std::cout << "Fine ts: " << fine_timestamp << " ps" << std::endl;
407 std::cout << "Flags (high): 0x" << std::hex << flags_high << std::dec
408 << std::endl;
409 std::cout << "Flags (low): 0x" << std::hex << flags_low << std::dec
410 << std::endl;
411
412 if (data_type == ALL) {
413 std::cout << "Down-sampling: " << static_cast<Int_t>(down_sampling)
414 << std::endl;
415 std::cout << "Board fail: " << static_cast<Int_t>(board_fail)
416 << std::endl;
417 std::cout << "Flush: " << static_cast<Int_t>(flush) << std::endl;
418 std::cout << "Trigger thr: " << trigger_thr << std::endl;
419 std::cout << "Event size: " << event_size << " bytes" << std::endl;
420 std::cout << "Agg counter: " << agg_counter << std::endl;
421 std::cout << "Trace length: " << trace_len << " samples" << std::endl;
422 std::cout << "Analog probe 0: " << static_cast<Int_t>(ana_probe_type[0])
423 << std::endl;
424 std::cout << "Analog probe 1: " << static_cast<Int_t>(ana_probe_type[1])
425 << std::endl;
426 std::cout << "Digital probe 0: " << static_cast<Int_t>(dig_probe_type[0])
427 << std::endl;
428 std::cout << "Digital probe 1: " << static_cast<Int_t>(dig_probe_type[1])
429 << std::endl;
430 std::cout << "Digital probe 2: " << static_cast<Int_t>(dig_probe_type[2])
431 << std::endl;
432 std::cout << "Digital probe 3: " << static_cast<Int_t>(dig_probe_type[3])
433 << std::endl;
434 }
435
436 if (hasTraces()) {
437 const Int_t *a0 = getAnalog0();
438 const Int_t *a1 = getAnalog1();
439 if (data_type == ALL && a0) {
440 std::cout << "Trace0 samples: " << getSamples() << std::endl;
441 std::cout << "Trace1 samples: " << getSamples() << std::endl;
442 UInt_t n = TMath::Min(5, static_cast<Int_t>(getSamples()));
443 std::cout << "Trace0 first 5: ";
444 for (UInt_t i = 0; i < n; i++) {
445 std::cout << a0[i] << " ";
446 }
447 std::cout << std::endl;
448 std::cout << "Trace1 first 5: ";
449 for (UInt_t i = 0; i < n; i++) {
450 std::cout << a1[i] << " ";
451 }
452 std::cout << std::endl;
453 } else if (data_type == OneTrace) {
454 const Int_t *ot = getOneTrace();
455 if (ot) {
456 std::cout << "Trace0 samples: " << getSamples() << std::endl;
457 UInt_t n = TMath::Min(5, static_cast<Int_t>(getSamples()));
458 std::cout << "Trace0 first 5: ";
459 for (UInt_t i = 0; i < n; i++) {
460 std::cout << ot[i] << " ";
461 }
462 std::cout << std::endl;
463 }
464 }
465 }
466}
467
468// --- SOLReader ---
469
470// Little-endian reads from a block buffer, mirroring the memcpy+shift
471// pattern used elsewhere in this file.
472static Long64_t ReadLe6(const char *p) {
473 Long64_t v = 0;
474 for (Int_t i = 0; i < 6; i++)
475 v |= static_cast<Long64_t>(static_cast<unsigned char>(p[i]))
476 << (static_cast<Long64_t>(i) * 8);
477 return v;
478}
479
480static ULong64_t ReadLe8(const char *p) {
481 ULong64_t v = 0;
482 for (Int_t i = 0; i < 8; i++)
483 v |= static_cast<ULong64_t>(static_cast<unsigned char>(p[i]))
484 << (static_cast<ULong64_t>(i) * 8);
485 return v;
486}
487
488// Keep at least needBytes valid bytes at the front of buf, compacting the
489// unconsumed tail and reading from inFile as required. Returns kFALSE when
490// the file is exhausted before the request can be satisfied.
491static Bool_t RefillInput(std::ifstream &inFile, std::vector<char> &buf,
492 Int_t &pos, Int_t &len, Long64_t needBytes) {
493 if (Long64_t(len - pos) >= needBytes)
494 return kTRUE;
495 if (pos > 0) {
496 std::memmove(buf.data(), buf.data() + pos, size_t(len - pos));
497 len -= pos;
498 pos = 0;
499 }
500 if (Long64_t(buf.size()) < needBytes)
501 buf.resize(size_t(needBytes));
502 while (Long64_t(len) < needBytes) {
503 inFile.read(buf.data() + len, Long64_t(buf.size()) - len);
504 Long64_t got = inFile.gcount();
505 len += Int_t(got);
506 if (got <= 0)
507 return kFALSE;
508 }
509 return kTRUE;
510}
511
512std::vector<TString> SOLReader::SplitSolFileByTime(const char *inputFile,
513 const char *outputDir,
514 Double_t chunkSeconds,
515 Int_t &totalBlocks,
516 Int_t &totalChunks) {
517 std::vector<TString> outputFiles;
518 totalBlocks = 0;
519 totalChunks = 0;
520
521 gSystem->mkdir(outputDir, kTRUE);
522
523 TString baseName = TString(inputFile);
524 Int_t lastSlash = baseName.Last('/');
525 if (lastSlash >= 0) {
526 baseName = baseName(lastSlash + 1, baseName.Length() - lastSlash - 1);
527 }
528 Int_t dotPos = baseName.Last('.sol');
529 if (dotPos >= 3) {
530 baseName = baseName(0, dotPos - 3);
531 }
532
533 std::ifstream inFile(inputFile, std::ios::binary);
534 if (!inFile.is_open()) {
535 std::cerr << "ERROR: Cannot open input file " << inputFile << std::endl;
536 return outputFiles;
537 }
538
539 TString outPath = TString(outputDir) + "/" + baseName + "_chunk%03d.sol";
540 TString currentOutPath = Form(outPath.Data(), 0);
541 std::ofstream outFile(currentOutPath.Data(), std::ios::binary);
542
543 if (!outFile.is_open()) {
544 std::cerr << "ERROR: Cannot open output file " << currentOutPath
545 << std::endl;
546 inFile.close();
547 return outputFiles;
548 }
549
550 Long64_t chunkEndTs = 0;
551 Int_t chunkIndex = 0;
552 Bool_t firstBlock = kTRUE;
553
554 // Application-level I/O batching: blocks are parsed straight out of a
555 // large input buffer and staged in a large output buffer, so per-block
556 // parsing never costs individual stream calls. Minimum-format files are
557 // ~11 bytes per block; without batching the per-block stream overhead
558 // caps throughput around 15 MB/s instead of the disk's ~2 GB/s.
559 const Int_t kBulkRead = 4 * 1024 * 1024;
560 const Int_t kBulkWrite = 4 * 1024 * 1024;
561 std::vector<char> ibuf(kBulkRead);
562 std::vector<char> obuf;
563 obuf.reserve(kBulkWrite);
564 Int_t iPos = 0; // next unconsumed byte in ibuf
565 Int_t iLen = 0; // valid bytes in ibuf
566 Long64_t filePos = 0; // absolute file offset of ibuf[iPos]
567
568 while (RefillInput(inFile, ibuf, iPos, iLen, 2)) {
569 UShort_t blockHeader;
570 std::memcpy(&blockHeader, ibuf.data() + iPos, 2);
571 if ((blockHeader & 0xAA00) != 0xAA00) {
572 std::cerr << "WARNING: Invalid block header 0x" << std::hex << blockHeader
573 << std::dec << " at position " << filePos + 2 << std::endl;
574 break;
575 }
576
577 UChar_t dataType = blockHeader & 0xF;
578 Bool_t isPsd = ((blockHeader >> 4) & 0xF) != 0;
579
580 Long64_t headerSize = 0;
581 Long64_t timestamp = 0;
582 Bool_t hasTimestamp = kTRUE;
583 Long64_t dataBytes = 0;
584
585 if (dataType == SOLData::ALL) {
586 headerSize = 1 + 2 + (isPsd ? 2 : 0) + 6 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 8 +
587 4 + 8 + 2 + 4;
588 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize))
589 break;
590
591 Int_t tsOffset = 2 + 1 + 2 + (isPsd ? 2 : 0);
592 timestamp = ReadLe6(ibuf.data() + iPos + tsOffset);
593
594 Int_t tlOffset = tsOffset + 6 + 2 + 1 + 2 + 1 + 1 + 1 + 2 + 8 + 4;
595 ULong64_t traceLen = ReadLe8(ibuf.data() + iPos + tlOffset);
596 if (traceLen > 0) {
597 dataBytes = traceLen * 12;
598 }
599
600 } else if (dataType == SOLData::OneTrace) {
601 headerSize = 1 + 2 + (isPsd ? 2 : 0) + 6 + 2 + 1 + 2 + 8 + 1;
602 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize))
603 break;
604
605 Int_t tsOffset = 2 + 1 + 2 + (isPsd ? 2 : 0);
606 timestamp = ReadLe6(ibuf.data() + iPos + tsOffset);
607
608 Int_t tlOffset = tsOffset + 6 + 2 + 1 + 2;
609 ULong64_t traceLen = ReadLe8(ibuf.data() + iPos + tlOffset);
610 if (traceLen > 0) {
611 dataBytes = traceLen * sizeof(Int_t);
612 }
613
614 } else if (dataType == SOLData::NoTrace) {
615 headerSize = 1 + 2 + (isPsd ? 2 : 0) + 6 + 2 + 1 + 2;
616 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize))
617 break;
618
619 Int_t tsOffset = 2 + 1 + 2 + (isPsd ? 2 : 0);
620 timestamp = ReadLe6(ibuf.data() + iPos + tsOffset);
621
622 } else if (dataType == SOLData::Minimum) {
623 headerSize = 1 + 2 + (isPsd ? 2 : 0) + 6;
624 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize))
625 break;
626
627 Int_t tsOffset = 2 + 1 + 2 + (isPsd ? 2 : 0);
628 timestamp = ReadLe6(ibuf.data() + iPos + tsOffset);
629
630 } else if (dataType == SOLData::MiniWithFineTime) {
631 headerSize = 1 + 2 + (isPsd ? 2 : 0) + 6 + 2;
632 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize))
633 break;
634
635 Int_t tsOffset = 2 + 1 + 2 + (isPsd ? 2 : 0);
636 timestamp = ReadLe6(ibuf.data() + iPos + tsOffset);
637
638 } else if (dataType == SOLData::Raw) {
639 headerSize = 8;
640 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize))
641 break;
642
643 hasTimestamp = kFALSE;
644 dataBytes = ReadLe8(ibuf.data() + iPos + 2);
645
646 } else {
647 std::cerr << "ERROR: Unknown SOL data type 0x" << std::hex << dataType
648 << std::dec << " at position " << filePos << std::endl;
649 break;
650 }
651
652 // Ensure the full block (header + payload) is available before writing.
653 if (!RefillInput(inFile, ibuf, iPos, iLen, 2 + headerSize + dataBytes))
654 break;
655
656 // Rotate to new chunk if timestamp crosses boundary.
657 // Block that crosses boundary goes into the NEW chunk only.
658 if (hasTimestamp && (firstBlock || timestamp >= chunkEndTs)) {
659 if (!firstBlock) {
660 if (!obuf.empty()) {
661 outFile.write(obuf.data(), obuf.size());
662 obuf.clear();
663 }
664 outFile.close();
665 chunkIndex++;
666 currentOutPath = Form(outPath.Data(), chunkIndex);
667 outFile.open(currentOutPath.Data(), std::ios::binary);
668 if (!outFile.is_open()) {
669 std::cerr << "ERROR: Cannot open output file " << currentOutPath
670 << std::endl;
671 break;
672 }
673 }
674 chunkEndTs = timestamp + static_cast<Long64_t>(chunkSeconds * 1e9);
675 firstBlock = kFALSE;
676 outputFiles.push_back(currentOutPath);
677 totalChunks++;
678 } else if (!hasTimestamp && firstBlock) {
679 firstBlock = kFALSE;
680 outputFiles.push_back(currentOutPath);
681 totalChunks++;
682 }
683
684 // Stage the block in the write buffer, flushing when it grows large.
685 const Int_t blockBytes = Int_t(2 + headerSize + dataBytes);
686 obuf.insert(obuf.end(), ibuf.begin() + iPos,
687 ibuf.begin() + iPos + blockBytes);
688 iPos += blockBytes;
689 filePos += blockBytes;
690 if (Long64_t(obuf.size()) >= kBulkWrite) {
691 outFile.write(obuf.data(), obuf.size());
692 obuf.clear();
693 }
694
695 totalBlocks++;
696 }
697
698 // Flush any staged output before closing.
699 if (!obuf.empty()) {
700 outFile.write(obuf.data(), obuf.size());
701 obuf.clear();
702 }
703 outFile.close();
704 inFile.close();
705
706 std::cout << "Split " << totalBlocks << " blocks into " << totalChunks
707 << " chunks in " << outputDir << std::endl;
708
709 return outputFiles;
710}
711
713 if (IsEOF()) {
714 return kFALSE;
715 }
716
717 current_event = SOLData();
718
719 UShort_t block_header;
720 file.read(reinterpret_cast<char *>(&block_header), sizeof(UShort_t));
721 if (file.fail()) {
722 return kFALSE;
723 }
724 bytes_read += sizeof(UShort_t);
725
726 if ((block_header & 0xAA00) != 0xAA00) {
727 std::cerr << "WARNING: SOL block header mismatch at block " << block_id
728 << " (got 0x" << std::hex << block_header << std::dec
729 << ", expected 0xAAxx)" << std::endl;
730 return kFALSE;
731 }
732
733 current_event.block_header = block_header;
734 current_event.data_type = block_header & 0xF;
735 current_event.is_psd = ((block_header >> 4) & 0xF) != 0;
736
737 if (current_event.data_type == SOLData::ALL) {
738 Int_t header_bytes = 1 + 2 + (current_event.is_psd ? 2 : 0) + 6 + 2 + 1 +
739 2 + 1 + 1 + 1 + 2 + 8 + 4 + 8 + 2 + 4;
740 std::vector<char> hdr(header_bytes);
741 file.read(hdr.data(), header_bytes);
742 if (file.fail()) {
743 std::cerr << "WARNING: Incomplete ALL-format block at byte " << bytes_read
744 << " (truncated file)" << std::endl;
745 return kFALSE;
746 }
747 bytes_read += header_bytes;
748
749 Int_t off = 0;
750 current_event.channel = static_cast<UChar_t>(hdr[off]);
751 off += 1;
752 std::memcpy(&current_event.energy, hdr.data() + off, 2);
753 off += 2;
754 if (current_event.is_psd) {
755 std::memcpy(&current_event.energy_short, hdr.data() + off, 2);
756 off += 2;
757 }
758 std::memcpy(&current_event.timestamp, hdr.data() + off, 6);
759 off += 6;
760 std::memcpy(&current_event.fine_timestamp, hdr.data() + off, 2);
761 off += 2;
762 current_event.flags_high = static_cast<UChar_t>(hdr[off]);
763 off += 1;
764 std::memcpy(&current_event.flags_low, hdr.data() + off, 2);
765 off += 2;
766 current_event.down_sampling = static_cast<UChar_t>(hdr[off]);
767 off += 1;
768 current_event.board_fail = static_cast<UChar_t>(hdr[off]);
769 off += 1;
770 current_event.flush = static_cast<UChar_t>(hdr[off]);
771 off += 1;
772 std::memcpy(&current_event.trigger_thr, hdr.data() + off, 2);
773 off += 2;
774 std::memcpy(&current_event.event_size, hdr.data() + off, 8);
775 off += 8;
776 std::memcpy(&current_event.agg_counter, hdr.data() + off, 4);
777 off += 4;
778 std::memcpy(&current_event.trace_len, hdr.data() + off, 8);
779 off += 8;
780 std::memcpy(current_event.ana_probe_type, hdr.data() + off, 2);
781 off += 2;
782 std::memcpy(current_event.dig_probe_type, hdr.data() + off, 4);
783
784 if (current_event.trace_len > 0) {
785 ULong64_t n_samples = current_event.trace_len;
786 const ULong64_t kMaxTraceLen = 100000;
787 Long64_t trace_bytes = n_samples * 12;
788
789 if (n_samples > kMaxTraceLen) {
790 std::cerr << "WARNING: Implausible trace length " << n_samples
791 << " at block " << block_id << " (skipping trace data)"
792 << std::endl;
793 file.seekg(trace_bytes, std::ios::cur);
794 bytes_read += trace_bytes;
795 } else if (skip_traces) {
796 file.seekg(trace_bytes, std::ios::cur);
797 bytes_read += trace_bytes;
798 } else {
799 current_event.trace_data.resize(trace_bytes);
800 file.read(current_event.trace_data.data(), trace_bytes);
801 if (file.fail() ||
802 file.gcount() != static_cast<std::streamsize>(trace_bytes)) {
803 std::cerr << "WARNING: Incomplete traces at block " << block_id
804 << " (truncated file)" << std::endl;
805 return kFALSE;
806 }
807 bytes_read += trace_bytes;
808 }
809 }
810
811 } else if (current_event.data_type == SOLData::OneTrace) {
812 Int_t header_bytes =
813 1 + 2 + (current_event.is_psd ? 2 : 0) + 6 + 2 + 1 + 2 + 8 + 1;
814 std::vector<char> hdr(header_bytes);
815 file.read(hdr.data(), header_bytes);
816 if (file.fail()) {
817 std::cerr << "WARNING: Incomplete OneTrace-format block at byte "
818 << bytes_read << " (truncated file)" << std::endl;
819 return kFALSE;
820 }
821 bytes_read += header_bytes;
822
823 Int_t off = 0;
824 current_event.channel = static_cast<UChar_t>(hdr[off]);
825 off += 1;
826 std::memcpy(&current_event.energy, hdr.data() + off, 2);
827 off += 2;
828 if (current_event.is_psd) {
829 std::memcpy(&current_event.energy_short, hdr.data() + off, 2);
830 off += 2;
831 }
832 std::memcpy(&current_event.timestamp, hdr.data() + off, 6);
833 off += 6;
834 std::memcpy(&current_event.fine_timestamp, hdr.data() + off, 2);
835 off += 2;
836 current_event.flags_high = static_cast<UChar_t>(hdr[off]);
837 off += 1;
838 std::memcpy(&current_event.flags_low, hdr.data() + off, 2);
839 off += 2;
840 std::memcpy(&current_event.trace_len, hdr.data() + off, 8);
841 off += 8;
842 current_event.ana_probe_type[0] = static_cast<UChar_t>(hdr[off]);
843
844 if (current_event.trace_len > 0) {
845 ULong64_t n_samples = current_event.trace_len;
846 const ULong64_t kMaxTraceLen = 100000;
847 Long64_t trace_bytes = n_samples * sizeof(Int_t);
848
849 if (n_samples > kMaxTraceLen) {
850 std::cerr << "WARNING: Implausible trace length " << n_samples
851 << " at block " << block_id << " (skipping trace data)"
852 << std::endl;
853 file.seekg(trace_bytes, std::ios::cur);
854 bytes_read += trace_bytes;
855 } else if (skip_traces) {
856 file.seekg(trace_bytes, std::ios::cur);
857 bytes_read += trace_bytes;
858 } else {
859 current_event.trace_data.resize(trace_bytes);
860 file.read(current_event.trace_data.data(), trace_bytes);
861 if (file.fail()) {
862 std::cerr << "WARNING: Incomplete trace at block " << block_id
863 << " (truncated file)" << std::endl;
864 return kFALSE;
865 }
866 bytes_read += trace_bytes;
867 }
868 }
869
870 } else if (current_event.data_type == SOLData::NoTrace) {
871 Int_t header_bytes =
872 1 + 2 + (current_event.is_psd ? 2 : 0) + 6 + 2 + 1 + 2 +
873 (current_event.is_psd ? 20 : 18) -
874 (1 + 2 + (current_event.is_psd ? 2 : 0) + 6 + 2 + 1 + 2);
875 std::vector<char> hdr(header_bytes);
876 file.read(hdr.data(), header_bytes);
877 if (file.fail()) {
878 std::cerr << "WARNING: Incomplete NoTrace-format block at byte "
879 << bytes_read << " (truncated file)" << std::endl;
880 return kFALSE;
881 }
882 bytes_read += header_bytes;
883
884 Int_t off = 0;
885 current_event.channel = static_cast<UChar_t>(hdr[off]);
886 off += 1;
887 std::memcpy(&current_event.energy, hdr.data() + off, 2);
888 off += 2;
889 if (current_event.is_psd) {
890 std::memcpy(&current_event.energy_short, hdr.data() + off, 2);
891 off += 2;
892 }
893 std::memcpy(&current_event.timestamp, hdr.data() + off, 6);
894 off += 6;
895 std::memcpy(&current_event.fine_timestamp, hdr.data() + off, 2);
896 off += 2;
897 current_event.flags_high = static_cast<UChar_t>(hdr[off]);
898 off += 1;
899 std::memcpy(&current_event.flags_low, hdr.data() + off, 2);
900
901 } else if (current_event.data_type == SOLData::MiniWithFineTime) {
902 Int_t header_bytes = 1 + 2 + (current_event.is_psd ? 2 : 0) + 6 + 2;
903 std::vector<char> hdr(header_bytes);
904 file.read(hdr.data(), header_bytes);
905 if (file.fail()) {
906 std::cerr << "WARNING: Incomplete MiniWithFineTime block at byte "
907 << bytes_read << " (truncated file)" << std::endl;
908 return kFALSE;
909 }
910 bytes_read += header_bytes;
911
912 Int_t off = 0;
913 current_event.channel = static_cast<UChar_t>(hdr[off]);
914 off += 1;
915 std::memcpy(&current_event.energy, hdr.data() + off, 2);
916 off += 2;
917 if (current_event.is_psd) {
918 std::memcpy(&current_event.energy_short, hdr.data() + off, 2);
919 off += 2;
920 }
921 std::memcpy(&current_event.timestamp, hdr.data() + off, 6);
922 off += 6;
923 std::memcpy(&current_event.fine_timestamp, hdr.data() + off, 2);
924
925 } else if (current_event.data_type == SOLData::Minimum) {
926 Int_t header_bytes = 1 + 2 + (current_event.is_psd ? 2 : 0) + 6;
927 std::vector<char> hdr(header_bytes);
928 file.read(hdr.data(), header_bytes);
929 if (file.fail()) {
930 std::cerr << "WARNING: Incomplete Minimum-format block at byte "
931 << bytes_read << " (truncated file)" << std::endl;
932 return kFALSE;
933 }
934 bytes_read += header_bytes;
935
936 Int_t off = 0;
937 current_event.channel = static_cast<UChar_t>(hdr[off]);
938 off += 1;
939 std::memcpy(&current_event.energy, hdr.data() + off, 2);
940 off += 2;
941 if (current_event.is_psd) {
942 std::memcpy(&current_event.energy_short, hdr.data() + off, 2);
943 off += 2;
944 }
945 std::memcpy(&current_event.timestamp, hdr.data() + off, 6);
946
947 } else if (current_event.data_type == SOLData::Raw) {
948 ULong64_t data_size;
949 file.read(reinterpret_cast<char *>(&data_size), 8);
950 if (file.fail()) {
951 std::cerr << "WARNING: Incomplete Raw-format block at byte " << bytes_read
952 << " (truncated file)" << std::endl;
953 return kFALSE;
954 }
955 bytes_read += 8;
956
957 if (data_size > 0) {
958 file.seekg(data_size, std::ios::cur);
959 bytes_read += data_size;
960 }
961
962 } else {
963 std::cerr << "ERROR: Unknown SOL data type 0x" << std::hex
964 << current_event.data_type << std::dec << " at block " << block_id
965 << std::endl;
966 return kFALSE;
967 }
968
969 current_event.block_id = block_id;
970 block_id++;
971
972 return kTRUE;
973}
974
976 SOLHit hit;
977 hit.channel = current_event.channel;
978 hit.energy = current_event.energy;
979 hit.energy_short = current_event.energy_short;
980 hit.timestamp = current_event.timestamp;
981 hit.fine_timestamp = current_event.fine_timestamp;
982 hit.flags_high = current_event.flags_high;
983 hit.flags_low = current_event.flags_low;
984 hit.data_type = current_event.data_type;
985 hit.is_psd = current_event.is_psd;
986 hit.down_sampling = current_event.down_sampling;
987 hit.board_fail = current_event.board_fail;
988 hit.flush = current_event.flush;
989 hit.trigger_thr = current_event.trigger_thr;
990 hit.event_size = current_event.event_size;
991 hit.agg_counter = current_event.agg_counter;
992 hit.block_id = current_event.block_id;
993 hit.trace_len = current_event.trace_len;
994 hit.ana_probe_type[0] = current_event.ana_probe_type[0];
995 hit.ana_probe_type[1] = current_event.ana_probe_type[1];
996 hit.dig_probe_type[0] = current_event.dig_probe_type[0];
997 hit.dig_probe_type[1] = current_event.dig_probe_type[1];
998 hit.dig_probe_type[2] = current_event.dig_probe_type[2];
999 hit.dig_probe_type[3] = current_event.dig_probe_type[3];
1000 return hit;
1001}
Readers for CAEN and SOLARIS acquisition binary formats.
Long64_t bytes_read
std::ifstream file
virtual Bool_t Open(const char *fname)
Open a binary file for reading.
Bool_t IsEOF() const
Whether the file is exhausted or was never opened.
Bool_t hasDeadtime() const
Deadtime occurred before this event.
Bool_t isFirstAfterBusy() const
First event after a busy period.
UShort_t energy_short_ch
Short-gate energy; valid if hasEnergyShort().
UShort_t channel
Channel index on that board.
UShort_t energy_ch
Energy in ADC channel units; valid if hasEnergyCh().
std::vector< TString > getActiveFlags() const
Names of every status flag currently set in flags.
void Print() const
Print the whole event to stdout.
TArrayS samples
Waveform samples; valid if hasWaveform().
void PrintFlags() const
Print the active status flags to stdout.
Bool_t isOverTemperature() const
The board reported over-temperature.
UShort_t board
Digitiser board id.
Bool_t hasTriggerLost() const
At least one trigger was lost.
Bool_t hasPLLLockLoss() const
The PLL lost lock.
Bool_t isPileup() const
Pileup was detected.
Bool_t hasNTriggersCounted() const
The counted-trigger field is present.
UInt_t flags
Status bits; see the flag constants above.
Bool_t hasTimestampRollover() const
The timestamp counter rolled over.
Bool_t hasEnergyShort() const
Whether the record carries a short-gate energy (header bit 2).
Double_t energy_cal
Calibrated energy; valid if hasEnergyCal().
Bool_t hasFineTimestamp() const
A fine timestamp is available.
Bool_t isNotMatchedTimeFilter() const
The event failed the coincidence time filter.
Bool_t hasMemoryFull() const
Board memory was full.
ULong64_t timestamp
Acquisition timestamp, in picoseconds.
Bool_t has1024Triggers() const
1024 triggers have been counted.
UShort_t header
Global header word; says which fields are valid.
Bool_t isInputSaturating() const
The input is currently saturating.
UInt_t num_samples
Length of samples.
Bool_t hasSaturation() const
The input saturated within the integration gate.
CoMPASSData()
Construct with all fields zeroed.
Bool_t hasEnergyCal() const
Whether the record carries a calibrated energy (header bit 1).
TString getWaveformCodeName() const
Human-readable name for waveform_code.
void PrintHeader() const
Print the header fields to stdout.
Bool_t isADCShutdown() const
The ADC shut down.
Bool_t hasNTriggersLost() const
The lost-trigger count field is present.
Bool_t isFakeEvent() const
Synthetic event inserted by the acquisition.
Bool_t hasEnergyCh() const
Whether the record carries an uncalibrated energy (header bit 0).
void PrintWaveform() const
Print the waveform samples to stdout.
UChar_t waveform_code
Processing stage of samples; see WaveformCode.
Bool_t hasTimestampResetExt() const
An external timestamp reset was applied.
Bool_t hasWaveform() const
Whether the record carries waveform samples (header bit 3).
Bool_t ReadEvent() override
Decode the next event into the current-event object.
Bool_t Open(const char *fname) override
Open a file and read its global header.
One decoded SOL block, optionally carrying its traces.
UChar_t down_sampling
UShort_t energy_short
void Print() const
UInt_t agg_counter
Bool_t is_psd
UChar_t ana_probe_type[2]
ULong64_t block_id
UShort_t block_header
Raw block header word.
UChar_t board_fail
Bool_t hasTraces() const
Whether this block carries traces.
UShort_t trigger_thr
UShort_t energy
UChar_t flush
const Int_t * getAnalog0() const
Pointer to the first analogue trace.
UChar_t data_type
const Int_t * getOneTrace() const
Pointer to the single trace of a OneTrace block.
TString getDataTypeName() const
Human-readable name for data_type.
ULong64_t timestamp
UShort_t fine_timestamp
UShort_t flags_high
UChar_t channel
UInt_t getSamples() const
Samples per trace in this block.
SOLData()
Construct with all fields zeroed and no traces.
ULong64_t event_size
UChar_t dig_probe_type[4]
const Int_t * getAnalog1() const
Pointer to the second analogue trace.
ULong64_t trace_len
UShort_t flags_low
SOLHit ToHit() const
Copy the current block's header fields into a standalone SOLHit.
static std::vector< TString > SplitSolFileByTime(const char *inputFile, const char *outputDir, Double_t chunkSeconds, Int_t &totalBlocks, Int_t &totalChunks)
Split a run file into time-bounded chunks.
Bool_t ReadEvent() override
Decode the next record into the derived reader's current event.
UInt_t pattern
Front-panel I/O pattern word.
WaveDump742Data()
Construct with all fields zeroed.
UInt_t start_index_cell
Switched-capacitor start cell.
UInt_t group_trigger_time_tag
Group trigger time tag.
TArrayS samples
Waveform samples, in ADC counts.
UInt_t event_counter
Board's running event counter.
UInt_t board_id
Digitiser board id.
void Print() const
UInt_t event_size
Record size in 32-bit words.
UInt_t channel
Channel index.
UInt_t dc_offset
DC offset applied to this channel.
Bool_t ReadEvent() override
Decode the next record into the derived reader's current event.
Lightweight SOL block: header fields only, traces stripped.
Bool_t is_psd
Whether this block came from a PSD firmware.
UChar_t data_type
Block format; see SOLData::DataType.
UChar_t board_fail
Board failure indicator.
ULong64_t event_size
Size of the source block, in bytes.
UShort_t energy_short
Short-gate energy; meaningful when is_psd.
UChar_t dig_probe_type[4]
Digital probe selection for traces 0 to 3.
ULong64_t trace_len
Samples the source block carried, though this struct stores none of them.
UChar_t down_sampling
Trace downsampling factor applied on-board.
UShort_t trigger_thr
Trigger threshold in force.
ULong64_t timestamp
Coarse timestamp, in nanoseconds.
UChar_t flush
Flush indicator.
ULong64_t block_id
Zero-based index of this block in the file.
UChar_t ana_probe_type[2]
Analogue probe selection for traces 0 and 1.
UShort_t flags_high
Upper status word.
UShort_t energy
Long-gate energy, in ADC channel units.
UChar_t channel
Channel index.
UShort_t fine_timestamp
Sub-nanosecond refinement of timestamp.
UInt_t agg_counter
Aggregate counter from the board.
UShort_t flags_low
Lower status word.