def soft_pileup_corr(w_in, n_in, tau_in, w_out): """ Fit the baseline to an exponential with the provided time constant and then subtract the best-fit function from the entire waveform. Parameters ---------- w_in : array-like The input waveform n_in : int The number of samples at the beginning of the waveform to fit to an exponential tau_in: float The fixed, exponential time constant w_out : array-like The output waveform with the exponential subtracted Processing Chain Example ------------------------ "wf_bl": { "function": "soft_pileup_corr", "module": "pygama.dsp.processors", "args": ["waveform", "1000", "500*us", "wf_bl"], "unit": "ADC", "prereqs": ["waveform"] } """ w_out[:] = np.nan if np.isnan(w_in).any() or np.isnan(n_in) or np.isnan(tau_in): return if not np.floor(n_in) == n_in: raise DSPFatal('The number of samples is not an integer') if n_in < 2: raise DSPFatal('The number of samples is not enough for a fit') if n_in > len(w_in): raise DSPFatal( 'The number of samples is more than the waveform length') s1 = 0.0 s2 = 0.0 s3 = 0.0 s4 = 0.0 s5 = 0.0 for i in range(0, n_in, 1): s1 += 1.0 s2 += np.exp(-1.0 * i / tau_in) s3 += np.exp(-2.0 * i / tau_in) s4 += np.exp(-1.0 * i / tau_in) * w_in[i] s5 += w_in[i] B = (s5 - s2 * (s4 * s1 - s2 * s5) / (s3 * s1 - s2 * s2)) / s1 A = (s4 - B * s2) / s3 for i in range(0, len(w_in), 1): w_out[i] = w_in[i] - (A * np.exp(-1.0 * i / tau_in) + B)
def t0_filter(rise, fall): """ Apply a modified, asymmetric trapezoidal filter to the waveform. Note that it is composed of a factory function that is called using the init_args argument and that the function the waveforms are passed to using args. Initialization Parameters ------------------------- rise: int The length of the rise section. This is the linearly increasing section of the filter that performs a weighted average. fall: int The length of the fall section. This is the simple averaging part of the filter. Parameters ---------- w_in : array-like The input waveform w_out: array-like The filtered waveform Processing Chain Example ------------------------ "wf_t0_filter": { "function": "t0_filter", "module": "pygama.dsp.processors", "args": ["wf_pz", "wf_t0_filter(3748,f)"], "unit": "ADC", "prereqs": ["wf_pz"], "init_args": ["128*ns", "2*us"] } """ if rise < 0: raise DSPFatal('The length of the rise section must be positive') if fall < 0: raise DSPFatal('The length of the fall section must be positive') t0_kern = np.arange(2 / float(rise), 0, -2 / (float(rise)**2)) t0_kern = np.append(t0_kern, np.zeros(int(fall)) - (1 / float(fall))) @guvectorize( ["void(float32[:], float32[:])", "void(float64[:], float64[:])"], "(n),(m)", forceobj=True) def t0_filter_out(w_in, w_out): w_out[:] = np.nan if np.isnan(w_in).any(): return if len(t0_kern) > len(w_in): raise DSPFatal('The filter is longer than the input waveform') w_out[:] = np.convolve(w_in, t0_kern)[:len(w_in)] return t0_filter_out
def moving_window_multi(w_in, length, num_mw, w_out): """ Apply a series of moving-average windows to the waveform, alternating its application between the left and the right. Parameters ---------- w_in : array-like The input waveform length: int The length of the moving window num_mw: int The number of moving windows w_out : array-like The windowed waveform Processing Chain Example ------------------------ "curr_av": { "function": "moving_window_multi", "module": "pygama.dsp.processors", "args": ["curr", "96*ns", "3", "curr_av"], "unit": "ADC/sample", "prereqs": ["curr"] } """ w_out[:] = np.nan if np.isnan(w_in).any(): return if np.floor(length) != length: raise DSPFatal('The length of the moving window must be an integer') if np.floor(num_mw) != num_mw: raise DSPFatal('The number of moving windows must be an integer') if int(length) < 0 or int(length) >= len(w_in): raise DSPFatal('The length of the moving window is out of range') if int(num_mw) < 0: raise DSPFatal('The number of moving windows much be positive') wf_buf = w_in.copy() for i in range(0, int(num_mw), 1): if i % 2 == 1: w_out[-1] = w_in[-1] for i in range(len(w_in) - 2, len(w_in) - int(length) - 1, -1): w_out[i] = w_out[i+1] + (w_in[i] - w_out[-1]) / length for i in range(len(wf_buf) - int(length) - 1, -1, -1): w_out[i] = w_out[i+1] + (wf_buf[i] - wf_buf[i+int(length)]) / length else: w_out[0] = wf_buf[0] / length for i in range(1, int(length), 1): w_out[i] = w_out[i-1] + wf_buf[i] / length for i in range(int(length), len(w_in), 1): w_out[i] = w_out[i-1] + (wf_buf[i] - wf_buf[i-int(length)]) / length wf_buf[:] = w_out[:]
def multi_a_filter(w_in, va_arbitrary_in, a_delta_in, va_max_out): """ Finds the maximums in a waveform and returns the amplitude of the wave at those points Parameters ---------- w_in : array-like The array of data within which amplitudes of extrema will be found va_arbitrary_in : array-like An array of fixed length that tells numba to return the list of amplitudes of the same length a_delta_in : scalar The absolute level by which data must vary (in one direction) about an extremum in order for it to be tagged Returns ------- va_max_out: array-like An array of the amplitudes of the maximums of the waveform """ # Initialize output parameters va_max_out[:] = np.nan # Check inputs if (np.isnan(w_in).any() or np.isnan(a_delta_in)): return if not a_delta_in >= 0: raise DSPFatal('Error Message: a_delta_in must be positive') if len(va_arbitrary_in) != len(va_max_out): raise DSPFatal( 'Error Message: Output arrays must be the same length as the arbitary input array' ) if (not len(va_arbitrary_in) < len(w_in)): raise DSPFatal( 'The length of your return array must be smaller than the length of your waveform' ) # Use get_multi_local_extrema to find vt_max_out for a waveform vt_max_out = np.full_like(va_arbitrary_in, np.nan, dtype=np.float32) vt_min_out = np.full_like(va_arbitrary_in, np.nan, dtype=np.float32) n_max_out = np.array([np.nan], dtype=np.float32) n_min_out = np.array([np.nan], dtype=np.float32) flag_out = np.array([np.nan], dtype=np.float32) get_multi_local_extrema(w_in, a_delta_in, va_arbitrary_in, vt_max_out, vt_min_out, n_max_out, n_min_out, flag_out) # Feed vt_max_out into fixed_time_pickoff to get va_max_out fixed_time_pickoff(w_in, vt_max_out, va_max_out)
def time_point_thresh(w_in, a_threshold, t_start, walk_forward, t_out): """ Find the index where the waveform value crosses the threshold, walking either forward or backward from the starting index. Parameters ---------- w_in : array-like The input waveform a_threshold : float The threshold value t_start : int The starting index walk_forward: int The backward (0) or forward (1) search direction t_out : float The index where the waveform value crosses the threshold Processing Chain Example ------------------------ "tp_0": { "function": "time_point_thresh", "module": "pygama.dsp.processors", "args": ["wf_atrap", "bl_std", "tp_start", 0, "tp_0"], "unit": "ns", "prereqs": ["wf_atrap", "bl_std", "tp_start"] } """ t_out[0] = np.nan if np.isnan(w_in).any() or np.isnan(a_threshold) or np.isnan(t_start) or np.isnan(walk_forward): return if np.floor(t_start) != t_start: raise DSPFatal('The starting index must be an integer') if np.floor(walk_forward) != walk_forward: raise DSPFatal('The search direction must be an integer') if int(t_start) < 0 or int(t_start) >= len(w_in): raise DSPFatal('The starting index is out of range') if int(walk_forward) == 1: for i in range(int(t_start), len(w_in) - 1, 1): if w_in[i] <= a_threshold < w_in[i+1]: t_out[0] = i return else: for i in range(int(t_start), 1, -1): if w_in[i-1] < a_threshold <= w_in[i]: t_out[0] = i return
def optimize_1pz(w_in, a_baseline_in, t_beg_in, t_end_in, p0_in, val0_out): """ Find the optimal, single pole-zero cancellation's parameter by minimizing the slope in the waveform's specified time range. Parameters ---------- w_in : array-like The input waveform a_baseline_in: float The resting baseline t_beg_in : int The lower bound's index for the time range over which to optimize the pole-zero cancellation t_end_in : int The upper bound's index for the time range over which to optimize the pole-zero cancellation p0_in : float The initial guess of the optimal time constant val0_out : float The output value of the best-fit time constant Processing Chain Example ------------------------ "tau0": { "function": "optimize_1pz", "module": "pygama.dsp.processors", "args": ["waveform", "baseline", "0", "20*us", "500*us", "tau0"], "prereqs": ["waveform", "baseline"], "unit": "us" } """ val0_out[0] = np.nan if np.isnan(w_in).any() or np.isnan(a_baseline_in) or np.isnan(t_beg_in) or np.isnan(t_end_in) or\ np.isnan(p0_in): return if not np.floor(t_beg_in) == t_beg_in or\ not np.floor(t_end_in) == t_end_in: raise DSPFatal('The waveform index is not an integer') if int(t_beg_in) < 0 or int(t_beg_in) > len(w_in) or\ int(t_end_in) < 0 or int(t_end_in) > len(w_in): raise DSPFatal('The waveform index is out of range') m = Minuit(Model(pole_zero, w_in, a_baseline_in, int(t_beg_in), int(t_end_in)), [p0_in]) m.print_level = -1 m.strategy = 1 m.errordef = Minuit.LEAST_SQUARES m.migrad() val0_out[0] = m.values[0]
def multi_a_filter(w_in, vt_maxs_in, va_max_out): """ Finds the maximums in a waveform and returns the amplitude of the wave at those points Parameters ---------- w_in : array-like The array of data within which amplitudes of extrema will be found vt_maxs_in : array-like The array of max positions for each wf Returns ------- va_max_out: array-like An array of the amplitudes of the maximums of the waveform """ # Initialize output parameters va_max_out[:] = np.nan # Check inputs if (np.isnan(w_in).any()): return if (not len(vt_maxs_in)<len(w_in)): raise DSPFatal('The length of your return array must be smaller than the length of your waveform') fixed_time_pickoff(w_in, vt_maxs_in, va_max_out)
def saturation(w_in, bit_depth_in, n_lo_out, n_hi_out): """ Count the number of samples in the waveform that are saturated at the minimum and maximum possible values based on the bit depth. Parameters ---------- w_in : array-like The input waveform bit_depth_in: int The bit depth of the analog-to-digital converter n_lo_out : int The output number of samples at the minimum n_hi_out : int The output number of samples at the maximum Processing Chain Example ------------------------ "sat_lo, sat_hi": { "function": "saturation", "module": "pygama.dsp.processors", "args": ["waveform", "16", "sat_lo", "sat_hi"], "unit": "ADC", "prereqs": ["waveform"] } """ n_lo_out[0] = np.nan n_hi_out[0] = np.nan if np.isnan(w_in).any() or np.isnan(bit_depth_in): return if not np.floor(bit_depth_in) == bit_depth_in: raise DSPFatal('The bit depth is not an integer') if bit_depth_in <= 0: raise DSPFatal('The bit depth is not positive') n_lo_out[0] = 0 n_hi_out[0] = 0 for i in range(0, len(w_in), 1): if w_in[i] == 0: n_lo_out[0] += 1 elif w_in[i] == np.power(2, int(bit_depth_in)): n_hi_out[0] += 1
def cusp_out(w_in, w_out): w_out[:] = np.nan if np.isnan(w_in).any(): return if len(cuspd) > len(w_in): raise DSPFatal('The filter is longer than the input waveform') w_out[:] = np.convolve(w_in, cuspd, 'valid')
def t0_filter_out(w_in, w_out): w_out[:] = np.nan if np.isnan(w_in).any(): return if len(t0_kern) > len(w_in): raise DSPFatal('The filter is longer than the input waveform') w_out[:] = np.convolve(w_in, t0_kern)[:len(w_in)]
def windower(w_in, t0_in, w_out): """ Return a shorter sample of the waveform, starting at the specified index. Note that the length of the output waveform is determined by the length of "w_out" rather than an input parameter. If the the length of "w_out" plus "t0_in" extends past the end of "w_in" or if "t0_in" is negative, remaining values are padded with NaN. Parameters ---------- w_in : array-like The input waveform t0_in: int The starting index of the window w_out: array-like The windowed waveform """ w_out[:] = np.nan if np.isnan(w_in).any() or np.isnan(t0_in): return if np.floor(t0_in) != t0_in: raise DSPFatal('The starting index must be an integer') if len(w_out) >= len(w_in): raise DSPFatal( 'The windowed waveform must be smaller than the input waveform') beg = min(int(t0_in), len(w_in)) end = max(beg + len(w_out), 0) if beg < 0: w_out[:len(w_out) - end] = np.nan w_out[len(w_out) - end:] = w_in[:end] elif end < len(w_in): w_out[:] = w_in[beg:end] else: w_out[:len(w_in) - beg] = w_in[beg:len(w_in)] w_out[len(w_in) - beg:] = np.nan
def moving_window_left(w_in, length, w_out): """ Apply a moving-average window to the waveform from the left. Parameters ---------- w_in : array-like The input waveform length: int The length of the moving window w_out : array-like The windowed waveform Processing Chain Example ------------------------ "wf_mw": { "function": "moving_window_left", "module": "pygama.dsp.processors", "args": ["wf_pz", "96*ns", "wf_mw"], "unit": "ADC", "prereqs": ["wf_pz"] } """ w_out[:] = np.nan if np.isnan(w_in).any(): return if np.floor(length) != length: raise DSPFatal('The length of the moving window must be an integer') if int(length) < 0 or int(length) >= len(w_in): raise DSPFatal('The length of the moving window is out of range') w_out[0] = w_in[0] / length for i in range(1, int(length)): w_out[i] = w_out[i-1] + w_in[i] / length for i in range(int(length), len(w_in)): w_out[i] = w_out[i-1] + (w_in[i] - w_in[i-int(length)]) / length
def the_processor_template(w_in, t_in, a_in, w_out, t_out): # 4) Document the algorithm # """ Add here a complete description of what the processor does, including the meaning of input and output variables """ # 5) Initialize output parameters # # Notes: # - All output parameters should be initializes to a NaN. If a processor # fails, its output parameters should have the default NaN value # - Use np.nan for both variables and arrays # w_out[:] = np.nan # use [:] for arrays t_out[0] = np.nan # use [0] for scalar parameters # 6) Check inputs # # There are two kinds of checks: # - NaN checks. A processor might depend on others, i.e., its input # parameters are the output parameters of an other processors. When a # processor fails, all processors depending on its output should not run. # Thus, skip this processor if a NaN value is detected and return NaN # output parameters to propagate the failure throughout the processing chain. # - In-range checks. Check if indexes are within 0 and len(waveform), # amplitudes are positive, etc. A failure of this check implies errors in # the DSP JSON config file. Abort the analysis immediately. # if np.isnan(w_in).any() or np.isnan(t_in) or np.isnan(a_in): return if a_in < 0: raise DSPFatal('The error message goes here') # 7) Algorithm # # Loop over waveforms by using a "for i in range(.., .., ..)" instruction. # Avoid loops based on a while condition which might lead to segfault. Avoid # also enumerate/ndenumerate to keep code as similar as possible among all # processors. # # Example of an algorithm to find the first index above "t_in" in which the # signal crossed the value "a_in" # for i in range(t_in, 1, 1): if w_in[i] >= a_in and w_in[i - 1] < a_in: t_out[0] = i return
def avg_current(w_in, length, w_out): """ Calculate the derivative of the waveform, averaged across the specified number of samples. Parameters ---------- w_in : array-like The input waveform length: int The length of the moving window w_out : array-like The output derivative Processing Chain Example ------------------------ "curr": { "function": "avg_current", "module": "pygama.dsp.processors", "args": ["wf_pz", 1, "curr(len(wf_pz)-1, f)"], "unit": "ADC/sample", "prereqs": ["wf_pz"] } """ w_out[:] = np.nan if np.isnan(w_in).any(): return if np.floor(length) != length: raise DSPFatal('The length of the moving window must be an integer') if int(length) < 0 or int(length) >= len(w_in): raise DSPFatal('The length of the moving window is out of range') w_out[:] = w_in[int(length):] - w_in[:-int(length)] w_out /= length
def fixed_time_pickoff(w_in, t_in, a_out): """ Pick off the waveform value at the provided index. If the provided index is out of range, return NaN. Parameters ---------- w_in : array-like The input waveform t_in : int The waveform index to pick off a_out: float The output pick-off value Processing Chain Example ------------------------ "trapEftp": { "function": "fixed_time_pickoff", "module": "pygama.dsp.processors", "args": ["wf_trap", "tp_0+10*us", "trapEftp"], "unit": "ADC", "prereqs": ["wf_trap", "tp_0"] } """ a_out[0] = np.nan if np.isnan(w_in).any() or np.isnan(t_in): return if np.floor(t_in) != t_in: raise DSPFatal('The pick-off index must be an integer') if int(t_in) < 0 or int(t_in) >= len(w_in): return a_out[0] = w_in[int(t_in)]
def trap_pickoff(w_in, rise, flat, t_pickoff, a_out): """ Pick off the value at the provided index of a symmetric trapezoidal filter to the input waveform, normalized by the number of samples averaged in the rise and fall sections. Parameters ---------- w_in : array-like The input waveform rise : int The number of samples averaged in the rise and fall sections flat : int The delay between the rise and fall sections t_pickoff: int The waveform index to pick off a_out : float The output pick-off value of the filtered waveform Processing Chain Example ------------------------ "ct_corr": { "function": "trap_pickoff", "module": "pygama.dsp.processors", "args": ["wf_pz", "1.5*us", 0, "tp_0", "ct_corr"], "unit": "ADC", "prereqs": ["wf_pz", "tp_0"] } """ a_out[0] = np.nan if np.isnan(w_in).any() or np.isnan(rise) or np.isnan(flat) or np.isnan( t_pickoff): return if np.floor(rise) != rise: raise DSPFatal( 'The number of samples in the rise section must be an integer') if np.floor(flat) != flat: raise DSPFatal( 'The number of samples in the flat section must be an integer') if np.floor(t_pickoff) != t_pickoff: raise DSPFatal('The pick-off index must be an integer') if int(rise) < 0: raise DSPFatal( 'The number of samples in the rise section must be positive') if int(flat) < 0: raise DSPFatal( 'The number of samples in the flat section must be positive') if 2 * int(rise) + int(flat) > len(w_in): raise DSPFatal('The trapezoid width is wider than the waveform') I_1 = 0. I_2 = 0. rise_int = int(rise) flat_int = int(flat) start_time = int(t_pickoff + 1) for i in range(start_time - rise_int, start_time, 1): I_1 += w_in[i] for i in range(start_time - 2 * rise_int - flat_int, start_time - rise_int - flat_int, 1): I_2 += w_in[i] a_out[0] = (I_1 - I_2) / rise
def cusp_filter(length, sigma, flat, decay): """ Apply a CUSP filter to the waveform. Note that it is composed of a factory function that is called using the init_args argument and that the function the waveforms are passed to using args. Initialization Parameters ------------------------- length: int The length of the filter to be convolved sigma : float The curvature of the rising and falling part of the kernel flat : int The length of the flat section decay : int The decay constant of the exponential to be convolved Parameters ---------- w_in : array-like The input waveform w_out: array-like The filtered waveform Processing Chain Example ------------------------ "wf_cusp": { "function": "cusp_filter", "module": "pygama.dsp.processors", "args": ["wf_bl", "wf_cusp(101,f)"], "unit": "ADC", "prereqs": ["wf_bl"], "init_args": ["len(wf_bl)-100", "40*us", "3*us", "45*us"] } """ if length <= 0: raise DSPFatal('The length of the filter must be positive') if sigma < 0: raise DSPFatal('The curvature parameter must be positive') if flat < 0: raise DSPFatal('The length of the flat section must be positive') if decay < 0: raise DSPFatal('The decay constant must be positive') lt = int((length - flat) / 2) flat_int = int(flat) cusp = np.zeros(length) for ind in range(0, lt, 1): cusp[ind] = float(np.sinh(ind / sigma) / np.sinh(lt / sigma)) for ind in range(lt, lt + flat_int + 1, 1): cusp[ind] = 1 for ind in range(lt + flat_int + 1, length, 1): cusp[ind] = float( np.sinh((length - ind) / sigma) / np.sinh(lt / sigma)) den = [1, -np.exp(-1 / decay)] cuspd = np.convolve(cusp, den, 'same') @guvectorize( ["void(float32[:], float32[:])", "void(float64[:], float64[:])"], "(n),(m)", forceobj=True) def cusp_out(w_in, w_out): w_out[:] = np.nan if np.isnan(w_in).any(): return if len(cuspd) > len(w_in): raise DSPFatal('The filter is longer than the input waveform') w_out[:] = np.convolve(w_in, cuspd, 'valid') return cusp_out
def zac_filter(length, sigma, flat, decay): """ Apply a ZAC (Zero Area CUSP) filter to the waveform. Note that it is composed of a factory function that is called using the init_args argument and that the function the waveforms are passed to using args. Initialization Parameters ------------------------- length: int The length of the filter to be convolved sigma : float The curvature of the rising and falling part of the kernel flat : int The length of the flat section decay : int The decay constant of the exponential to be convolved Parameters ---------- w_in : array-like The input waveform w_out: array-like The filtered waveform Processing Chain Example ------------------------ "wf_zac": { "function": "zac_filter", "module": "pygama.dsp.processors", "args": ["wf_bl", "wf_zac(101,f)"], "unit": "ADC", "prereqs": ["wf_bl"], "init_args": ["len(wf_bl)-100", "40*us", "3*us", "45*us"], } """ if length <= 0: raise DSPFatal('The length of the filter must be positive') if sigma < 0: raise DSPFatal('The curvature parameter must be positive') if flat < 0: raise DSPFatal('The length of the flat section must be positive') if decay < 0: raise DSPFatal('The decay constant must be positive') lt = int((length - flat) / 2) flat_int = int(flat) # calculate cusp filter and negative parables cusp = np.zeros(length) par = np.zeros(length) for ind in range(0, lt, 1): cusp[ind] = float(np.sinh(ind / sigma) / np.sinh(lt / sigma)) par[ind] = np.power(ind - lt / 2, 2) - np.power(lt / 2, 2) for ind in range(lt, lt + flat_int + 1, 1): cusp[ind] = 1 for ind in range(lt + flat_int + 1, length, 1): cusp[ind] = float( np.sinh((length - ind) / sigma) / np.sinh(lt / sigma)) par[ind] = np.power(length - ind - lt / 2, 2) - np.power(lt / 2, 2) # calculate area of cusp and parables areapar, areacusp = 0, 0 for i in range(0, length, 1): areapar += par[i] areacusp += cusp[i] # normalize parables area par = -par / areapar * areacusp # create zac filter zac = cusp + par # deconvolve zac filter den = [1, -np.exp(-1 / decay)] zacd = np.convolve(zac, den, 'same') @guvectorize( ["void(float32[:], float32[:])", "void(float64[:], float64[:])"], "(n),(m)", forceobj=True) def zac_out(w_in, w_out): w_out[:] = np.nan if np.isnan(w_in).any(): return if len(zacd) > len(w_in): raise DSPFatal('The filter is longer than the input waveform') w_out[:] = np.convolve(w_in, zacd, 'valid') return zac_out
def get_multi_local_extrema(w_in, a_delta_in, vt_max_out, vt_min_out, n_max_out, n_min_out, flag_out): """ Get lists of indices of the local maxima and minima of data The "local" extrema are those maxima / minima that have heights / depths of at least a_delta_in. Converted from MATLAB script at: http://billauer.co.il/peakdet.html Parameters ---------- w_in : array-like The array of data within which extrema will be found a_delta_in : scalar The absolute level by which data must vary (in one direction) about an extremum in order for it to be tagged Returns ------- vt_max_out, vt_min_out : array-like, array-like Arrays of fixed length (padded with nans) that hold the indices of the identified local maxima and minima n_max_out, n_min_out: scalar, scalar The number of maxima and minima found in a waveform flag_out: scalar Returns 1 if there is only one maximum and it is a simple waveform, Returns 0 if there are no peaks, or multiple peaks in a waveform """ # prepare output vt_max_out[:]= np.nan vt_min_out[:] = np.nan n_max_out[0] = np.nan n_min_out[0] = np.nan flag_out[0] = np.nan # initialize internal counters n_max_counter = 0 n_min_counter = 0 # Checks if (np.isnan(w_in).any() or np.isnan(a_delta_in)): return if (not len(vt_max_out)<len(w_in) or not len(vt_min_out)<len(w_in)): raise DSPFatal('The length of your return array must be smaller than the length of your waveform') if (not a_delta_in >= 0): raise DSPFatal('a_delta_in must be positive') # now loop over data imax, imin = 0, 0 find_max = True for i in range(len(w_in)): if w_in[i] > w_in[imax]: imax = i if w_in[i] < w_in[imin]: imin = i if find_max: # if the sample is less than the current max by more than a_delta_in, # declare the previous one a maximum, then set this as the new "min" if w_in[i] < w_in[imax] - a_delta_in and int(n_max_counter) < int(len(vt_max_out)): vt_max_out[int(n_max_counter)]=imax n_max_counter += 1 imin = i find_max = False else: # if the sample is more than the current min by more than a_delta_in, # declare the previous one a minimum, then set this as the new "max" if w_in[i] > w_in[imin] + a_delta_in and int(n_min_counter) < int(len(vt_min_out)): vt_min_out[int(n_min_counter)] = imin n_min_counter += 1 imax = i find_max = True # set output n_max_out[0] = n_max_counter n_min_out[0] = n_min_counter if n_max_out[0] == 1: flag_out[0] = 1 else: flag_out[0] = 0
def trap_norm(w_in, rise, flat, w_out): """ Apply a symmetric trapezoidal filter to the waveform, normalized by the number of samples averaged in the rise and fall sections. Parameters ---------- w_in : array-like The input waveform rise : int The number of samples averaged in the rise and fall sections flat : int The delay between the rise and fall sections w_out: array-like The normalized, filtered waveform Processing Chain Example ------------------------ "wf_tf": { "function": "trap_norm", "module": "pygama.dsp.processors", "args": ["wf_pz", "10*us", "3*us", "wf_tf"], "unit": "ADC", "prereqs": ["wf_pz"] } """ w_out[:] = np.nan if np.isnan(w_in).any() or np.isnan(rise) or np.isnan(flat): return if np.floor(rise) != rise: raise DSPFatal( 'The number of samples in the rise section must be an integer') if np.floor(flat) != flat: raise DSPFatal( 'The number of samples in the flat section must be an integer') if int(rise) < 0: raise DSPFatal( 'The number of samples in the rise section must be positive') if int(flat) < 0: raise DSPFatal( 'The number of samples in the flat section must be positive') if 2 * int(rise) + int(flat) > len(w_in): raise DSPFatal('The trapezoid width is wider than the waveform') rise_int = int(rise) flat_int = int(flat) w_out[0] = w_in[0] / rise for i in range(1, rise_int, 1): w_out[i] = w_out[i - 1] + w_in[i] / rise for i in range(rise_int, rise_int + flat_int, 1): w_out[i] = w_out[i - 1] + (w_in[i] - w_in[i - rise_int]) / rise for i in range(rise_int + flat_int, 2 * rise_int + flat_int, 1): w_out[i] = w_out[i - 1] + (w_in[i] - w_in[i - rise_int] - w_in[i - rise_int - flat_int]) / rise for i in range(2 * rise_int + flat_int, len(w_in), 1): w_out[i] = w_out[i - 1] + (w_in[i] - w_in[i - rise_int] - w_in[i - rise_int - flat_int] + w_in[i - 2 * rise_int - flat_int]) / rise