10 : polarity_(config.polarity), trigger_threshold_(config.trigger_threshold),
11 num_samples_baseline_(config.num_samples_baseline),
12 pre_samples_(config.pre_samples), post_samples_(config.post_samples),
13 pre_gate_(config.pre_gate), short_gate_(config.short_gate),
14 long_gate_(config.long_gate), max_events_(config.max_events),
15 verbose_(config.verbose),
16 adc_saturation_code_(config.adc_saturation_code),
17 sample_waveforms_to_save_(config.sample_waveforms_to_save),
18 sample_waveforms_saved_(0), output_file_(nullptr), output_tree_(nullptr),
19 store_waveforms_(config.store_waveforms), save_waveform_(new TArrayF()),
20 input_format_(config.input_format) {}
24 if (output_file_->IsOpen()) {
25 output_file_->Close();
28 output_file_ =
nullptr;
29 if (store_waveforms_) {
30 save_waveform_ =
nullptr;
33 delete save_waveform_;
34 save_waveform_ =
nullptr;
38 Int_t n = samples.GetSize();
43 Int_t raw_max = samples.At(0);
46 for (Int_t i = 1; i < n; ++i) {
47 if (samples[i] > raw_max)
51 for (Int_t i = 1; i < n; ++i) {
52 if (samples[i] < raw_max)
60 if (trigger_pos < 0) {
61 stats_.rejected_no_trigger++;
65 if (trigger_pos < pre_samples_ ||
66 (save_waveform_->GetSize() - trigger_pos) <= post_samples_) {
67 stats_.rejected_insufficient_samples++;
83 if (sample_waveforms_saved_ < sample_waveforms_to_save_) {
87 current_features_ = features;
91 if (current_baseline_rms_valid_) {
92 stats_.sum_baseline_rms_accepted += current_baseline_rms_;
93 stats_.baseline_rms_count_accepted++;
98std::mutex WaveformProcessingUtils::canvas_mutex_;
102 std::lock_guard<std::mutex> lock(canvas_mutex_);
104 Int_t n = waveform.GetSize();
105 const Float_t *arr = waveform.GetArray();
106 std::vector<Double_t> x(n), y(n);
107 for (Int_t i = 0; i < n; ++i) {
114 TGraph *graph =
new TGraph(n, x.data(), y.data());
120 TString output_name = Form(
"%s_waveform_%04d", current_output_name_.Data(),
121 sample_waveforms_saved_);
128 sample_waveforms_saved_++;
132 Int_t n = samples.GetSize();
134 Float_t baseline = 0;
135 Int_t baseline_samples = TMath::Min(num_samples_baseline_, n);
136 for (Int_t i = 0; i < baseline_samples; ++i) {
137 baseline += samples.GetAt(i);
139 baseline /= baseline_samples;
141 current_baseline_rms_valid_ = kFALSE;
142 if (baseline_samples > 1) {
143 Double_t sum_sq = 0.0;
144 for (Int_t i = 0; i < baseline_samples; ++i) {
145 Double_t d = samples.GetAt(i) - baseline;
148 Float_t rms = TMath::Sqrt(sum_sq / (baseline_samples - 1));
149 stats_.sum_baseline_rms += rms;
150 stats_.baseline_rms_count++;
151 current_baseline_rms_ = rms;
152 current_baseline_rms_valid_ = kTRUE;
155 save_waveform_->Set(n);
156 if (polarity_ == -1) {
157 for (Int_t i = 0; i < n; ++i) {
158 save_waveform_->SetAt(baseline - samples.GetAt(i), i);
161 for (Int_t i = 0; i < n; ++i) {
162 save_waveform_->SetAt(samples.GetAt(i) - baseline, i);
168 Int_t n = waveform.GetSize();
169 const Float_t *arr = waveform.GetArray();
171 Float_t peak_value = *std::max_element(arr, arr + n);
172 Float_t trigger_level = peak_value * trigger_threshold_;
174 for (Int_t i = 0; i < n; ++i) {
175 if (arr[i] >= trigger_level) {
185 Int_t start = trigger_pos - pre_samples_;
186 Int_t end = TMath::Min(trigger_pos + post_samples_, waveform.GetSize());
187 Int_t crop_size = end - start;
189 TArrayF cropped(crop_size);
190 const Float_t *src = waveform.GetArray();
191 for (Int_t i = 0; i < crop_size; ++i) {
192 cropped[i] = src[start + i];
195 *save_waveform_ = cropped;
201 Int_t integration_start = pre_samples_ - pre_gate_;
203 Int_t n = cropped_wf.GetSize();
204 const Float_t *arr = cropped_wf.GetArray();
206 const Float_t *max_it = std::max_element(arr, arr + n);
213 Int_t negative_samples = 0;
214 Int_t short_end = TMath::Min(integration_start + short_gate_, n);
215 Int_t long_end = TMath::Min(integration_start + long_gate_, n);
217 for (Int_t i = integration_start; i < long_end; ++i) {
218 Float_t sample_value = arr[i];
223 if (sample_value < 0)
230 Float_t(negative_samples) / Float_t(long_end - integration_start);
241 stats_.rejected_clipped++;
246 stats_.rejected_baseline++;
251 stats_.rejected_negative_integral++;
259 std::cout <<
"Waveform processing statistics..." << std::endl;
260 std::cout <<
"Total processed: " << stats_.total_processed << std::endl;
261 std::cout << std::endl;
262 std::cout <<
"Accepted: " << stats_.accepted << std::endl;
263 std::cout << std::endl;
264 std::cout <<
"Rejected no trigger: " << stats_.rejected_no_trigger
266 std::cout <<
"Rejected clipped ADC: " << stats_.rejected_clipped << std::endl;
267 std::cout <<
"Rejected insufficient samples: "
268 << stats_.rejected_insufficient_samples << std::endl;
269 std::cout <<
"Rejected negative integral: "
270 << stats_.rejected_negative_integral << std::endl;
271 std::cout <<
"Rejected bad baseline: " << stats_.rejected_baseline
273 std::cout << std::endl;
275 if (stats_.total_processed > 0) {
276 std::cout <<
"Acceptance rate: "
277 << 100 * Float_t(stats_.accepted) /
278 Float_t(stats_.total_processed)
281 if (stats_.baseline_rms_count > 0) {
282 std::cout <<
"Mean baseline RMS (all processed): "
283 << stats_.sum_baseline_rms / stats_.baseline_rms_count
284 <<
" ADC counts (over " << stats_.baseline_rms_count
285 <<
" waveforms)" << std::endl;
287 if (stats_.baseline_rms_count_accepted > 0) {
288 std::cout <<
"Mean baseline RMS (accepted only): "
289 << stats_.sum_baseline_rms_accepted /
290 stats_.baseline_rms_count_accepted
291 <<
" ADC counts (over " << stats_.baseline_rms_count_accepted
292 <<
" waveforms)" << std::endl;
294 std::cout << std::endl;
298 std::ofstream stats_file(stats_path.Data(), std::ios::app);
299 if (stats_file.is_open()) {
300 stats_file <<
"Waveform processing statistics..." << std::endl;
301 stats_file <<
"Total processed: " << stats_.total_processed << std::endl;
302 stats_file << std::endl;
303 stats_file <<
"Accepted: " << stats_.accepted << std::endl;
304 stats_file << std::endl;
305 stats_file <<
"Rejected no trigger: " << stats_.rejected_no_trigger
307 stats_file <<
"Rejected clipped ADC: " << stats_.rejected_clipped
309 stats_file <<
"Rejected insufficient samples: "
310 << stats_.rejected_insufficient_samples << std::endl;
311 stats_file <<
"Rejected negative integral: "
312 << stats_.rejected_negative_integral << std::endl;
313 stats_file <<
"Rejected bad baseline: " << stats_.rejected_baseline
315 stats_file << std::endl;
317 if (stats_.total_processed > 0) {
318 stats_file <<
"Acceptance rate: "
319 << 100 * Float_t(stats_.accepted) /
320 Float_t(stats_.total_processed)
323 if (stats_.baseline_rms_count > 0) {
324 stats_file <<
"Mean baseline RMS (all processed): "
325 << stats_.sum_baseline_rms / stats_.baseline_rms_count
326 <<
" ADC counts (over " << stats_.baseline_rms_count
327 <<
" waveforms)" << std::endl;
329 if (stats_.baseline_rms_count_accepted > 0) {
330 stats_file <<
"Mean baseline RMS (accepted only): "
331 << stats_.sum_baseline_rms_accepted /
332 stats_.baseline_rms_count_accepted
333 <<
" ADC counts (over " << stats_.baseline_rms_count_accepted
334 <<
" waveforms)" << std::endl;
336 stats_file << std::endl;
341 const TString output_name) {
342 current_output_name_ = output_name;
343 sample_waveforms_saved_ = 0;
344 if (!save_waveform_) {
345 save_waveform_ =
new TArrayF();
349 if (gSystem->AccessPathName(base_dir)) {
350 gSystem->mkdir(base_dir, kTRUE);
354 TString clear_path = base_dir +
"/" + output_name +
".stats";
355 std::ofstream(clear_path.Data(), std::ios::trunc);
357 TString output_subpath = output_name +
".root";
358 TString output_filename = base_dir +
"/" + output_subpath;
360 if (!output_file_ || output_file_->IsZombie()) {
361 std::cout <<
"ERROR: Could not create output file " << output_filename
366 output_tree_ =
new TTree(
"features",
"Waveform Features");
368 output_tree_->Branch(
"pulse_height", ¤t_features_.pulse_height,
370 output_tree_->Branch(
"trigger_position", ¤t_features_.trigger_position,
371 "trigger_position/I");
372 output_tree_->Branch(
"short_integral", ¤t_features_.short_integral,
374 output_tree_->Branch(
"long_integral", ¤t_features_.long_integral,
376 output_tree_->Branch(
"timestamp", ¤t_features_.timestamp,
379 if (store_waveforms_) {
380 output_tree_->Branch(
"Samples", &save_waveform_);
381 std::cout <<
"Storing events that pass cuts." << std::endl;
384 TFile *file = TFile::Open(filepath,
"READ");
385 if (!file || file->IsZombie()) {
386 std::cout <<
"ERROR opening file: " << filepath << std::endl;
390 TTree *tree =
static_cast<TTree *
>(file->Get(
"Data_R"));
392 std::cout <<
"ERROR: TTree 'Data_R' not found in " << filepath << std::endl;
397 TArrayS *samples =
nullptr;
398 TArrayI *solaris_trace0 =
nullptr;
399 UInt_t trigger_time_tag = 0;
400 TArrayI converted_samples;
403 samples =
new TArrayS();
404 tree->SetBranchAddress(
"Samples", &samples);
405 tree->SetBranchAddress(
"Timestamp", ¤t_timestamp_);
407 samples =
new TArrayS();
408 tree->SetBranchAddress(
"Samples", &samples);
409 tree->SetBranchAddress(
"TriggerTimeTag", &trigger_time_tag);
411 solaris_trace0 =
new TArrayI();
412 tree->SetBranchAddress(
"Trace0", &solaris_trace0);
413 tree->SetBranchAddress(
"Timestamp", ¤t_timestamp_);
416 Long64_t n_entries = tree->GetEntries();
418 for (Long64_t entry = 0; entry < n_entries; ++entry) {
419 if (max_events_ > 0 && stats_.accepted >= max_events_) {
422 tree->GetEntry(entry);
423 stats_.total_processed++;
426 Int_t n = samples->GetSize();
427 converted_samples.Set(n);
428 for (Int_t i = 0; i < n; ++i) {
429 converted_samples.SetAt(samples->At(i), i);
433 current_timestamp_ =
static_cast<ULong64_t
>(trigger_time_tag);
434 Int_t n = samples->GetSize();
435 converted_samples.Set(n);
436 for (Int_t i = 0; i < n; ++i) {
437 converted_samples.SetAt(samples->At(i), i);
446 delete solaris_trace0;
451 output_tree_->Write(
"", TObject::kOverwrite);
452 output_file_->Close();
454 output_file_ =
nullptr;
455 output_tree_ =
nullptr;
456 if (store_waveforms_) {
457 save_waveform_ =
nullptr;
468 const std::vector<TString> &filepaths,
469 const std::vector<TString> &output_names,
472 ROOT::EnableThreadSafety();
475 Int_t n_files = Int_t(filepaths.size());
476 Int_t n_workers = max_workers > 0
478 : Int_t(std::thread::hardware_concurrency());
479 n_workers = TMath::Min(n_workers, n_files);
481 std::cout <<
"Processing " << n_files <<
" files with " << n_workers
482 <<
" workers." << std::endl;
484 std::function<Bool_t(
const TString &,
const TString &)> process_one =
485 [&config](
const TString &filepath,
const TString &output_name) -> Bool_t {
487 Bool_t result = processor->
ProcessFile(filepath, output_name);
492 for (Int_t i = 0; i < n_files; i += n_workers) {
493 std::vector<std::future<Bool_t>> futures;
494 Int_t batch_end = TMath::Min(i + n_workers, n_files);
496 for (Int_t j = i; j < batch_end; ++j) {
497 futures.push_back(std::async(std::launch::async, process_one,
498 std::cref(filepaths[j]),
499 std::cref(output_names[j])));
502 for (
size_t j = 0; j < futures.size(); ++j) {
503 Bool_t result = futures[j].get();
504 std::cout <<
"Finished: " << output_names[i + j]
505 << (result ?
" [OK]" :
" [FAILED]") << std::endl;
@ kPNG
PNG raster output; line width 2.
static void SaveFigure(TCanvas *canvas, TString output_filename, TString output_subdirectory="", PlotSaveOptions save_options=PlotSaveOptions::kBOTH)
Write a canvas to disk under the configured plots base directory.
static void ConfigureGraph(TGraph *graph, Int_t color, const TString title="")
Apply the house style to a graph without drawing it.
static void SetStylePreferences(PlotSaveFormat save_format=PlotSaveFormat::kPNG)
Install the global ROOT style and choose the output format.
static TCanvas * GetConfiguredCanvas(Bool_t logy=kFALSE)
Create a 1200x800 canvas with grid and ticks already set up.
TString GetRootFilesBaseDir()
Current base directory for relative subpaths, without trailing slash.
void SetThreadSafe(Bool_t enabled=kTRUE)
Enable ROOT thread safety and serialise file opening.
TFile * OpenForWriting(const TString &subpath, const TString mode="RECREATE")
Open a ROOT file for writing, creating parent directories first.
Everything needed to configure one processing run.