def shaper(shape, order, peaktime, dt=1E-9, pz=0.0, normalize=True, return_zpk=False, bipolar=False): if shape == 'gaussian': f = gaussian_shaper elif shape == 'crrc': f = crrc_shaper else: raise ValueError('specified shaper shape not supported') dzpk, azpk = f(order, peaktime, dt=dt, pz=pz) zeros, poles, gain = dzpk if bipolar: zeros = np.append(zeros, [1]) if normalize: sos = signal.zpk2sos(zeros, poles, 1) t = np.arange(-1 * peaktime, 2 * peaktime, dt) tail = np.zeros_like(t) tail[t > 0] = np.exp(-t[t > 0] * pz) ytail = signal.sosfilt(sos, tail) gain = 1. / np.max(ytail) sos = signal.zpk2sos(zeros, poles, gain) dzpk = zeros, poles, gain else: sos = signal.zpk2sos(*dzpk) if return_zpk: return sos, dzpk, azpk else: return sos
def bandpass(self, data): # raise for some bad scenarios if self.high - 1.0 > -1e-6: msg = ("Selected high corner frequency ({}) of bandpass is at or " "above Nyquist ({}). Applying a high-pass instead.").format( self.freqmax, self.fe) logger.warning(msg) #warnings.warn(msg) return highpass(data, freq=self.freqmin, df=self.sampling_rate, corners=self.corners, zerophase=self.zerophase) if self.low > 1: msg = "Selected low corner frequency is above Nyquist." raise ValueError(msg) if self.zi is None: z, p, k = iirfilter(self.corners, [self.low, self.high], btype='bandpass', ftype='butter', output='zpk') self.sos = zpk2sos(z, p, k) self.zi = sosfilt_zi(self.sos) data, self.zi = sosfilt(self.sos, data, zi=self.zi) return data
def _butter_bandpass(self, lowcut, highcut, fs, order): nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq z, p, k = butter(order, [low, high], btype='bandpass', output='zpk') sos = zpk2sos(z, p, k) return sos
def cheby_bandpass(data, cutoff, fs, Rs = 100): """ cheby_bandpass(data, flow, fhigh, fs, , Rs = 100) Parameters ---------- data : 1d ndarray, signal cutoff : List [lowcut = Float, low bound frequency limit, highcut = Float, upper bound frequency limit] fs : Int, sampling rate Rs: Int, stopband attenuation in Db, Optional, Default value = 100. Returns ------- y : 1d ndarray, filtered signal """ flow = cutoff[0]; fhigh = cutoff[1] Fn = fs/2 # Nyquist Frequency (Hz) Wp = [flow/Fn, fhigh/Fn] # Passband Frequency (Normalised) Ws = [(flow-1)/Fn, (fhigh+1)/Fn] # Stopband Frequency (Normalised) Rp = 1 # Passband Ripple (dB) Rs = 100 # Get Filter Order n, Ws = cheb2ord(Wp,Ws,Rp,Rs); # Design Filter z,p,k = cheby2(n,Rs,Ws,btype = 'bandpass', analog=False, output='zpk') # Convert to second order sections sos = zpk2sos(z,p,k); # filter data y = sosfiltfilt(sos,data) return y
def generate_noise(fs=1024, f1=15, f2=100, amplify=1): bg_raw = np.random.randn(1000) # f1 and f2 is a bandpass sosBP1 = sig.butter(8, [f1 / (fs / 2), f2 / (fs / 2)], btype='bandpass', output='sos') # invertable band pass fz = [4, 400] fp = [15, 20, 150] _, z0, _ = sig.butter(10, fz[0] / (fs / 2), btype='lowpass', output='zpk') _, p0, _ = sig.butter(8, fp[0] / (fs / 2), btype='lowpass', output='zpk') _, p1, _ = sig.butter(1, fp[1] / (fs / 2), btype='lowpass', output='zpk') _, p2, _ = sig.butter(3, fp[1] / (fs / 2), btype='lowpass', output='zpk') _, z1, _ = sig.butter(2, fz[1] / (fs / 2), btype='lowpass', output='zpk') zs = np.concatenate((z0, z1)) ps = np.concatenate((p0, p1)) BP = sig.zpk2sos(zs, ps, 1, pairing='keep_odd') sosBP = np.concatenate([sosBP1, BP], axis=0) # filter the data using many second-order-sections bg = sig.sosfilt(sosBP, bg_raw) * amplify return bg
def butter_highpass(data, cutoff, fs, order = 5): """ butter_highpass(data, cutoff, fs, order = 5) Parameters ---------- data : 1d ndarray, signal cutoff : Float, cutoff frequency fs : Int, sampling rate order : Int, filter order. The default is 5. Returns ------- y : 1d ndarray, filtered signal """ nyq = 0.5 * fs # Nyquist Frequency (Hz) normal_cutoff = cutoff[0] / nyq # Low-bound Frequency (Normalised) # Design filter z,p,k = butter(order, normal_cutoff, btype='high', analog=False, output ='zpk') sos = zpk2sos(z,p,k) # Convert to second order sections y = sosfiltfilt(sos, data) # Filter data return y
def add_antialias(self,Fs,freq,maxorder=8): # From obspy nyquist = Fs * 0.5 # rp - maximum ripple of passband, rs - attenuation of stopband rp, rs, order = 1, 96, 1e99 ws = freq / nyquist # stop band frequency wp = ws # pass band frequency # raise for some bad scenarios if ws > 1: ws = 1.0 msg = "** Selected corner frequency is above Nyquist. " + \ "** Setting Nyquist as high corner." warnings.warn(msg) while True: if order <= maxorder: break wp = wp * 0.99 order, wn = cheb2ord(wp, ws, rp, rs, analog=0) z, p, k = cheby2(order, rs, wn, btype='low', analog=0, output='zpk') self.anti_alias = zpk2sos(z,p,k) return()
def lowpass(freq, df, corners=4): """ Butterworth-Lowpass Filter. Filter data removing data over certain frequency ``freq`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). :type data: numpy.ndarray :param data: Data to filter. :param freq: Filter corner frequency. :param df: Sampling rate in Hz. :param corners: Filter corners / order. :param zerophase: If True, apply filter once forwards and once backwards. This results in twice the number of corners but zero phase shift in the resulting filtered trace. :return: Filtered data. """ fe = 0.5 * df f = freq / fe # raise for some bad scenarios if f > 1: f = 1.0 msg = "Selected corner frequency is above Nyquist. " + \ "Setting Nyquist as high corner." warnings.warn(msg) z, p, k = iirfilter(corners, f, btype='lowpass', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) return sos
def bandpass_in_time_domain_sos(data, freqmin=0.01, freqmax=1.0, sample_rate=0.5, order=2, axis=-1, taper=None, zerophase=True, **kwargs): fe = 0.5 * sample_rate low = freqmin / fe high = freqmax / fe # raise for some bad scenarios if high - 1.0 > -1e-6: print("freqmax ({}) of bandpass is at or above Nyquist ({}). Ignoring." ).format(freqmax, fe) return data if low > 1: print( "Selected freqmin ({}) is above Nyquist. Ignoring".format(freqmin)) return data z, p, k = iirfilter(order, [low, high], btype='band', ftype='butter', output='zpk') #,fs=sample_rate) sos = zpk2sos(z, p, k) if zerophase: result = sosfilt(sos, data) return np.flip(sosfilt(sos, np.flip(result, axis=axis)), axis=axis) else: return sosfilt(sos, data, axis=axis)
def low_pass_filter(source, sr, cutoff_freq, order): """Function that takes an 1D array as an argument and returns the filtered signal following the application of a low pass filter with a cutoff frequency = cutoff_freq and order = order Args : source (np.array) = The signal to be filtered sr (int) = The sampling rate of the signal cutoff_freq (int) = the cutoff frequency of the lowpass filter order (int) = the order of the lowpass filter Returns : sink (np.array) = The centered moving average of the input signal """ for name, arg in zip(("sr", "cutoff_freq", "order"), (sr, cutoff_freq, order)): if not isinstance(arg, int): raise RuntimeError("The argument {0} is of incorrect type. Input : {1}, Requiered : int" .format(name, type(arg))) source = np.array(source) if len(source.shape) == 1: nyq = sr / 2 # The nyquist frequency normalized_cutoff = cutoff_freq / nyq z, p, k = signal.butter(order, normalized_cutoff, output="zpk") lesos = signal.zpk2sos(z, p, k) filtered = signal.sosfilt(lesos, source) sink = np.array(filtered) return sink else: raise RuntimeError("The input array has too many dimensions. Input : {0}D, Requiered : 1D" .format(len(source.shape)))
def bandstop(data, freqmin, freqmax, df, corners=4, zerophase=False): """ Butterworth-Bandstop Filter. Filter data removing data between frequencies ``freqmin`` and ``freqmax`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). Parameters ---------- data : array Data to filter. freqmin : float Stop band low corner frequency. freqmax : float Stop band high corner frequency. df : float Sampling rate in Hz. corners : int Filter corners / order. **Default:** ``4`` zerophase : bool If True, apply filter once forwards and once backwards. This results in twice the filter order but zero phase shift in the resulting filtered trace. **Default:** ``False`` Returns ------- data : array Filtered data. """ fe = 0.5 * df low = freqmin / fe high = freqmax / fe # raise for some bad scenarios if high > 1: high = 1.0 msg = "Selected high corner frequency is above Nyquist. " + \ "Setting Nyquist as high corner." warnings.warn(msg) if low > 1: msg = "Selected low corner frequency is above Nyquist." raise ValueError(msg) z, p, k = iirfilter(corners, [low, high], btype='bandstop', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) if zerophase: firstpass = sosfilt(sos, data) return sosfilt(sos, firstpass[::-1])[::-1] else: return sosfilt(sos, data)
def butter_bandpass(data, cutoff, fs, order = 10): """ butter_bandpass(data, cutoff, fs, order = 10) Parameters ---------- data : 1d ndarray, signal cutoff : List [lowcut = Float, low bound frequency limit, highcut = Float, upper bound frequency limit] fs : Int, sampling rate order : Int, filter order. The default is 5. Returns ------- y : 1d ndarray, filtered signal """ nyq = 0.5 * fs # Nyquist Frequency (Hz) low = cutoff[0] / nyq # Low-bound Frequency (Normalised) high = cutoff[1] / nyq # Upper-bound Frequency (Normalised) # Design filter z,p,k = butter(order, [low, high], btype='band', analog=False, output ='zpk') # Convert to second order sections sos = zpk2sos(z,p,k) # Filter data y = sosfiltfilt(sos, data) return y
def highpass(data, freq, df, corners=4, zerophase=False): """ Butterworth-Highpass Filter. Filter data removing data below certain frequency ``freq`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). :type data: numpy.ndarray :param data: Data to filter. :param freq: Filter corner frequency. :param df: Sampling rate in Hz. :param corners: Filter corners / order. :param zerophase: If True, apply filter once forwards and once backwards. This results in twice the number of corners but zero phase shift in the resulting filtered trace. :return: Filtered data. """ fe = 0.5 * df f = freq / fe # raise for some bad scenarios if f > 1: msg = "Selected corner frequency is above Nyquist." raise ValueError(msg) z, p, k = iirfilter(corners, f, btype='highpass', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) if zerophase: firstpass = sosfilt(sos, data) return sosfilt(sos, firstpass[::-1])[::-1] else: return sosfilt(sos, data)
def _design_iir(wp, ws, sample_rate, gpass, gstop, analog=False, ftype='cheby1', output='zpk'): nyq = sample_rate / 2. wp = atleast_1d(wp) ws = atleast_1d(ws) if analog: wp *= TWO_PI ws *= TWO_PI else: wp /= nyq ws /= nyq z, p, k = signal.iirdesign(wp, ws, gpass, gstop, analog=analog, ftype=ftype, output='zpk') if analog: z /= -TWO_PI p /= -TWO_PI if output == 'zpk': return z, p, k elif output == 'ba': return signal.zpk2tf(z, p, k) elif output == 'sos': return signal.zpk2sos(z, p, k) else: raise ValueError("'%s' is not a valid output form." % output)
def bandpass(data, freq_min, freq_max, sample_frequency, corners=4): """ Zero-Phase Butterworth-Bandpass Filter. Filter data from ``freqmin`` to ``freqmax`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). :type data: numpy.ndarray :param data: Data to filter. :param freqmin: Pass band low corner frequency. :param freqmax: Pass band high corner frequency. :param df: Sampling rate in Hz. :param corners: Filter corners / order. :return: Filtered data. """ nyquist = 0.5 * sample_frequency low = freq_min / nyquist high = freq_max / nyquist zeroes, poles, gain = iirfilter(corners, [low, high], btype='band', ftype='butter', output='zpk') sos = zpk2sos(zeroes, poles, gain) firstpass = sosfilt(sos, data) return sosfilt(sos, firstpass[::-1])[::-1]
def band_stop(ite_data, freqmin, freqmax, samp_freq, corners=4, zerophase=True): fe = samp_freq / 2.0 low = freqmin / fe high = freqmax / fe # raise error for illegal input if high - 1.0 > -1e-6: msg = ( "Selected high corner frequency ({}) of bandpass is at or above Nyquist ({}). Applying a high-pass instead." ).format(freqmax, fe) return False if low > 1: msg = "selected low corner requency is above Nyquist" return False z, p, k = signal.iirfilter(corners, [low, high], btype='bandstop', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) ite_data = sosfilt(sos, ite_data) if zerophase: ite_data = sosfilt(sos, ite_data[::-1])[::-1] return ite_data
def stream_filter_bessel(stream, center_freq, gamma=0.5, corners=4, zerophase=False): filtered_stream = obspy.Stream() for tr in stream.copy(): fe = 0.5 * tr.stats.sampling_rate freqmin = center_freq - (gamma * center_freq) freqmax = center_freq + (gamma * center_freq) low = freqmin / fe high = freqmax / fe z, p, k = signal.iirfilter(corners, [low, high], btype='bandpass', ftype='bessel', output='zpk') sos = signal.zpk2sos(z, p, k) if zerophase: first_pass = signal.sosfilt(sos, tr.data) second_pass = signal.sosfilt(sos, first_pass[::-1])[::-1] tr.data = second_pass filtered_stream += tr else: filtered_data = signal.sosfilt(sos, tr.data) tr.data = filtered_data filtered_stream += tr return filtered_stream
def _design_iir(wp, ws, sample_rate, gpass, gstop, analog=False, ftype='cheby1', output='zpk'): # pylint: disable=invalid-name nyq = sample_rate / 2. wp = numpy.atleast_1d(wp) ws = numpy.atleast_1d(ws) if analog: # convert Hz to rad/s wp *= TWO_PI ws *= TWO_PI else: # convert Hz to half-cycles / sample wp /= nyq ws /= nyq z, p, k = signal.iirdesign(wp, ws, gpass, gstop, analog=analog, ftype=ftype, output='zpk') if analog: # convert back to Hz z /= -TWO_PI p /= -TWO_PI k *= TWO_PI ** z.size / -TWO_PI ** p.size if output == 'zpk': return z, p, k elif output == 'ba': return signal.zpk2tf(z, p, k) elif output == 'sos': return signal.zpk2sos(z, p, k) else: raise ValueError("'%s' is not a valid output form." % output)
def bandpass(data, fs, fl, fh, order=None, axis=-1): """ Apply Butterworth bandpass filter using scipy.signal.sosfiltfilt method Parameters ---------- data: array fs: sampling frequency fl, fh: low and high frequency for bandpass axis: axis to apply the filter on Returns: -------- data_filt: filtered array """ if order is None: order = 8 # Make filter nyq = fs/2. low, high = fl/nyq, fh/nyq # normalize frequency z, p, k = sig.butter(order, [low, high], btype='bandpass', output='zpk') sos = sig.zpk2sos(z, p, k) # Apply filter and return output data_filt = sig.sosfiltfilt(sos, data, axis=axis) return data_filt
def highpass(rawData, samplFreq, highpassCutOff): #This function high passes the data with sampling frequency samplFreq with a highpass # cut off of highpassCutOff # # Usage: [highPassedData] = highpass(rawData, samplFreq, highpassCutOff) # # rawData : raw time series Data # samplFreq: sampling Frequency of Data # highpassCutOff: The high pass frequency cut off. # numberOfChannels = len(rawData) dataLength = len(rawData[0]) duration = dataLength / samplFreq halfDataLength = dataLength / 2 + 1 for channelNumber in xrange(numberOfChannels): if (len(rawData[channelNumber]) != dataLength): sys.exit('Data length not consistent\n') nyquistFrequency = samplFreq / 2.0 lpefOrder = 0 if (highpassCutOff > 0): hpfOrder = 12 hpfZeros, hpfPoles, hpfGain = sig.butter(hpfOrder, highpassCutOff / nyquistFrequency, btype='highpass', output='zpk') hpfSOS = sig.zpk2sos(hpfZeros, hpfPoles, hpfGain) #magnitude response of high pass filter minimumFrequencyStep = 1.0 / duration frequencies = np.arange(0, nyquistFrequency, minimumFrequencyStep) hpfArgument = np.power((frequencies / highpassCutOff), 2 * hpfOrder) hpfResponse = hpfArgument / (1 + hpfArgument) highPassCutOffIndex = np.ceil(highpassCutOff / minimumFrequencyStep) highPassedData = [] for channelNumber in xrange(numberOfChannels): if (highpassCutOff > 0): x = sig.sosfilt(hpfSOS, rawData[channelNumber]) x = np.flipud(x) x = sig.sosfilt(hpfSOS, x) x = np.flipud(x) else: x = rawData[channelNumber] x[0:lpefOrder] = np.zeros(lpefOrder) x[dataLength - lpefOrder:dataLength - 1] = np.zeros(lpefOrder) highPassedData.append(x) highPassedData = np.asarray(highPassedData) return highPassedData
def chebyBandpassFilter(data, cutoff, gstop=40, gpass=1, fs=2048.): """ Design a filter with scipy functions avoiding unstable results (when using ab output and filtfilt(), lfilter()...). Cf. ()[] Parameters ---------- data : instance of numpy.array | instance of pandas.core.DataFrame Data to be filtered. Each column will be filtered if data is a dataframe. cutoff : array-like of float Pass and stop frequencies in order: - the first element is the stop limit in the lower bound - the second element is the lower bound of the pass-band - the third element is the upper bound of the pass-band - the fourth element is the stop limit in the upper bound For instance, [0.9, 1, 45, 48] will create a band-pass filter between 1 Hz and 45 Hz. gstop : int The minimum attenuation in the stopband (dB). gpass : int The maximum loss in the passband (dB). Returns: zpk : filteredData : instance of numpy.array | instance of pandas.core.DataFrame The filtered data. """ wp = [cutoff[1] / (fs / 2), cutoff[2] / (fs / 2)] ws = [cutoff[0] / (fs / 2), cutoff[3] / (fs / 2)] z, p, k = iirdesign(wp=wp, ws=ws, gstop=gstop, gpass=gpass, ftype='cheby2', output='zpk') zpk = [z, p, k] sos = zpk2sos(z, p, k) order, Wn = cheb2ord(wp=wp, ws=ws, gstop=gstop, gpass=gpass, analog=False) print 'Creating cheby filter of order %d...' % order if (data.ndim == 2): print 'Data contain multiple columns. Apply filter on each columns.' filteredData = np.zeros(data.shape) for electrode in range(data.shape[1]): # print 'Filtering electrode %s...' % electrode filteredData[:, electrode] = sosfiltfilt(sos, data[:, electrode]) else: # Use sosfiltfilt instead of filtfilt fixed the artifacts at the beggining # of the signal filteredData = sosfiltfilt(sos, data) return zpk, filteredData
def test_filter(self, losc): zpk = [], [], 1 fts = losc.filter(zpk, analog=True) utils.assert_quantity_sub_equal(losc, fts) # check SOS filters can be used directly zpk = filter_design.highpass(50, sample_rate=losc.sample_rate) sos = signal.zpk2sos(*zpk) utils.assert_quantity_almost_equal(losc.filter(zpk), losc.filter(sos))
def highpass(rawData, samplFreq, highpassCutOff): #This function high passes the data with sampling frequency samplFreq with a highpass # cut off of highpassCutOff # # Usage: [highPassedData] = highpass(rawData, samplFreq, highpassCutOff) # # rawData : raw time series Data # samplFreq: sampling Frequency of Data # highpassCutOff: The high pass frequency cut off. # numberOfChannels = len(rawData) dataLength = len(rawData[0]) duration = dataLength/samplFreq halfDataLength = dataLength/2 + 1 for channelNumber in xrange(numberOfChannels): if(len(rawData[channelNumber]) != dataLength): sys.exit('Data length not consistent\n') nyquistFrequency = samplFreq/2.0 lpefOrder = 0 if(highpassCutOff>0): hpfOrder = 12 hpfZeros, hpfPoles, hpfGain = sig.butter(hpfOrder, highpassCutOff/nyquistFrequency, btype = 'highpass', output = 'zpk' ) hpfSOS = sig.zpk2sos(hpfZeros, hpfPoles, hpfGain) #magnitude response of high pass filter minimumFrequencyStep = 1.0/duration frequencies = np.arange(0, nyquistFrequency, minimumFrequencyStep) hpfArgument = np.power((frequencies / highpassCutOff), 2*hpfOrder) hpfResponse = hpfArgument/(1 + hpfArgument) highPassCutOffIndex = np.ceil(highpassCutOff/minimumFrequencyStep) highPassedData = [] for channelNumber in xrange(numberOfChannels): if(highpassCutOff>0): x = sig.sosfilt(hpfSOS, rawData[channelNumber]) x = np.flipud(x) x = sig.sosfilt(hpfSOS, x) x = np.flipud(x) else: x = rawData[channelNumber] x[0:lpefOrder] = np.zeros(lpefOrder) x[dataLength - lpefOrder:dataLength-1] = np.zeros(lpefOrder) highPassedData.append(x) highPassedData = np.asarray(highPassedData) return highPassedData
def butter_bandstop_filter(lowcut, highcut, fs, order): nyq = 0.5 * fs low = lowcut / nyq high = highcut / nyq z, p, k = butter(order, [low, high], btype='bandstop', output='zpk') assert np.all(np.abs(p) < 1), 'unstable filter' sos = zpk2sos(z, p, k) # sos = iirdesign([low-0.01, high+0.01], [low+0.01, high-0.01], 3, 40, output='sos') return sos
def iir_band_filter(ite_data, fs, btype, ftype, order=None, Quality=None, window=None, lowcut=None, highcut=None, zerophase=None, rps=None): fe = fs / 2.0 if not lowcut == '' and not highcut == '': wn = [lowcut / fe, highcut / fe] elif not lowcut == '': wn = lowcut / fe elif not highcut == '': wn = highcut / fe if rps[0] == '': rps[0] = 10 if rps[1] == '': rps[1] = 10 if btype in ["butter", "bessel", "cheby1", "cheby2", "ellip"]: z, p, k = signal.iirfilter(order, wn, btype=ftype, ftype=btype, output="zpk", rp=rps[0], rs=rps[1]) try: sos = signal.zpk2sos(z, p, k) ite_data = signal.sosfilt(sos, ite_data) if zerophase: ite_data = signal.sosfilt(sos, ite_data[::-1])[::-1] except: print('A filter had an issue: ', 'btype', btype, 'ftype', ftype, 'order', order, 'Quality', Quality, 'window', window, 'lowcut', lowcut, 'highcut', highcut, 'rps', rps) elif btype == "iirnotch": b, a = signal.iirnotch(lowcut, Quality, fs) y = signal.filtfilt(b, a, ite_data) elif btype == "Moving average": z2 = np.cumsum(np.pad(ite_data, ((window, 0)), 'constant', constant_values=0), axis=0) z1 = np.cumsum(np.pad(ite_data, ((0, window)), 'constant', constant_values=ite_data[-1]), axis=0) ite_data = (z1 - z2)[(window - 1):-1] / window return ite_data
def preprocessEEG(eeg, fs, eogChannels=None, badChannels=None, hpCutoff=0.5, stdThresh=4): """ Preprocess EEG data and return data that has been filtered with bad channels removed. Input: eeg - ndarray of samples x channels (TxD) fs - sampling rate in Hz eogChannels - list/tuple of which channels in EEG are EOG (Default = None) badChannels - list/tuple of channels in EEG to remove (Default = None) hpCutoff - cutoff of the high-pass filter in Hz (Default = 0.5 Hz) stdThresh - # of standard deviations to tolerate before marking sample outlier (Default = 4) """ z, p, k = sig.butter(5, hpCutoff / fs * 2, 'highpass', output='zpk') sos = sig.zpk2sos(z, p, k) T, D = eeg.shape eeg = eeg - np.concatenate([eeg[[0], :] for i in range(T)]) # remove starting offset eeg = sig.sosfilt(sos, eeg) # apply high-pass filtering if eogChannels is not None: eeg = eeg - np.dot(eeg[:, eogChannels], lin.lstsq(eeg[:, eogChannels], eeg)[0]) # regress out EOG data eeg = np.delete(eeg, eogChannels, axis=1) # detect outliers > stdThresh stdThresh = stdThresh * np.std(eeg, 0) stdThresh = np.stack([stdThresh for _ in range(T)], axis=0) eeg[np.nonzero(np.abs(eeg) > stdThresh)] = 0 # remove outliers h = np.zeros(int(np.around(fs * 0.04))) h[0] = 1 eeg = sig.lfilter(h, 1, np.flipud(sig.lfilter(h, 1, np.flipud(eeg)))) eeg[np.isnan(eeg)] = 0.0 if badChannels is not None: eeg[:, badChannels] = 0 # zero out bad channels return eeg
def filter_from_npz(filename, fs): with np.load(filename) as data: z = data['z'] p = data['p'] k = data['k'] #sos conversion zd, pd, kd = zpk_bilinear(z, p, k, fs) sys_disc = sig.ZerosPolesGain(zd, pd, kd, dt=1 / fs) sos = sig.zpk2sos(sys_disc.zeros, sys_disc.poles, sys_disc.gain) return sos
def lowpass(data, freq, df, corners=4, zerophase=False): """ Butterworth-Lowpass Filter. Filter data removing data over certain frequency ``freq`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). Parameters ---------- data : array Data to filter. freq : float Filter corner frequency. df : float Sampling rate in Hz. corners : int Filter corners / order. **Default:** ``4`` zerophase : bool If True, apply filter once forwards and once backwards. This results in twice the filter order but zero phase shift in the resulting filtered trace. **Default:** ``False`` Returns ------- data : array Filtered data. """ fe = 0.5 * df f = freq / fe # raise for some bad scenarios if f > 1: f = 1.0 msg = "Selected corner frequency is above Nyquist. " + \ "Setting Nyquist as high corner." warnings.warn(msg) z, p, k = iirfilter(corners, f, btype='lowpass', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) if zerophase: firstpass = sosfilt(sos, data) return sosfilt(sos, firstpass[::-1])[::-1] else: return sosfilt(sos, data)
def lowpass_filter(data, SRD, freq, order): sampling_rate = SRD nyq = sampling_rate / 2 cutoff = freq normalized_cutoff = cutoff / nyq z, p, k = signal.butter(order, normalized_cutoff, output="zpk") lesos = signal.zpk2sos(z, p, k) filtered = signal.sosfilt(lesos, data) filtered = np.array(filtered) return filtered
def band_pass_filter(data, minfreq = 0.5, maxfreq = 0.8, df = 4, corners = 4): fe = 0.5 * df low = minfreq / fe high = maxfreq / fe z, p, k = sig.iirfilter(corners, [low, high], btype='band', ftype='butter', output='zpk') sos = sig.zpk2sos(z, p, k) return sig.sosfilt(sos, data)
def cost_filter(fs, zeros, poles, zero_order, pole_order): s_zeros = [] s_poles = [] if sum(zero_order) != sum(pole_order): raise ValueError('Bandpass not invertible!') for ii, fz in enumerate(zeros): _, sz, _ = sig.butter(zero_order[ii], fz / (fs / 2), output='zpk') s_zeros.append(sz) for ii, fp in enumerate(poles): _, sp, _ = sig.butter(pole_order[ii], fp / (fs / 2), output='zpk') s_poles.append(sp) s_zeros = np.concatenate(s_zeros) s_poles = np.concatenate(s_poles) BP = sig.zpk2sos(s_zeros, s_poles, 1, pairing='keep_odd') invBP = sig.zpk2sos(s_poles, s_zeros, 1, pairing='keep_odd') return BP, invBP
def test_equivalence(self): x = np.random.RandomState(0).randn(1000) for order in range(1, 6): zpk = signal.butter(order, 0.35, output="zpk") b, a = signal.zpk2tf(*zpk) sos = signal.zpk2sos(*zpk) y = signal.filtfilt(b, a, x) y_sos = sosfiltfilt(sos, x) np.testing.assert_allclose(y, y_sos, atol=1e-12, err_msg=f"order={order}")
def zpk2sos_quant(discrete_system, Qformat): sos = signal.zpk2sos(*discrete_system, pairing='nearest') # Trick for higher accuracy on small numerator coefficients non_zeros = sos[0,:3] > 0 b_factor = np.prod(sos[0,:3][non_zeros]) ** (1/non_zeros.sum()) sos[0,:3] /= b_factor sos[:,:3] *= b_factor ** (1/sos.shape[0]) sos_quant = quantizer_real(sos, Qformat) return sos, sos_quant
def test_filter(self, losc): zpk = [], [], 1 fts = losc.filter(zpk, analog=True) utils.assert_quantity_sub_equal(losc, fts) # check SOS filters can be used directly zpk = filter_design.highpass(50, sample_rate=losc.sample_rate) try: sos = signal.zpk2sos(*zpk) except AttributeError: # scipy < 0.16 pass else: utils.assert_quantity_almost_equal(losc.filter(zpk), losc.filter(sos))
def A_weighting(fs, output='ba'): """ Design of a digital A-weighting filter. Designs a digital A-weighting filter for sampling frequency `fs`. Warning: fs should normally be higher than 20 kHz. For example, fs = 48000 yields a class 1-compliant filter. Parameters ---------- fs : float Sampling frequency output : {'ba', 'zpk', 'sos'}, optional Type of output: numerator/denominator ('ba'), pole-zero ('zpk'), or second-order sections ('sos'). Default is 'ba'. Examples -------- Plot frequency response >>> from scipy.signal import freqz >>> import matplotlib.pyplot as plt >>> fs = 200000 >>> b, a = A_weighting(fs) >>> f = np.logspace(np.log10(10), np.log10(fs/2), 1000) >>> w = 2*pi * f / fs >>> w, h = freqz(b, a, w) >>> plt.semilogx(w*fs/(2*pi), 20*np.log10(abs(h))) >>> plt.grid(True, color='0.7', linestyle='-', which='both', axis='both') >>> plt.axis([10, 100e3, -50, 20]) Since this uses the bilinear transform, frequency response around fs/2 will be inaccurate at lower sampling rates. """ z, p, k = ABC_weighting('A') # Use the bilinear transformation to get the digital filter. z_d, p_d, k_d = _zpkbilinear(z, p, k, fs) if output == 'zpk': return z_d, p_d, k_d elif output in {'ba', 'tf'}: return zpk2tf(z_d, p_d, k_d) elif output == 'sos': return zpk2sos(z_d, p_d, k_d) else: raise ValueError("'%s' is not a valid output form." % output)
def lowpass_cheby_2(data, freq, df, maxorder=12, ba=False, freq_passband=False): """ Cheby2-Lowpass Filter Filter data by passing data only below a certain frequency. The main purpose of this cheby2 filter is downsampling. #318 shows some plots of this filter design itself. This method will iteratively design a filter, whose pass band frequency is determined dynamically, such that the values above the stop band frequency are lower than -96dB. :type data: numpy.ndarray :param data: Data to filter. :param freq: The frequency above which signals are attenuated with 95 dB :param df: Sampling rate in Hz. :param maxorder: Maximal order of the designed cheby2 filter :param ba: If True return only the filter coefficients (b, a) instead of filtering :param freq_passband: If True return additionally to the filtered data, the iteratively determined pass band frequency :return: Filtered data. """ nyquist = df * 0.5 # rp - maximum ripple of passband, rs - attenuation of stopband rp, rs, order = 1, 96, 1e99 ws = freq / nyquist # stop band frequency wp = ws # pass band frequency # raise for some bad scenarios if ws > 1: ws = 1.0 msg = "Selected corner frequency is above Nyquist. " + \ "Setting Nyquist as high corner." warnings.warn(msg) while True: if order <= maxorder: break wp = wp * 0.99 order, wn = cheb2ord(wp, ws, rp, rs, analog=0) if ba: return cheby2(order, rs, wn, btype='low', analog=0, output='ba') z, p, k = cheby2(order, rs, wn, btype='low', analog=0, output='zpk') sos = zpk2sos(z, p, k) if freq_passband: return sosfilt(sos, data), wp * nyquist return sosfilt(sos, data)
def bandpass(data, freqmin, freqmax, df, corners=4, zerophase=False): """ Butterworth-Bandpass Filter. Filter data from ``freqmin`` to ``freqmax`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). :type data: numpy.ndarray :param data: Data to filter. :param freqmin: Pass band low corner frequency. :param freqmax: Pass band high corner frequency. :param df: Sampling rate in Hz. :param corners: Filter corners / order. :param zerophase: If True, apply filter once forwards and once backwards. This results in twice the filter order but zero phase shift in the resulting filtered trace. :return: Filtered data. """ fe = 0.5 * df low = freqmin / fe high = freqmax / fe # raise for some bad scenarios if high - 1.0 > -1e-6: msg = ("Selected high corner frequency ({}) of bandpass is at or " "above Nyquist ({}). Applying a high-pass instead.").format( freqmax, fe) warnings.warn(msg) return highpass(data, freq=freqmin, df=df, corners=corners, zerophase=zerophase) if low > 1: msg = "Selected low corner frequency is above Nyquist." raise ValueError(msg) z, p, k = iirfilter(corners, [low, high], btype='band', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) if zerophase: firstpass = sosfilt(sos, data) return sosfilt(sos, firstpass[::-1])[::-1] else: return sosfilt(sos, data)
def bandstop(data, freqmin, freqmax, df, corners=4, zerophase=False): """ Butterworth-Bandstop Filter. Filter data removing data between frequencies ``freqmin`` and ``freqmax`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). :type data: numpy.ndarray :param data: Data to filter. :param freqmin: Stop band low corner frequency. :param freqmax: Stop band high corner frequency. :param df: Sampling rate in Hz. :param corners: Filter corners / order. :param zerophase: If True, apply filter once forwards and once backwards. This results in twice the number of corners but zero phase shift in the resulting filtered trace. :return: Filtered data. """ fe = 0.5 * df low = freqmin / fe high = freqmax / fe # raise for some bad scenarios if high > 1: high = 1.0 msg = "Selected high corner frequency is above Nyquist. " + \ "Setting Nyquist as high corner." warnings.warn(msg) if low > 1: msg = "Selected low corner frequency is above Nyquist." raise ValueError(msg) z, p, k = iirfilter(corners, [low, high], btype='bandstop', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) if zerophase: firstpass = sosfilt(sos, data) return sosfilt(sos, firstpass[::-1])[::-1] else: return sosfilt(sos, data)
def bandpass(freqmin, freqmax, df, corners=4): """ From obspy with modification. Butterworth-Bandpass Filter. Filter data from ``freqmin`` to ``freqmax`` using ``corners`` corners. The filter uses :func:`scipy.signal.iirfilter` (for design) and :func:`scipy.signal.sosfilt` (for applying the filter). :type data: numpy.ndarray :param data: Data to filter. :param freqmin: Pass band low corner frequency. :param freqmax: Pass band high corner frequency. :param df: Sampling rate in Hz. :param corners: Filter corners / order. :param zerophase: If True, apply filter once forwards and once backwards. This results in twice the filter order but zero phase shift in the resulting filtered trace. :return: Filtered data. """ fe = 0.5 * df low = freqmin / fe high = freqmax / fe # raise for some bad scenarios if high > 1: high = 1.0 msg = "Selected high corner frequency is above Nyquist. " + \ "Setting Nyquist as high corner." warnings.warn(msg) if low > 1: msg = "Selected low corner frequency is above Nyquist." raise ValueError(msg) z, p, k = iirfilter(corners, [low, high], btype='band', ftype='butter', output='zpk') sos = zpk2sos(z, p, k) return sos
def ITU_R_468_weighting(fs, output='ba'): """ Return ITU-R 468 digital weighting filter transfer function Parameters ---------- fs : float Sampling frequency Examples -------- >>> from scipy.signal import freqz >>> import matplotlib.pyplot as plt >>> fs = 200000 >>> b, a = ITU_R_468_weighting(fs) >>> f = np.logspace(np.log10(10), np.log10(fs/2), 1000) >>> w = 2*pi * f / fs >>> w, h = freqz(b, a, w) >>> plt.semilogx(w*fs/(2*pi), 20*np.log10(abs(h))) >>> plt.grid(True, color='0.7', linestyle='-', which='both', axis='both') >>> plt.axis([10, 100e3, -50, 20]) """ z, p, k = ITU_R_468_weighting_analog() # Use the bilinear transformation to get the digital filter. zz, pz, kz = _zpkbilinear(z, p, k, fs) if output == 'zpk': return zz, pz, kz elif output in {'ba', 'tf'}: return zpk2tf(zz, pz, kz) elif output == 'sos': return zpk2sos(zz, pz, kz) else: raise ValueError("'%s' is not a valid output form." % output)
# Frequence de coupure dans la bande attenuee fa = 4000 ws = fa/(fe/2) # Taux d'ondulation dans la bande passante (dB) delta1 = 3 # Taux d'ondulation dans la bande attenuee (dB) delta2 = 30 # Determination de l'ordre et de la frequence de coupure N, Wn = signal.buttord(wp, ws, delta1, delta2) # Determination des coefficients a, b = signal.butter(N, Wn) # Determination de la reponse frequentielle w, h = signal.freqz(b, a, whole=True) # Plot f, (ax1, ax2) = plt.subplots(2) ax1.plot(w, 20 * np.log10(abs(h))) ax2.plot(w, np.unwrap(np.angle(h))) plt.show() # Decomposition en cellules du 1er et du 2eme ordre Z = signal.zpk2sos(np.roots(a), np.roots(b), a[0]) print(Z)
def plane_25d(x0, r0, npw, fs, max_order=None, c=None, s2z=matchedz_zpk): r"""Virtual plane wave by 2.5-dimensional NFC-HOA. .. math:: D(\phi_0, s) = 2\e{\frac{s}{c}r_0} \sum_{m=-M}^{M} (-1)^m \Big(\frac{s}{s-\frac{c}{r_0}\sigma_0}\Big)^\mu \prod_{l=1}^{\nu} \frac{s^2}{(s-\frac{c}{r_0}\sigma_l)^2+(\frac{c}{r_0}\omega_l)^2} \e{\i m(\phi_0 - \phi_\text{pw})} The driving function is represented in the Laplace domain, from which the recursive filters are designed. :math:`\sigma_l + \i\omega_l` denotes the complex roots of the reverse Bessel polynomial. The number of second-order sections is :math:`\nu = \big\lfloor\tfrac{|m|}{2}\big\rfloor`, whereas the number of first-order section :math:`\mu` is either 0 or 1 for even and odd :math:`|m|`, respectively. Parameters ---------- x0 : (N, 3) array_like Sequence of secondary source positions. r0 : float Radius of the circular secondary source distribution. npw : (3,) array_like Unit vector (propagation direction) of plane wave. fs : int Sampling frequency in Hertz. max_order : int, optional Ambisonics order. c : float, optional Speed of sound in m/s. s2z : callable, optional Function transforming s-domain poles and zeros into z-domain, e.g. :func:`matchedz_zpk`, :func:`scipy.signal.bilinear_zpk`. Returns ------- delay : float Overall delay in seconds. weight : float Overall weight. sos : list of numpy.ndarray Second-order section filters :func:`scipy.signal.sosfilt`. phaseshift : (N,) numpy.ndarray Phase shift in radians. selection : (N,) numpy.ndarray Boolean array containing only ``True`` indicating that all secondary source are "active" for NFC-HOA. secondary_source_function : callable A function that can be used to create the sound field of a single secondary source. See `sfs.td.synthesize()`. Examples -------- .. plot:: :context: close-figs delay, weight, sos, phaseshift, selection, secondary_source = \ sfs.td.nfchoa.plane_25d(array.x, R, npw, fs) d = sfs.td.nfchoa.driving_signals_25d( delay, weight, sos, phaseshift, signal) plot(d, selection, secondary_source) """ if max_order is None: max_order = _util.max_order_circular_harmonics(len(x0)) if c is None: c = _default.c x0 = _util.asarray_of_rows(x0) npw = _util.asarray_1d(npw) phi0, _, _ = _util.cart2sph(*x0.T) phipw, _, _ = _util.cart2sph(*npw) phaseshift = phi0 - phipw + _np.pi delay = -r0 / c weight = 2 sos = [] for m in range(max_order + 1): _, p, _ = _sig.besselap(m, norm='delay') s_zeros = _np.zeros(m) s_poles = c / r0 * p s_gain = 1 z_zeros, z_poles, z_gain = s2z(s_zeros, s_poles, s_gain, fs) sos.append(_sig.zpk2sos(z_zeros, z_poles, z_gain, pairing='nearest')) selection = _util.source_selection_all(len(x0)) return (delay, weight, sos, phaseshift, selection, _secondary_source_point(c))
def point_3d(x0, r0, xs, fs, max_order=None, c=None, s2z=matchedz_zpk): r"""Virtual point source by 3-dimensional NFC-HOA. .. math:: D(\phi_0, s) = \frac{\e{\frac{s}{c}(r_0-r_\text{s})}}{4 \pi r_0 r_\text{s}} \sum_{n=0}^{N} (2n+1) P_{n}(\cos\Theta) \Big(\frac{s-\frac{c}{r_\text{s}}\sigma_0}{s-\frac{c}{r_0}\sigma_0}\Big)^\mu \prod_{l=1}^{\nu} \frac{(s-\frac{c}{r_\text{s}}\sigma_l)^2-(\frac{c}{r_\text{s}}\omega_l)^2} {(s-\frac{c}{r_0}\sigma_l)^2+(\frac{c}{r_0}\omega_l)^2} The driving function is represented in the Laplace domain, from which the recursive filters are designed. :math:`\sigma_l + \i\omega_l` denotes the complex roots of the reverse Bessel polynomial. The number of second-order sections is :math:`\nu = \big\lfloor\tfrac{|m|}{2}\big\rfloor`, whereas the number of first-order section :math:`\mu` is either 0 or 1 for even and odd :math:`|m|`, respectively. :math:`P_{n}(\cdot)` denotes the Legendre polynomial of degree :math:`n`, and :math:`\Theta` the angle between :math:`(\theta, \phi)` and :math:`(\theta_\text{s}, \phi_\text{s})`. Parameters ---------- x0 : (N, 3) array_like Sequence of secondary source positions. r0 : float Radius of the spherial secondary source distribution. xs : (3,) array_like Virtual source position. fs : int Sampling frequency in Hertz. max_order : int, optional Ambisonics order. c : float, optional Speed of sound in m/s. s2z : callable, optional Function transforming s-domain poles and zeros into z-domain, e.g. :func:`matchedz_zpk`, :func:`scipy.signal.bilinear_zpk`. Returns ------- delay : float Overall delay in seconds. weight : float Overall weight. sos : list of numpy.ndarray Second-order section filters :func:`scipy.signal.sosfilt`. phaseshift : (N,) numpy.ndarray Phase shift in radians. selection : (N,) numpy.ndarray Boolean array containing only ``True`` indicating that all secondary source are "active" for NFC-HOA. secondary_source_function : callable A function that can be used to create the sound field of a single secondary source. See `sfs.td.synthesize()`. """ if max_order is None: max_order = _util.max_order_spherical_harmonics(len(x0)) if c is None: c = _default.c x0 = _util.asarray_of_rows(x0) xs = _util.asarray_1d(xs) phi0, theta0, _ = _util.cart2sph(*x0.T) phis, thetas, rs = _util.cart2sph(*xs) phaseshift = _np.arccos(_np.dot(x0 / r0, xs / rs)) delay = (rs - r0) / c weight = 1 / r0 / rs sos = [] for m in range(max_order + 1): _, p, _ = _sig.besselap(m, norm='delay') s_zeros = c / rs * p s_poles = c / r0 * p s_gain = 1 z_zeros, z_poles, z_gain = s2z(s_zeros, s_poles, s_gain, fs) sos.append(_sig.zpk2sos(z_zeros, z_poles, z_gain, pairing='nearest')) selection = _util.source_selection_all(len(x0)) return (delay, weight, sos, phaseshift, selection, _secondary_source_point(c))
def filter(self, *filt): """Apply the given filter to this `TimeSeries`. All recognised filter arguments are converted either into cascading second-order sections (if scipy >= 0.16 is installed), or into the ``(numerator, denominator)`` representation before being applied to this `TimeSeries`. .. note:: All filters are presumed to be digital (Z-domain), if you have an analog ZPK (in Hertz or in rad/s) you should be using `TimeSeries.zpk` instead. .. note:: When using `scipy` < 0.16 some higher-order filters may be unstable. With `scipy` >= 0.16 higher-order filters are decomposed into second-order-sections, and so are much more stable. Parameters ---------- *filt one of: - :class:`scipy.signal.lti` - `MxN` `numpy.ndarray` of second-order-sections (`scipy` >= 0.16 only) - ``(numerator, denominator)`` polynomials - ``(zeros, poles, gain)`` - ``(A, B, C, D)`` 'state-space' representation Returns ------- result : `TimeSeries` the filtered version of the input `TimeSeries` See also -------- TimeSeries.zpk for instructions on how to filter using a ZPK with frequencies in Hertz scipy.signal.sosfilter for details on the second-order section filtering method (`scipy` >= 0.16 only) scipy.signal.lfilter for details on the filtering method Raises ------ ValueError If ``filt`` arguments cannot be interpreted properly """ sos = None # single argument given if len(filt) == 1: filt = filt[0] # detect LTI if isinstance(filt, signal.lti): filt = filt a = filt.den b = filt.num # detect SOS elif isinstance(filt, numpy.ndarray) and filt.ndim == 2: sos = filt # detect taps else: b = filt a = [1] # detect TF elif len(filt) == 2: b, a = filt elif len(filt) == 3: try: sos = signal.zpk2sos(*filt) except AttributeError: b, a = signal.zpk2tf(*filt) elif len(filt) == 4: try: zpk = signal.ss2zpk(*filt) sos = signal.zpk2sos(zpk) except AttributeError: b, a = signal.ss2tf(*filt) else: raise ValueError("Cannot interpret filter arguments. Please " "give either a signal.lti object, or a " "tuple in zpk or ba format. See " "scipy.signal docs for details.") if sos is not None: new = signal.sosfilt(sos, self, axis=0).view(self.__class__) else: new = signal.lfilter(b, a, self, axis=0).view(self.__class__) new.__dict__ = self.copy_metadata() return new
#dt = np.sum(np.abs(examp_time[:-1])) / (ntimesteps-1) dt = duration / ntimesteps dt_test = abs(examp_time[-1]-examp_time[0]) / ntimesteps if dt_test != dt and rank == 0: msg = 'Small discrepancy between inferred and prescribed sampling rate:' warn(msg) print(dt) print(dt_test) # Determine the sampling rate fs_old = 1./dt # Get filter coeff z, p, k = cheby2_lowpass(fs_old,freq) sos = zpk2sos(z, p, k) # Determine which channels to save... if channel is not 'all': cha_incr = 3 if channel == 'MXN': cha_offset = 0 elif channel == 'MXE': cha_offset = 1 elif channel == 'MXZ': cha_offset = 2 else: cha_incr = 1 cha_offset = 0 counter = 0
def filter_zpk(timeseries, z, p, k): """Return a new timeseries that was filtered with a zero-pole-gain filter. The transfer function in the s-domain looks like: .. math:: \\frac{H(s) = (s - s_1) * (s - s_3) * ... * (s - s_n)}{(s - s_2) * (s - s_4) * ... * (s - s_m)}, m >= n The zeroes, and poles entered in Hz are converted to angular frequency, along the imaginary axis in the s-domain s=i*omega. Then the zeroes, and poles are bilinearly transformed via: .. math:: z(s) = \\frac{(1 + s*T/2)}{(1 - s*T/2)} Where z is the z-domain value, s is the s-domain value, and T is the sampling period. After the poles and zeroes have been bilinearly transformed, then the second-order sections are found and filter the data using scipy. Parameters ---------- timeseries: TimeSeries The TimeSeries instance to be filtered. z: array Array of zeros to include in zero-pole-gain filter design. In units of Hz. p: array Array of poles to include in zero-pole-gain filter design. In units of Hz. k: float Gain to include in zero-pole-gain filter design. This gain is a constant multiplied to the transfer function. Returns ------- Time Series: TimeSeries A new TimeSeries that has been filtered. Examples -------- To apply a 5 zeroes at 100Hz, 5 poles at 1Hz, and a gain of 1e-10 filter to a TimeSeries instance, do: >>> filtered_data = zpk_filter(timeseries, [100]*5, [1]*5, 1e-10) """ # sanity check type if not isinstance(timeseries, TimeSeries): raise TypeError("Can only filter TimeSeries instances.") # sanity check casual filter degree = len(p) - len(z) if degree < 0: raise TypeError("May not have more zeroes than poles. \ Filter is not casual.") # cast zeroes and poles as arrays and gain as a float z = np.array(z) p = np.array(p) k = float(k) # put zeroes and poles in the s-domain # convert from frequency to angular frequency z *= -2 * np.pi p *= -2 * np.pi # get denominator of bilinear transform fs = 2.0 * timeseries.sample_rate # zeroes in the z-domain z_zd = (1 + z/fs) / (1 - z/fs) # any zeros that were at infinity are moved to the Nyquist frequency z_zd = z_zd[np.isfinite(z_zd)] z_zd = np.append(z_zd, -np.ones(degree)) # poles in the z-domain p_zd = (1 + p/fs) / (1 - p/fs) # gain change in z-domain k_zd = k * np.prod(fs - z) / np.prod(fs - p) # get second-order sections sos = zpk2sos(z_zd, p_zd, k_zd) # filter filtered_data = sosfilt(sos, timeseries.numpy()) return TimeSeries(filtered_data, delta_t = timeseries.delta_t, dtype=timeseries.dtype, epoch=timeseries._epoch)