-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmake_template.py
147 lines (123 loc) · 5.01 KB
/
make_template.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""
DEPRECATED, use `Template` from `toy.py`.
Make a template for the matched filter with an LNGS wav.
"""
import numpy as np
from matplotlib import pyplot as plt
from scipy import linalg, signal
import integrate
from single_filter_analysis import single_filter_analysis
def make_template(data, ignore=None, length=2000, noisecorr=False, fig=None, figcov=None, norm=True):
"""
Make a template waveform for the matched filter.
Parameters
----------
data : array (nevents, 2, 15001)
As returned by readwav.readwav().
ignore : bool array (nevents,), optional
Flag events to be ignored.
length : int
Number of samples of the waveform.
noisecorr : bool
If True, optimize the filter for the noise spectrum.
fig : matplotlib figure, optional
If given, plot the waveform.
figcov : matplotlib figure, optional
If given, plot the covariance matrix of the noise as a bitmap.
norm : bool
If True, normalize the output to unit sum, so that applying it behaves
like a weighted mean.
Return
------
waveform : array (length,)
The waveform.
"""
if ignore is None:
ignore = np.zeros(len(data), bool)
# Run a moving average filter to find and separate the signals by number of
# photoelectrons.
trigger, baseline, value = integrate.filter(data, bslen=8000, length_ma=1470, delta_ma=1530)
corr_value = baseline - value[:, 0]
snr, center, width = single_filter_analysis(corr_value[~ignore], return_full=True)
assert snr > 15
assert len(center) > 2
# Select the data corresponding to 1 photoelectron and subtract the
# baseline.
lower = (center[0] + center[1]) / 2
upper = (center[1] + center[2]) / 2
selection = (lower < corr_value) & (corr_value < upper) & ~ignore
t = int(np.median(trigger))
data1pe = data[selection, 0, t:t + length] - baseline[selection].reshape(-1, 1)
# Compute the waveform as the median of the signals.
waveform = np.median(data1pe, axis=0)
# waveform = np.mean(data1pe, axis=0)
if noisecorr:
# Select baseline data and compute covariance matrix over a slice with
# the same length of the template.
bsend = t - 100
N = 2 * (length + length % 2)
databs = data[~ignore, 0, bsend - N:bsend]
cov = np.cov(databs, rowvar=False)
cov = toeplitze(cov)
s = slice(N // 4, N // 4 + length)
cov = cov[s, s]
# use cov(fweights=~ignore) to avoid using ~ignore
# Correct the waveform.
wnocov = waveform
waveform = linalg.solve(cov, waveform, assume_a='pos')
waveform *= linalg.norm(cov) / len(waveform)
if fig is not None:
axs = fig.subplots(2, 1)
ax = axs[0]
if noisecorr:
ax.plot(wnocov / np.max(np.abs(wnocov)), label='assuming white noise')
ax.plot(waveform / np.max(np.abs(waveform)), label='corrected for actual noise', zorder=-1)
else:
# std = np.std(data1pe, axis=0)
# bottom = waveform - std
# top = waveform + std
bottom, top = np.quantile(data1pe, [0.25, 0.75], axis=0)
ax.fill_between(np.arange(length), bottom, top, facecolor='lightgray', label='25%-75% quantiles')
ax.plot(waveform, 'k-', label='median')
ax.set_ylabel('ADC value')
ax.grid()
ax.legend(loc='best')
ax.set_xlabel('Sample number')
ax.set_title('Template for matched filter')
ax = axs[1]
f, s = signal.periodogram(wnocov if noisecorr else waveform, window='hann')
ax.plot(f[1:], np.sqrt(s[1:]), label='spectrum of template')
f, ss = signal.periodogram(data1pe, axis=-1)
s = np.median(ss, axis=0)
ax.plot(f[1:], np.sqrt(s[1:]), label='spectrum of template sources')
ax.set_ylabel('Spectral density [GHz$^{-1/2}$]')
ax.set_xlabel('Frequency [GHz]')
ax.grid()
ax.set_yscale('log')
ax.legend(loc='best')
if noisecorr and figcov is not None:
ax = figcov.subplots(1, 1)
m = np.max(np.abs(cov))
ax.imshow(cov, vmin=-m, vmax=m, cmap='PiYG')
ax.set_title('Noise covariance matrix')
ax.set_xlabel('Sample number [ns]')
ax.set_ylabel('Sample number [ns]')
if norm:
waveform /= np.sum(waveform)
return waveform
def toeplitze(a):
"""
Convert the matrix `a` to a Toeplitz matrix by averaging the shifted rows.
"""
# I think I confused a toeplitz matrix with a circulant matrix when I wrote
# this. Anyway, I used it in a way that works around this by extracting
# the NxN central submatrix of a (2N)x(2N) matrix, so in the end I'm
# doing the right thing.
rowsum = np.zeros(len(a))
for i in range(len(a)):
rowsum += np.roll(a[i], -i)
rowsum /= len(a)
b = np.empty(a.shape)
for i in range(len(a)):
b[i] = np.roll(rowsum, i)
return b