def fun(delta, tau, kind, waveform, start_mf): try: if kind == 'exp': start, baseline, value = integrate.filter(self.data, bslen, delta_exp=delta, tau_exp=tau) elif kind == 'ma': start, baseline, value = integrate.filter(self.data, bslen, delta_ma=delta, length_ma=tau) elif kind == 'mf': start, baseline, value = integrate.filter( self.data, bslen, delta_mf=delta, length_mf=tau, waveform_mf=waveform, start_mf=start_mf) else: raise KeyError(kind) except ZeroDivisionError: return 0 corr_value = (baseline - value[:, 0])[~self.ignore] snr = single_filter_analysis(corr_value) return -snr
def fingerplot(delta=0, bslen=8000): """ Make a fingerplot with the matched filter. Parameters ---------- delta : int The offset from the trigger minus the waveform length. bslen : int Number of samples used for baseline computation. """ fig1 = plt.figure('fingersnrmf-fingerplot-1', figsize=[7.27, 5.73]) fig2 = plt.figure('fingersnrmf-fingerplot-2', figsize=[6.4, 4.8]) fig1.clf() fig2.clf() trigger, baseline, value = integrate.filter(data, bslen=bslen, delta_mf=len(waveform) + delta, waveform_mf=waveform) value = value[:, 0] corr_value = (baseline - value)[~ignore] snr = single_filter_analysis(corr_value, fig1, fig2) print(f'snr = {snr:.2f}') fig1.tight_layout() fig2.tight_layout() fig1.show() fig2.show()
def make_template(data, ignore=None, length=2000, fig=None): """ 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. fig : matplotlib figure, optional If given, plot the waveform. Return ------ waveform : array (length,) The waveform. It is normalized to unit sum. """ 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, bottom, top = np.quantile(data1pe, [0.5, 0.25, 0.75], axis=0) # waveform = np.mean(data1pe, axis=0) # std = np.std(data1pe, axis=0) # bottom = waveform - std # top = waveform + std if fig is not None: ax = fig.subplots(1, 1) ax.fill_between(np.arange(length), bottom, top, facecolor='lightgray', label='25%-75% quantiles') ax.plot(waveform, 'k-', label='median') ax.grid() ax.legend(loc='best') ax.set_xlabel('Sample number') ax.set_ylabel('ADC value') ax.set_title('Template for matched filter (unnormalized)') return waveform / np.sum(waveform)
def snrseries(deltamin=-50, deltamax=50, ndelta=101, bslen=8000): """ Compute SNR as a function of the offset from the trigger where the filter is evaluated ("delta"). Parameters ---------- deltamin, deltamax, ndelta: int The delta values where the SNR is computed is a range of ndelta values from deltamin to deltamax. The range is specified relative to the number of samples of the waveform, i.e. delta=0 -> delta=len(waveform). bslen : int The number of samples used for the baseline. Returns ------- delta : array (ndelta,) Values of delta. snr : array (ndelta,) The SNR for each delta. """ delta = np.rint(np.linspace(deltamin, deltamax, ndelta)) + len(waveform) start, baseline, value = integrate.filter(data, bslen=bslen, delta_mf=delta, waveform_mf=waveform) snr = np.empty(len(delta)) for i in tqdm.tqdm(range(len(snr))): val = value[:, i] corr_value = (baseline - val)[~ignore] snr[i] = single_filter_analysis(corr_value) output = delta, snr snrplot(*output) return output
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
nphotons = [1, 3, 5] length = 2000 leftmargin = 100 rep = 1 filename = 'darksidehd/nuvhd_lf_3x_tile57_77K_64V_6VoV_1.wav' ########################### data = readwav.readwav(filename, mmap=False) mask = ~readwav.spurious_signals(data) # 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.single_filter_analysis( corr_value[mask], return_full=True) assert snr > 15 assert len(center) > 2 # Select the data corresponding to a different number of photoelectrons. nphotons = np.sort(nphotons) datanph = [] for nph in nphotons: lower = (center[nph - 1] + center[nph]) / 2 upper = (center[nph] + center[nph + 1]) / 2 selection = (lower < corr_value) & (corr_value < upper) & mask indices = np.flatnonzero(selection)[:rep]
def fingerplot(self, tau, delta, kind='ma', bslen=8000): """ Make a fingerplot with a specific filter and print the SNR. Parameters ---------- tau : int Length parameter of the filter. delta : int Offset from the trigger where the filter is evaluated. kind : str One of 'ma' = moving average, 'exp' = exponential moving average, 'mf' = matched filter, 'mfn' = matched filter with noise correction. bslen : int Number of samples used for the baseline. Return ------ fig1, fig2 : matplotlib figures """ if kind == 'ma': start, baseline, value = integrate.filter(self.data, bslen, delta_ma=delta, length_ma=tau) elif kind == 'exp': start, baseline, value = integrate.filter(self.data, bslen, delta_exp=delta, tau_exp=tau) elif kind in ('mf', 'mfn'): w0, offset = self.template.matched_filter_template( self.template.template_length, timebase=1, aligned='trigger') assert offset == 0, offset start_mf = integrate.make_start_mf(w0, tau) if kind == 'mfn': waveform = make_template.make_template(self.data, self.ignore, tau + start_mf[0], noisecorr=True) else: waveform = w0 start, baseline, value = integrate.filter(self.data, bslen, delta_mf=delta, waveform_mf=waveform, length_mf=tau, start_mf=start_mf) else: raise KeyError(kind) corr_value = (baseline - value[:, 0])[~self.ignore] fig1 = plt.figure('fingersnr-fingerplot-1', figsize=[7.27, 5.73]) fig2 = plt.figure('fingersnr-fingerplot-2', figsize=[6.4, 4.8]) fig1.clf() fig2.clf() snr = single_filter_analysis(corr_value, fig1, fig2) print(f'snr = {snr:.2f}') fig1.tight_layout() fig2.tight_layout() fig1.show() fig2.show() return fig1, fig2
def snrseries(self, tau=_default_tau, ndelta=_default_ndelta, bslen=8000, plot=True): """ Compute SNR as a function of tau and delta. Make a plot and return the results. Parameters ---------- tau : array (ntau,) Length parameter of the filters. ndelta : int Number of values of offset from trigger explored in a hardcoded range. bslen : int The number of samples used for the baseline. plot : bool If False, do not plot. The plot can be done separately by calling snrplot(). Returns ------- tau : array (ntau,) Values of the filter scale parameter. delta_ma : array (ntau, ndelta) Values of the offset for the moving average for each tau. delta_exp : array (ntau, ndelta) Values of the offset for the exponential moving average for each tau. delta_mf : array (ntau, ndelta) Values of the offset for the matched filter for each tau. waveform : array (max(tau),) Template used for the matched filter. snr : array (3, ntau, ndelta) The SNR for (moving average, exponential moving average, matched filter), and for each length parameter (tau) and offset from trigger (delta). """ # Generate delta ranges. ntau = len(tau) tau, delta_ma, delta_exp, delta_mf = self.make_tau_delta(tau, ndelta, flat=True) print('make template for matched filter...') # w0 = make_template.make_template(self.data, self.ignore, np.max(tau) + 200, noisecorr=False) w0, offset = self.template.matched_filter_template( self.template.template_length, timebase=1, aligned='trigger') assert offset == 0, offset start_mf = integrate.make_start_mf(w0, tau) # waveform = make_template.make_template(self.data, self.ignore, np.max(tau + start_mf), noisecorr=True) waveform = w0 print('computing filters...') start, baseline, vma, vexp, vmf = integrate.filter( self.data, bslen, delta_ma, tau, delta_exp, tau, delta_mf, waveform, tau, start_mf) snr = np.empty((3, len(tau))) print('analysing filter output...') for i in tqdm.tqdm(range(snr.shape[1])): for j, value in enumerate([vma, vexp, vmf]): value = value[:, i] corr_value = (baseline - value)[~self.ignore] snr[j, i] = single_filter_analysis(corr_value) # Reshape arrays, make plot and return. output = (tau.reshape(ntau, ndelta)[:, 0], ) for x in [delta_ma, delta_exp, delta_mf]: output += (x.reshape(ntau, ndelta), ) output += (waveform, snr.reshape(-1, ntau, ndelta)) if plot: self.snrplot(*output) return output