From a1e03a909e3ad86cd8ead3aa154d3a119a29be40 Mon Sep 17 00:00:00 2001
From: Chao Peng <cpeng@anl.gov>
Date: Sat, 18 Apr 2020 18:46:23 -0500
Subject: [PATCH] Adjust the pedestal fitting in analysis code, add a script to
 plot event from root file

---
 scripts/draw_events.py                        | 146 ++++++++++++++++++
 scripts/{ => old}/batch_run.py                |   0
 scripts/{ => old}/draw_samples.py             |   0
 scripts/{ => old}/draw_samples_ana.py         |   0
 scripts/{ => old}/fit_npe.C                   |   0
 scripts/{ => old}/rootlogon.C                 |   0
 scripts/{ => old}/sample_analysis.py          |   0
 .../analyze_waveform.py                       |   8 +-
 .../draw_waveform_events.py                   |   2 +-
 src/esb_analyze.cpp                           | 110 +++++++++----
 10 files changed, 229 insertions(+), 37 deletions(-)
 create mode 100644 scripts/draw_events.py
 rename scripts/{ => old}/batch_run.py (100%)
 rename scripts/{ => old}/draw_samples.py (100%)
 rename scripts/{ => old}/draw_samples_ana.py (100%)
 rename scripts/{ => old}/fit_npe.C (100%)
 rename scripts/{ => old}/rootlogon.C (100%)
 rename scripts/{ => old}/sample_analysis.py (100%)
 rename scripts/{mode1_analysis => raw_sample_analysis}/analyze_waveform.py (94%)
 rename scripts/{mode1_analysis => raw_sample_analysis}/draw_waveform_events.py (96%)

diff --git a/scripts/draw_events.py b/scripts/draw_events.py
new file mode 100644
index 0000000..de890ac
--- /dev/null
+++ b/scripts/draw_events.py
@@ -0,0 +1,146 @@
+import ROOT
+import os
+import random
+import numpy as np
+import pandas as pd
+import argparse
+from matplotlib import pyplot as plt
+from scipy import signal
+from collections.abc import Iterable
+
+
+
+# fit pedestal
+def fit_pedestal(bk, outlier_thres=2.0, min_lengths=10):
+    ped_mean = np.average(bk)
+    # no need to calculate error
+    if len(bk) < 5:
+        return ped_mean, 0.02*ped_mean
+    ped_err = np.std(bk)
+    # exclude outliers
+    errs = np.abs(bk - ped_mean)/ped_err
+    outliers = errs > outlier_thres
+    if sum(outliers) > 0 and sum(~outliers) > min_lengths:
+        return fit_pedestal(bk[~outliers], outlier_thres, min_lengths)
+    return ped_mean, ped_err
+
+
+def get_peak(vals, thres=8.0, twindow=(0, 192), zero_thres=5.0):
+    if len(vals) < 1:
+        return 0, 0, 0, 0
+    # find peaks
+    peaks, peak_props = signal.find_peaks(vals, prominence=thres)
+    # fit pedestal
+    peak_intervals = pd.IntervalIndex.from_arrays(peak_props['left_bases'], peak_props['right_bases'])
+    ped_mean, ped_err = fit_pedestal(np.array(vals)[[~peak_intervals.contains(x).any() for x in np.arange(len(vals))]])
+    # check peak
+    peak, pos = 0, 0
+    for p, lside, rside in zip(peaks, peak_props['left_bases'], peak_props['right_bases']):
+        if p > twindow[1] or p < twindow[0]:
+            continue
+        height = vals[p] - ped_mean
+        if height < zero_thres*ped_err:
+            continue
+        width = rside - lside
+        l, r = max(0, lside - width), min(len(vals), rside + width)
+        valley = min(vals[l:r]) - ped_mean
+        if valley < 0 and abs(valley) >= height:
+            continue
+        if height > peak:
+            peak = height
+            pos = p
+    return peak, pos, ped_mean, ped_err
+
+
+parser = argparse.ArgumentParser('Draw events from a root file')
+
+parser.add_argument('root_file', help='a root file of waveform data')
+parser.add_argument('output_dir', help='Output directory')
+parser.add_argument('-e', dest='event', help='specify an event number to plot', type=int)
+parser.add_argument('-n', dest='nevents', help='number of random events to plot', type=int, default=10)
+parser.add_argument('--time-min', dest='tmin', help='lower limit of the time window, default 0', type=int, default=0)
+parser.add_argument('--time-max', dest='tmax', help='upper limit of the time window, default 192 (max)', type=int, default=192)
+
+args = parser.parse_args()
+
+
+f = ROOT.TFile.Open(args.root_file, 'read')
+tree = f.EvTree
+
+# alignment of channels
+figsize = (16, 16)
+nrows, ncols = 4, 4
+ch = [
+    'Cer11_5', 'Cer12_5', 'Cer13_5', 'Cer14_5',
+    'Cer21_5', 'Cer22_5', 'Cer23_5', 'Cer24_5',
+    'Cer31_5', 'Cer32_5', 'Cer33_5', 'Cer34_5',
+    'Cer41_5', 'Cer42_5', 'Cer43_5', 'Cer44_5',
+]
+
+pmt = [
+    'A', 'A', 'A', 'A',
+    'B', 'B', 'B', 'B',
+    'C', 'C', 'C', 'C',
+    'D', 'D', 'D', 'D',
+]
+
+pmt_colors = {
+    'A': ('lightcoral', 0.05),
+    'B': ('lightgreen', 0.05),
+    'C': ('moccasin', 0.05),
+    'D': ('lightskyblue', 0.05),
+}
+
+
+def branch_to_array1d(br, t):
+    return np.ndarray((len(br), ), dtype=t, buffer=br)
+
+
+# get event list to plot
+if args.event:
+    evs = [args.event]
+else:
+    evs = random.choices(np.arange(0, tree.GetEntries()), k=args.nevents)
+
+# plot
+for iev in evs:
+    tree.GetEntry(iev)
+    samples = dict()
+    pprops = dict()
+    for c in ch:
+        samples[c] = branch_to_array1d(tree.__getattr__(c + '_raw'), np.int32)
+        pprops[c] = (
+            branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32),
+            branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32),
+            tree.__getattr__(c + '_ped_mean'),
+            tree.__getattr__(c + '_ped_err')
+        )
+
+    fig, axs = plt.subplots(nrows, ncols, figsize=figsize)
+    for (i, ax) in enumerate(axs.flat):
+        box = dict(boxstyle='round', facecolor='white', alpha=0.5)
+        ax.text(0.68, 0.83, ch[i], transform=ax.transAxes, fontsize=16, verticalalignment='top', bbox=box)
+        ax.patch.set_facecolor(pmt_colors[pmt[i]][0])
+        ax.patch.set_alpha(pmt_colors[pmt[i]][1])
+
+        sp = samples[ch[i]]
+        if len(sp) > 0:
+            pp = pprops[ch[i]]
+            ax.plot(np.arange(len(sp)), sp, label='Waveform Data')
+            ax.axhline(y=pp[2], color='red', label='Pedestal Mean')
+            xr = ax.get_xlim()
+            ax.fill_between(xr, pp[2]-pp[3], pp[2]+pp[3], color='red', alpha=0.25, label='Pedestal Error')
+            ax.set_xlim(xr[0], xr[1])
+            ax.axvline(x=args.tmin, color='k', linestyle=':', label='Time Window')
+            ax.axvline(x=args.tmax, color='k', linestyle=':')
+            for peak, pos in zip(pp[0], pp[1]):
+                if pos <= args.tmax and pos >= args.tmin:
+                    ax.plot(pos, peak + pp[2], 'kv', label='Peak')
+            lines, labels = ax.get_legend_handles_labels()
+
+    fig.tight_layout(rect=(0.03, 0.03, 0.99, 0.95))
+    fig.text(0.5, 0.02, 'Sample', ha='center', fontsize=18)
+    fig.text(0.02, 0.5, 'ADC Value', ha='center', rotation=90, fontsize=18)
+    fig.legend(lines, labels, loc = 'upper center', ncol=5, fontsize=18)
+    fig.savefig(os.path.join(args.output_dir, 'sample_ev{}.png'.format(iev)))
+
diff --git a/scripts/batch_run.py b/scripts/old/batch_run.py
similarity index 100%
rename from scripts/batch_run.py
rename to scripts/old/batch_run.py
diff --git a/scripts/draw_samples.py b/scripts/old/draw_samples.py
similarity index 100%
rename from scripts/draw_samples.py
rename to scripts/old/draw_samples.py
diff --git a/scripts/draw_samples_ana.py b/scripts/old/draw_samples_ana.py
similarity index 100%
rename from scripts/draw_samples_ana.py
rename to scripts/old/draw_samples_ana.py
diff --git a/scripts/fit_npe.C b/scripts/old/fit_npe.C
similarity index 100%
rename from scripts/fit_npe.C
rename to scripts/old/fit_npe.C
diff --git a/scripts/rootlogon.C b/scripts/old/rootlogon.C
similarity index 100%
rename from scripts/rootlogon.C
rename to scripts/old/rootlogon.C
diff --git a/scripts/sample_analysis.py b/scripts/old/sample_analysis.py
similarity index 100%
rename from scripts/sample_analysis.py
rename to scripts/old/sample_analysis.py
diff --git a/scripts/mode1_analysis/analyze_waveform.py b/scripts/raw_sample_analysis/analyze_waveform.py
similarity index 94%
rename from scripts/mode1_analysis/analyze_waveform.py
rename to scripts/raw_sample_analysis/analyze_waveform.py
index 7a90455..c194828 100644
--- a/scripts/mode1_analysis/analyze_waveform.py
+++ b/scripts/raw_sample_analysis/analyze_waveform.py
@@ -82,15 +82,15 @@ res = pd.DataFrame(columns=[c + '_peak' for c in list(ch)] + [c + '_pos' for c i
 
 count = 0
 for iev, ev in enumerate(f.EvTree):
-    if iev > 10000:
-        break
     if iev % 100 == 0:
         print('processed {}'.format(iev), end='\r')
+    evpeaks, evposes = [], []
     for c, t in ch.items():
         branch = ev.__getattr__(c + '_raw')
         samples = np.ndarray((len(branch), ), dtype=np.int32, buffer=branch)
         peak, pos, _ , _ = get_peak(samples, args.pthres, (args.tmin, args.tmax))
-        res.loc[iev, c + '_peak'] = peak
-        res.loc[iev, c + '_pos'] = pos
+        evpeaks.append(peak)
+        evposes.append(pos)
+    res.loc[iev, :] = evpeaks + evposes
 
 res.to_csv(args.output_file)
diff --git a/scripts/mode1_analysis/draw_waveform_events.py b/scripts/raw_sample_analysis/draw_waveform_events.py
similarity index 96%
rename from scripts/mode1_analysis/draw_waveform_events.py
rename to scripts/raw_sample_analysis/draw_waveform_events.py
index ea9f264..f720ce4 100644
--- a/scripts/mode1_analysis/draw_waveform_events.py
+++ b/scripts/raw_sample_analysis/draw_waveform_events.py
@@ -51,7 +51,7 @@ def get_peak(vals, thres=8.0, twindow=(0, 192), zero_thres=5.0):
     return peak, pos, ped_mean, ped_err
 
 
-parser = argparse.ArgumentParser('Raw waveform analysis')
+parser = argparse.ArgumentParser('Draw events from raw waveform analysis')
 
 parser.add_argument('root_file', help='a root file of waveform data')
 parser.add_argument('output_dir', help='Output directory')
diff --git a/src/esb_analyze.cpp b/src/esb_analyze.cpp
index a00ce4d..84aeca5 100644
--- a/src/esb_analyze.cpp
+++ b/src/esb_analyze.cpp
@@ -205,6 +205,7 @@ std::vector<Module> read_modules(const std::string &path)
         res.emplace_back(mod);
     }
 
+    /*
     // print out all channels
     std::cout << "Read-in " << res.size() << " modules from \"" << path << "\"." << std::endl;
     for (auto &mod : res) {
@@ -219,6 +220,7 @@ std::vector<Module> read_modules(const std::string &path)
                       << std::endl;
         }
     }
+    */
 
     return res;
 }
@@ -234,58 +236,102 @@ struct BranchData
     float ped_mean, ped_err, best_peak;
 };
 
+#define BUF_SIZE 1000
+static double buffer[BUF_SIZE], wfbuf[BUF_SIZE], bkbuf[BUF_SIZE];
+void refine_pedestal(const std::vector<uint32_t> &raw, float &ped_mean, float &ped_err)
+{
+    int count = 0;
+    float mean = 0.;
+    for (auto &val : raw) {
+        if (std::abs(val - ped_mean) < 2.0 * ped_err) {
+            buffer[count] = val;
+            mean += val;
+            count ++;
+        }
+    }
+    if (count == 0) {
+        return;
+    }
+
+    mean /= count;
+    float change = std::abs(ped_mean - mean);
+
+    // no outliers
+    if (change < 0.05 * ped_err) {
+        return;
+    }
+
+    // recursively refine
+    float err = 0.;
+    for (int i = 0; i < count; ++i) {
+        err += (buffer[i] - mean)*(buffer[i] - mean);
+    }
+    ped_mean = mean;
+    ped_err = std::sqrt(err/count);
+    refine_pedestal(raw, ped_mean, ped_err);
+}
+
+void find_pedestal(const std::vector<uint32_t> &raw, float &ped_mean, float &ped_err)
+{
+    ped_mean = 0;
+    ped_err = 0;
+
+    for (auto &val : raw) {
+        ped_mean += val;
+    }
+    ped_mean /= raw.size();
+
+    for (auto &val : raw) {
+        ped_err += (val - ped_mean)*(val - ped_mean);
+    }
+    ped_err = std::sqrt(ped_err/raw.size());
+
+    refine_pedestal(raw, ped_mean, ped_err);
+}
+
 void waveform_analysis(const std::vector<uint32_t> &raw, BranchData &res)
 {
-    int len = raw.size();
-    res.nraw = len;
-    // sanity check
-    if (len == 0) {
+    // no need to analyze
+    if (raw.empty()) {
         return;
     }
 
-    double wf[len], bk[len];
-    for (int i = 0; i < len; ++i) {
+    // fill in the raw data
+    res.nraw = raw.size();
+    for (size_t i = 0; i < raw.size(); ++i) {
         res.raw[i] = raw[i];
-        bk[i] = raw[i];
-        wf[i] = raw[i];
     }
 
-    // background analysis
-    TSpectrum s;
-    s.Background(bk, len, 6, TSpectrum::kBackDecreasingWindow,
-                 TSpectrum::kBackOrder2, kFALSE,
-                 TSpectrum::kBackSmoothing3, kFALSE);
+    // get pedestals
+    find_pedestal(raw, res.ped_mean, res.ped_err);
 
-    // pedestal mean
-    for (int i = 0; i < len; ++i) { res.ped_mean += bk[i]; }
-    res.ped_mean /= len;
-
-    // pedestal standard deviation
-    for (int i = 0; i < len; ++i) { res.ped_err += std::pow(bk[i] - res.ped_mean, 2); }
-    res.ped_err = std::sqrt(res.ped_err/len);
+    // fill in spectrum buffer
+    for (size_t i = 0; i < raw.size(); ++i) {
+        wfbuf[i] = raw[i] - res.ped_mean;
+    }
 
-    // find the peaks
-    for (int i = 0; i < len; ++i) { wf[i] -= bk[i]; }
-    int npeaks = s.SearchHighRes(wf, bk, len, 5.0, 5.0, false, 3, true, 3);
+    // find peaks
+    TSpectrum s;
+    int npeaks = s.SearchHighRes(wfbuf, bkbuf, res.nraw, 5.0, 5.0, false, 6, false, 3);
 
-    // add peak, timing and integral
+    // fill branch data
     double *pos = s.GetPositionX();
     res.npul = 0;
     res.best_peak = 0;
     for (int i = 0; i < npeaks; ++i) {
-        int j = pos[i];
-        if (wf[j] < 5.*res.ped_err) { continue; }
+        int j = pos[i] - 1;
+        if (wfbuf[j] < 5.*res.ped_err) { continue; }
         res.time[res.npul] = j;
-        res.peak[res.npul] = wf[j];
-        res.integral[res.npul] = wf[j];
+        res.peak[res.npul] = wfbuf[j];
+        res.integral[res.npul] = wfbuf[j];
         j = pos[i] - 1;
-        while ( (j > 0) && (wf[j] > 5.0*res.ped_err) ) { res.integral[res.npul] += wf[j--]; }
+        while ( (j > 0) && (wfbuf[j] > 3.0*res.ped_err) ) { res.integral[res.npul] += wfbuf[j--]; }
         j = pos[i] + 1;
-        while ( (j < len) && (wf[j] > 5.0*res.ped_err) ) { res.integral[res.npul] += wf[j++]; }
+        while ( (j < res.nraw) && (wfbuf[j] > 3.0*res.ped_err) ) { res.integral[res.npul] += wfbuf[j++]; }
         res.npul ++;
 
-        if (j < 30 && j < 10 && wf[j] > res.best_peak) {
-            res.best_peak = wf[j];
+        if (j < 40 && j < 10 && wfbuf[j] > res.best_peak) {
+            res.best_peak = wfbuf[j];
         }
     }
 }
-- 
GitLab