示例#1
0
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)
示例#2
0
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()
示例#3
0
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
示例#4
0
 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
示例#5
0
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
示例#6
0
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]
    indices0 = indices[:, None]
    indices2 = trigger[indices, None] + np.arange(-leftmargin, length)
    datanph.append(data[indices0, 0, indices2])
示例#7
0
import numpy as np
from matplotlib import pyplot as plt

import readwav
import single_filter_analysis
import integrate
import figlatex

filename = 'darksidehd/nuvhd_lf_3x_tile57_77K_64V_6VoV_1.wav'
length = 128

###########################

data = readwav.readwav(filename, mmap=False)
mask = ~readwav.spurious_signals(data)
trigger, baseline, value = integrate.filter(data,
                                            bslen=8000,
                                            length_ma=length,
                                            delta_ma=length)
corr_value = baseline - value[:, 0]

fig = plt.figure(num='figfingerplot', clear=True, figsize=[6.4, 3.5])
single_filter_analysis.single_filter_analysis(corr_value[mask], fig)

fig.tight_layout()
fig.show()

figlatex.save(fig)
示例#8
0
    filename = sys.argv[1]
    length = int(sys.argv[2])
    maxevents = int(sys.argv[3])
except:
    pass

data = readwav.readwav(filename, mmap=False, maxevents=maxevents)
mask = ~np.any(data[:, 0, :8000] < 700, axis=-1)
if data.shape[1] == 2:
    trigger, baseline, value = integrate.filter(data, bslen=8000, length_ma=length, delta_ma=length)
    value = value[:, 0]
else:
    baseline = np.median(data[:, 0, :8000], axis=-1)
    start = 8969 - 21
    value = np.mean(data[:, 0, start:start + length], axis=-1)
corr_value = baseline - value

fig = plt.figure(num='fingerplot', clear=True)
snr, _, _ = single_filter_analysis.single_filter_analysis(corr_value[mask], fig, return_full=True)
print(f'SNR = {snr:.3g}')

ax = fig.get_axes()[0]
ax.set_title(os.path.split(filename)[1])
textbox.textbox(ax, f"""\
first {len(data)} events
ignored {np.count_nonzero(~mask)} events
movavg {length} ns""", fontsize='small', loc='center right')

fig.tight_layout()
fig.show()
示例#9
0
    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
示例#10
0
    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
示例#11
0
 def from_lngs(cls, data, length, mask=None, trigger=None, fig=None):
     """
     Compute a template from 1 p.e. signals in an LNGS wav.
     
     Parameters
     ----------
     data : array (nevents, nchannels, 15001)
         Wav data as read by readwav.readwav(). If it has two channels, the
         second channel is the trigger. If there is only one channel,
         specify the trigger position with `trigger`.
     length : int
         Number of samples of the template (@ 1 GSa/s), starting from the
         beginning of the trigger impulse.
     mask : bool array (nevents,), optional
         Mask for the `data` array.
     trigger : int, optional
         Position of the trigger start in the events. If not specified,
         the trigger is read from the second channel of `data`. If specified,
         it supersedes `data` even with two channels.
     fig : matplotlib figure, optional
         If provided, the fingerplot used to select 1 pe pulses is plotted
         here.
     
     Return
     ------
     self : Template
         A template object.
     """
     self = cls.__new__(cls)
     
     if mask is None:
         mask = np.ones(len(data), bool)
         
     # Find the trigger.
     if trigger is None:
         hastrigger = True
         trigger = firstbelowthreshold.firstbelowthreshold(data[:, 1], 600)
     else:
         hastrigger = False
         trigger = np.full(len(data), trigger)
     
     # Find spurious signals.
     baseline_zone = data[:, 0, :np.min(trigger) - 1000]
     spurious = firstbelowthreshold.firstbelowthreshold(baseline_zone, 700) >= 0
     mask = mask & ~spurious
     
     # Count photoelectrons using the average.
     baseline = np.median(baseline_zone, axis=-1)
     value = meanat(data[:, 0], trigger - 21, 1500)
     corr_value = baseline - value
     snr, center, width = single_filter_analysis(corr_value[mask], return_full=True, fig1=fig)
     
     assert len(center) > 2, len(center)
     
     if snr == 0:
         print(f'warning: 0 and 1 pe peaks may have unexpected position')
     
     oursnr = center[1] / width[0]
     minsnr = 5
     assert oursnr >= minsnr, f'SNR = {oursnr:.3g} < {minsnr}'
 
     # Select the data corresponding to 1 photoelectron.
     lower = (center[0] + center[1]) / 2
     upper = (center[1] + center[2]) / 2
     selection = (lower < corr_value) & (corr_value < upper) & mask
     if fig is not None:
         ax, = fig.get_axes()
         ax.axvspan(lower, upper, color='#f55', zorder=-1, label='selection for template')
         ax.legend(loc='upper right')
 
     # Compute the waveform as the mean of the signals.
     mtrig = np.full(len(trigger), np.median(trigger))
     template = vecmeanat(data[:, 0], baseline, selection, mtrig, length)
     
     # Do a 2-sample moving average to remove the nyquist noise.
     tcs = np.pad(np.cumsum(template), (1, 0))
     filttempl = (tcs[2:] - tcs[:-2]) / 2
     
     # Redo the template aligning with the cross correlation filter.
     delta = 100
     t = trigger[0]
     filtered = signal.fftconvolve(data[selection, 0, t - delta:t + delta + length - 1], -filttempl[None, ::-1], axes=-1, mode='valid')
     indices = np.flatnonzero(selection)
     assert filtered.shape == (len(indices), 2 * delta + 1)
     idx = argminrelmin.argminrelmin(filtered, axis=-1)
     selection[indices] &= idx >= 0
     start = np.zeros(len(data), int)
     start[indices] = t - delta + idx
     template_aligned = vecmeanat(data[:, 0], baseline, selection, start, length)
     
     # Redo the template aligning with the trigger, if available.
     templates = [template, template_aligned]
     if hastrigger:
         template_trigger = vecmeanat(data[:, 0], baseline, selection, trigger, length)
         templates.append(template_trigger)
         
     self.templates = np.stack(templates)
     self.template_rel_std = np.sqrt(width[1] ** 2 - width[0] ** 2) / center[1]
     self.template_N = np.sum(selection)
     
     # For the moving average.
     self._cumsum_templates = np.pad(np.cumsum(self.templates, axis=1), [(0,0),(1,0)])
     
     # Compute the noise standard deviation.
     STDs = np.std(baseline_zone, axis=1)
     self.noise_std = np.mean(STDs[mask])
     
     # Compute the baseline distribution.
     bs = baseline[mask]
     self.baseline = np.mean(bs)
     self.baseline_std = np.std(bs)
     
     # Save filter-aligned template start distribution.
     trigarray = start[selection]
     self.start_min = np.min(trigarray)
     self.start_max = np.max(trigarray)
     self.start_median = np.median(trigarray)
     
     return self