diff --git a/scripts/analyze_waveform.py b/scripts/analyze_waveform.py
index 0298e5f037c002835446a209d9484e62c03be096..8c63f4c53617f2f6d2b79f569db90fb7d72c5c80 100644
--- a/scripts/analyze_waveform.py
+++ b/scripts/analyze_waveform.py
@@ -26,8 +26,8 @@ def get_channels(json_path):
 parser = argparse.ArgumentParser('Raw waveform analysis')
 
 parser.add_argument('root_file', help='a root file of waveform data')
-parser.add_argument('module_file', help='a json database of modules and channels')
-parser.add_argument('output_file', help='Output csv file')
+parser.add_argument('output_dir', help='output directory')
+parser.add_argument('--prefix', dest='prefix', help='prefix for the output files', default='')
 parser.add_argument('--peak-thres', dest='pthres', help='sigma threshold for finding the peak, default 8', type=float, default=8)
 parser.add_argument('--peak-min', dest='pmin', help='lower limit of the peak adc value, default 10', type=int, default=10)
 parser.add_argument('--peak-max', dest='pmax', help='upper limit of the peak adc value, default 8000', type=int, default=8000)
@@ -36,29 +36,79 @@ parser.add_argument('--time-max', dest='tmax', help='upper limit of the time win
 
 args = parser.parse_args()
 
+# figsize = (24, 16)
+# nrows, ncols = 3, 4
+# ch = [
+#     'S2_1', 'S2_2', 'S2_3', 'S2_4',
+#     'S2_5', 'S2_6', 'S2_7', 'S2_8',
+#     'S2_9', 'S2_10', 'S2_11',
+# ]
+
+# figsize = (16, 16)
+# nrows, ncols = 3, 3
+# ch = [
+#     'C4', 'C6_4', 'C7_4',
+#     'C5_4', 'C9_4', 'C8_4',
+#     'C1', 'C2', 'C3',
+# ]
+
+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',
+]
 
 f = ROOT.TFile.Open(args.root_file, 'read')
 tree = f.EvTree
-ch = get_channels(args.module_file)
-res = pd.DataFrame(columns=[c + '_peak' for c in list(ch)] + [c + '_pos' for c in list(ch)])
+
+props = {c: [np.array([]), np.array([])] for c in ch}
 
 for iev in np.arange(0, tree.GetEntries()):
     tree.GetEntry(iev)
-    if iev % 100 == 0:
+    if iev % 1000 == 0:
         print('processed {}'.format(iev), end='\r')
+
+    # channels
     evpeaks, evposes = [], []
-    for c in ch:
-        br_peak = branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)
-        br_pos = branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32)
-
-        ch_peak, ch_pos = 0, 0
-        for peak, pos in zip(br_peak, br_pos):
-            if pos < args.tmax and pos > args.tmin and peak > ch_peak:
-                ch_peak = peak
-                ch_pos = pos
-        evpeaks.append(ch_peak)
-        evposes.append(ch_pos)
-    res.loc[iev, :] = evpeaks + evposes
-
-res.to_csv(args.output_file)
+    for c, prop in props.items():
+        prop[0] = np.concatenate((prop[0], branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)))
+        prop[1] = np.concatenate((prop[1], branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32)))
+print('processed {}'.format(iev))
+
+
+bins = np.arange(0, 5000, step=1)
+indices = (bins[1:] + bins[:-1])/2.
+peaks = pd.DataFrame(index=indices, data={c: np.histogram(prop[0], bins)[0] for c, prop in props.items()})
+
+bins = np.arange(0, 64, step=1)
+indices = (bins[1:] + bins[:-1])/2.
+poses = pd.DataFrame(index=indices, data={c: np.histogram(prop[1], bins)[0] for c, prop in props.items()})
+
+
+# plot
+def plot_hist(df, ny, nx, x_label, y_label, fs=(16, 16), fontsize=18):
+    box = dict(boxstyle='round', facecolor='wheat', alpha=0.3)
+    fig, axs = plt.subplots(ny, nx, figsize=fs)
+    for i, ax in enumerate(axs.flat):
+        if i >= len(df.columns):
+            continue
+        ax.text(0.75, 0.90, df.columns[i], transform=ax.transAxes, fontsize=fontsize - 4, verticalalignment='top', bbox=box)
+        ax.bar(df.index.values, df.iloc[:, i].values, width=pd.Series(data=df.index).diff(1).mean())
+        # ax.patch.set_facecolor('wheat')
+        # ax.patch.set_alpha(0.05)
+        # ax.set_yscale('log')
+    fig.tight_layout(rect=(0.03, 0.05, 0.98, 0.95))
+    fig.text(0.5, 0.02, x_label, ha='center', fontsize=fontsize)
+    fig.text(0.02, 0.5, y_label, ha='center', rotation=90, fontsize=fontsize)
+    return fig
+
+
+plot_hist(peaks, nrows, ncols, 'ADC Value', 'Counts', figsize).savefig(os.path.join(args.output_dir, args.prefix + 'peaks.png'))
+plot_hist(poses, nrows, ncols, 'Sample Number', 'Counts', figsize).savefig(os.path.join(args.output_dir, args.prefix + 'timings.png'))
+
+peaks.to_csv(os.path.join(args.output_dir, args.prefix + 'peaks.csv'))
+poses.to_csv(os.path.join(args.output_dir, args.prefix + 'timings.csv'))
 
diff --git a/scripts/analyze_waveform_time.py b/scripts/analyze_waveform_time.py
new file mode 100644
index 0000000000000000000000000000000000000000..f179ea9d2df7cdd24dc88410e426157d068c023f
--- /dev/null
+++ b/scripts/analyze_waveform_time.py
@@ -0,0 +1,141 @@
+import ROOT
+import os
+import json
+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
+
+
+def branch_to_array1d(br, t):
+    return np.ndarray((len(br), ), dtype=t, buffer=br)
+
+
+def get_channels(json_path):
+    dbf = open(json_path)
+    db = json.load(dbf)
+    channels = dict()
+    for mod, mod_prop in db.items():
+        for ch in mod_prop['channels']:
+            channels[ch['name']] = ch['type']
+    return channels
+
+
+parser = argparse.ArgumentParser('Raw waveform analysis')
+
+parser.add_argument('root_file', help='a root file of waveform data')
+parser.add_argument('output_dir', help='output directory')
+parser.add_argument('--prefix', dest='prefix', help='prefix for the output files', default='')
+parser.add_argument('--peak-thres', dest='pthres', help='sigma threshold for finding the peak, default 8', type=float, default=8)
+parser.add_argument('--peak-min', dest='pmin', help='lower limit of the peak adc value, default 10', type=int, default=10)
+parser.add_argument('--peak-max', dest='pmax', help='upper limit of the peak adc value, default 8000', type=int, default=8000)
+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 63', type=int, default=63)
+
+args = parser.parse_args()
+
+# figsize = (24, 16)
+# nrows, ncols = 3, 4
+# ch = [
+#     'S2_1', 'S2_2', 'S2_3', 'S2_4',
+#     'S2_5', 'S2_6', 'S2_7', 'S2_8',
+#     'S2_9', 'S2_10', 'S2_11',
+# ]
+
+# figsize = (16, 16)
+# nrows, ncols = 3, 3
+# ch = [
+#     'C4', 'C6_4', 'C7_4',
+#     'C5_4', 'C9_4', 'C8_4',
+#     'C1', 'C2', 'C3',
+# ]
+
+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',
+]
+
+ch_pos = [
+    (20, 30), (20, 30), (20, 30), (20, 30),
+    (25, 35), (25, 35), (25, 35), (23, 33),
+    (23, 33), (23, 33), (23, 33), (23, 33),
+    (20, 30), (20, 30), (15, 25), (25, 35),
+]
+
+f = ROOT.TFile.Open(args.root_file, 'read')
+tree = f.EvTree
+
+props = {c: [np.array([]), np.array([])] for c in ch}
+cher_sums = pd.DataFrame(index=np.arange(0, tree.GetEntries()), columns=['sum', 'nhits'], dtype=('float64', 'int64'))
+
+for iev in np.arange(0, tree.GetEntries()):
+    tree.GetEntry(iev)
+    if iev > 1000:
+        break
+    if iev % 1000 == 0:
+        print('processed {}'.format(iev), end='\r')
+
+    csum = 0
+    cnhits = 0
+    # channels
+    evpeaks, evposes = [], []
+    for p, (c, prop) in zip(ch_pos, props.items()):
+        epeak, epos = 0, 0
+        peaks = branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)
+        poses = branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32)
+        for pos, peak in zip(poses, peaks):
+            if pos <= p[1] and pos >= p[0] and peak > epeak:
+                epeak = peak
+                epos = pos
+
+        if epeak >= 1:
+            cnhits += 1
+            csum += epeak
+            prop[0] = np.append(prop[0], epeak)
+            prop[1] = np.append(prop[1], epos)
+
+    cher_sums.loc[iev] = (csum, cnhits)
+print('processed {}'.format(iev))
+
+
+bins = np.arange(0, 5000, step=1)
+indices = (bins[1:] + bins[:-1])/2.
+peaks = pd.DataFrame(index=indices, data={c: np.histogram(prop[0], bins)[0] for c, prop in props.items()})
+
+bins = np.arange(0, 64, step=1)
+indices = (bins[1:] + bins[:-1])/2.
+poses = pd.DataFrame(index=indices, data={c: np.histogram(prop[1], bins)[0] for c, prop in props.items()})
+
+
+# plot
+def plot_hist(df, ny, nx, x_label, y_label, fs=(16, 16), fontsize=18):
+    box = dict(boxstyle='round', facecolor='wheat', alpha=0.3)
+    fig, axs = plt.subplots(ny, nx, figsize=fs)
+    for i, ax in enumerate(axs.flat):
+        if i >= len(df.columns):
+            continue
+        ax.text(0.75, 0.90, df.columns[i], transform=ax.transAxes, fontsize=fontsize - 4, verticalalignment='top', bbox=box)
+        ax.bar(df.index.values, df.iloc[:, i].values, width=pd.Series(data=df.index).diff(1).mean())
+        # ax.patch.set_facecolor('wheat')
+        # ax.patch.set_alpha(0.05)
+        # ax.set_yscale('log')
+    fig.tight_layout(rect=(0.03, 0.05, 0.98, 0.95))
+    fig.text(0.5, 0.02, x_label, ha='center', fontsize=fontsize)
+    fig.text(0.02, 0.5, y_label, ha='center', rotation=90, fontsize=fontsize)
+    return fig
+
+
+plot_hist(peaks, nrows, ncols, 'ADC Value', 'Counts', figsize).savefig(os.path.join(args.output_dir, args.prefix + 'peaks.png'))
+plot_hist(poses, nrows, ncols, 'Sample Number', 'Counts', figsize).savefig(os.path.join(args.output_dir, args.prefix + 'timings.png'))
+
+peaks.to_csv(os.path.join(args.output_dir, args.prefix + 'peaks.csv'))
+poses.to_csv(os.path.join(args.output_dir, args.prefix + 'timings.csv'))
+
+cher_sums[cher_sums.notnull().all(axis=1)].to_csv(os.path.join(args.output_dir, args.prefix + 'sum.csv'))
+
diff --git a/scripts/draw_events.py b/scripts/draw_events.py
index 4ef063886dd2c8eb0517c2b3f7d23878d14a30d5..b751c9688e4fe0156a72f2c9f7acdc92087bedc7 100644
--- a/scripts/draw_events.py
+++ b/scripts/draw_events.py
@@ -70,20 +70,27 @@ 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',
-# ]
-
 ch = [
-    'Cer11_1', 'Cer11_2', 'Cer11_3', 'Cer11_4',
-    'Cer12_1', 'Cer12_2', 'Cer12_3', 'Cer12_4',
-    'Cer13_1', 'Cer13_2', 'Cer13_3', 'Cer13_4',
-    'Cer14_1', 'Cer14_2', 'Cer14_3', 'Cer14_4',
+    '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',
+]
+
+ch_pos = [
+    (20, 30), (20, 30), (20, 30), (20, 30),
+    (25, 35), (25, 35), (25, 35), (23, 33),
+    (23, 33), (23, 33), (23, 33), (23, 33),
+    (20, 30), (20, 30), (15, 25), (25, 35),
 ]
 
+# ch = [
+#     'Cer11_1', 'Cer11_2', 'Cer11_3', 'Cer11_4',
+#     'Cer12_1', 'Cer12_2', 'Cer12_3', 'Cer12_4',
+#     'Cer13_1', 'Cer13_2', 'Cer13_3', 'Cer13_4',
+#     'Cer14_1', 'Cer14_2', 'Cer14_3', 'Cer14_4',
+# ]
+
 
 pmt = [
     'A', 'A', 'A', 'A',
@@ -99,6 +106,16 @@ pmt_colors = {
     'D': ('lightskyblue', 0.05),
 }
 
+# figsize = (16, 16)
+# nrows, ncols = 6, 4
+# ch = [
+#     'C1', 'C2', 'C3', 'C4',
+#     'C5_1', 'C5_2', 'C5_3', 'C5_4',
+#     'C6_1', 'C6_2', 'C6_3', 'C6_4',
+#     'C7_1', 'C7_2', 'C7_3', 'C7_4',
+#     'C8_1', 'C8_2', 'C8_3', 'C8_4',
+#     'C9_1', 'C9_2', 'C9_3', 'C9_4',
+# ]
 
 def branch_to_array1d(br, t):
     return np.ndarray((len(br), ), dtype=t, buffer=br)
@@ -140,10 +157,12 @@ for iev in evs:
             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=':')
+            time = ch_pos[i]
+            ax.axvline(x=time[0], color='k', linestyle=':', label='Time Window')
+            ax.axvline(x=time[1], color='k', linestyle=':')
             for peak, pos in zip(pp[0], pp[1]):
-                if pos <= args.tmax and pos >= args.tmin:
+                # if pos <= args.tmax and pos >= args.tmin:
+                if pos <= time[1] and pos >= time[0]:
                     ax.plot(pos, peak + pp[2], 'kv', label='Peak')
             lines, labels = ax.get_legend_handles_labels()
 
diff --git a/scripts/extract_calosum.py b/scripts/extract_calosum.py
new file mode 100644
index 0000000000000000000000000000000000000000..389d34f6caad5ddc47bb71522736271ab5974c59
--- /dev/null
+++ b/scripts/extract_calosum.py
@@ -0,0 +1,80 @@
+import ROOT
+import os
+import json
+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
+
+
+def branch_to_array1d(br, t):
+    return np.ndarray((len(br), ), dtype=t, buffer=br)
+
+
+def get_channels(json_path):
+    dbf = open(json_path)
+    db = json.load(dbf)
+    channels = dict()
+    for mod, mod_prop in db.items():
+        for ch in mod_prop['channels']:
+            channels[ch['name']] = ch['type']
+    return channels
+
+
+parser = argparse.ArgumentParser('Raw waveform analysis')
+
+parser.add_argument('root_file', help='a root file of waveform data')
+parser.add_argument('output_dir', help='output directory')
+parser.add_argument('--prefix', dest='prefix', help='prefix for the output files', default='')
+parser.add_argument('--peak-thres', dest='pthres', help='sigma threshold for finding the peak, default 8', type=float, default=8)
+parser.add_argument('--peak-min', dest='pmin', help='lower limit of the peak adc value, default 10', type=int, default=10)
+parser.add_argument('--peak-max', dest='pmax', help='upper limit of the peak adc value, default 8000', type=int, default=8000)
+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 63', type=int, default=63)
+
+args = parser.parse_args()
+
+calo_chs = [
+    'C1', 'C2', 'C3', 'C4',
+    'C5_1', 'C5_2', 'C5_3', 'C5_4',
+    'C6_1', 'C6_2', 'C6_3', 'C6_4',
+    'C7_1', 'C7_2', 'C7_3', 'C7_4',
+    'C8_1', 'C8_2', 'C8_3', 'C8_4',
+    'C9_1', 'C9_2', 'C9_3', 'C9_4',
+]
+calo_time = (25, 35)
+calo_thres = 1
+
+f = ROOT.TFile.Open(args.root_file, 'read')
+tree = f.EvTree
+
+props = {c: [np.array([]), np.array([])] for c in ch}
+calo_sums = pd.DataFrame(columns=['calo sum', 'calo nhits'], dtype='float64')
+
+for iev in np.arange(0, tree.GetEntries()):
+    tree.GetEntry(iev)
+    if iev % 1000 == 0:
+        print('processed {}'.format(iev), end='\r')
+
+    # calorimeter trigger
+    calo_sum = 0
+    calo_fires = 0
+    for c in calo_chs:
+        peaks = branch_to_array1d(tree.__getattr__(c + '_Ppeak'), np.float32)
+        times = branch_to_array1d(tree.__getattr__(c + '_Ptime'), np.float32)
+        cp, ct = 0, 0
+        for peak, time in zip(peaks, times):
+            if time <= calo_time[1] and time >= calo_time[0] and peak > cp:
+                cp = peak
+        calo_sum += cp
+        if cp > 1:
+            calo_fires += 1
+    if calo_sum < calo_thres:
+        continue
+    calo_sums.loc[iev] = (calo_sum, calo_fires)
+print('processed {}'.format(iev))
+
+calo_sums.to_csv(os.path.join(args.output_dir, args.prefix + 'calo_sum.csv'))
+
diff --git a/scripts/old/fit_npe.C b/scripts/old/fit_npe.C
index da494bb5725346d7f47cc11a8af43116820b6c07..c261b5941ae0815515545badf198c76c9e6ed365 100644
--- a/scripts/old/fit_npe.C
+++ b/scripts/old/fit_npe.C
@@ -21,3 +21,4 @@ void fit_npe(const char *path, int nhit = 5)
     gaus1->Draw("same");
     gaus2->Draw("same");
 }
+