def butter(signal, highpass_freq=None, lowpass_freq=None, order=4, filter_function='filtfilt', fs=1.0, axis=-1): """ Butterworth filtering function for neo.AnalogSignalArray. Filter type is determined according to how values of `highpass_freq` and `lowpass_freq` are given (see Parameters section for details). Parameters ---------- signal : AnalogSignalArray or Quantity array or NumPy ndarray Time series data to be filtered. When given as Quantity array or NumPy ndarray, the sampling frequency should be given through the keyword argument `fs`. highpass_freq, lowpass_freq : Quantity or float High-pass and low-pass cut-off frequencies, respectively. When given as float, the given value is taken as frequency in Hz. Filter type is determined depending on values of these arguments: * highpass_freq only (lowpass_freq = None): highpass filter * lowpass_freq only (highpass_freq = None): lowpass filter * highpass_freq < lowpass_freq: bandpass filter * highpass_freq > lowpass_freq: bandstop filter order : int Order of Butterworth filter. Default is 4. filter_function : string Filtering function to be used. Either 'filtfilt' (`scipy.signal.filtfilt()`) or 'lfilter' (`scipy.signal.lfilter()`). In most applications 'filtfilt' should be used, because it doesn't bring about phase shift due to filtering. Default is 'filtfilt'. fs : Quantity or float The sampling frequency of the input time series. When given as float, its value is taken as frequency in Hz. When the input is given as neo AnalogSignalArray, its attribute is used to specify the sampling frequency and this parameter is ignored. Default is 1.0. axis : int Axis along which filter is applied. Default is -1. Returns ------- filtered_signal : AnalogSignalArray or Quantity array or NumPy ndarray Filtered input data. The shape and type is identical to those of the input. """ def _design_butterworth_filter(Fs, hpfreq=None, lpfreq=None, order=4): # set parameters for filter design Fn = Fs / 2. # - filter type is determined according to the values of cut-off # frequencies if lpfreq and hpfreq: if hpfreq < lpfreq: Wn = (hpfreq / Fn, lpfreq / Fn) btype = 'bandpass' else: Wn = (lpfreq / Fn, hpfreq / Fn) btype = 'bandstop' elif lpfreq: Wn = lpfreq / Fn btype = 'lowpass' elif hpfreq: Wn = hpfreq / Fn btype = 'highpass' else: raise ValueError( "Either highpass_freq or lowpass_freq must be given") # return filter coefficients return scipy.signal.butter(order, Wn, btype=btype) # design filter Fs = signal.sampling_rate.rescale(pq.Hz).magnitude \ if hasattr(signal, 'sampling_rate') else fs Fh = highpass_freq.rescale(pq.Hz).magnitude \ if isinstance(highpass_freq, pq.quantity.Quantity) else highpass_freq Fl = lowpass_freq.rescale(pq.Hz).magnitude \ if isinstance(lowpass_freq, pq.quantity.Quantity) else lowpass_freq b, a = _design_butterworth_filter(Fs, Fh, Fl, order) # When the input is AnalogSignalArray, the axis for time index (i.e. the # first axis) needs to be rolled to the last data = np.asarray(signal) if isinstance(signal, neo.AnalogSignalArray): data = np.rollaxis(data, 0, len(data.shape)) # apply filter if filter_function is 'lfilter': filtered_data = scipy.signal.lfilter(b, a, data, axis=axis) elif filter_function is 'filtfilt': filtered_data = scipy.signal.filtfilt(b, a, data, axis=axis) else: raise ValueError( "filter_func must to be either 'filtfilt' or 'lfilter'") if isinstance(signal, neo.AnalogSignalArray): return signal.duplicate_with_new_array(filtered_data.T) elif isinstance(signal, pq.quantity.Quantity): return filtered_data * signal.units else: return filtered_data
def butter(signal, highpass_freq=None, lowpass_freq=None, order=4, filter_function='filtfilt', fs=1.0, axis=-1): """ Butterworth filtering function for neo.AnalogSignal. Filter type is determined according to how values of `highpass_freq` and `lowpass_freq` are given (see Parameters section for details). Parameters ---------- signal : AnalogSignal or Quantity array or NumPy ndarray Time series data to be filtered. When given as Quantity array or NumPy ndarray, the sampling frequency should be given through the keyword argument `fs`. highpass_freq, lowpass_freq : Quantity or float High-pass and low-pass cut-off frequencies, respectively. When given as float, the given value is taken as frequency in Hz. Filter type is determined depending on values of these arguments: * highpass_freq only (lowpass_freq = None): highpass filter * lowpass_freq only (highpass_freq = None): lowpass filter * highpass_freq < lowpass_freq: bandpass filter * highpass_freq > lowpass_freq: bandstop filter order : int Order of Butterworth filter. Default is 4. filter_function : string Filtering function to be used. Either 'filtfilt' (`scipy.signal.filtfilt()`) or 'lfilter' (`scipy.signal.lfilter()`). In most applications 'filtfilt' should be used, because it doesn't bring about phase shift due to filtering. Default is 'filtfilt'. fs : Quantity or float The sampling frequency of the input time series. When given as float, its value is taken as frequency in Hz. When the input is given as neo AnalogSignal, its attribute is used to specify the sampling frequency and this parameter is ignored. Default is 1.0. axis : int Axis along which filter is applied. Default is -1. Returns ------- filtered_signal : AnalogSignal or Quantity array or NumPy ndarray Filtered input data. The shape and type is identical to those of the input. """ def _design_butterworth_filter(Fs, hpfreq=None, lpfreq=None, order=4): # set parameters for filter design Fn = Fs / 2. # - filter type is determined according to the values of cut-off # frequencies if lpfreq and hpfreq: if hpfreq < lpfreq: Wn = (hpfreq / Fn, lpfreq / Fn) btype = 'bandpass' else: Wn = (lpfreq / Fn, hpfreq / Fn) btype = 'bandstop' elif lpfreq: Wn = lpfreq / Fn btype = 'lowpass' elif hpfreq: Wn = hpfreq / Fn btype = 'highpass' else: raise ValueError( "Either highpass_freq or lowpass_freq must be given" ) # return filter coefficients return scipy.signal.butter(order, Wn, btype=btype) # design filter Fs = signal.sampling_rate.rescale(pq.Hz).magnitude \ if hasattr(signal, 'sampling_rate') else fs Fh = highpass_freq.rescale(pq.Hz).magnitude \ if isinstance(highpass_freq, pq.quantity.Quantity) else highpass_freq Fl = lowpass_freq.rescale(pq.Hz).magnitude \ if isinstance(lowpass_freq, pq.quantity.Quantity) else lowpass_freq b, a = _design_butterworth_filter(Fs, Fh, Fl, order) # When the input is AnalogSignal, the axis for time index (i.e. the # first axis) needs to be rolled to the last data = np.asarray(signal) if isinstance(signal, neo.AnalogSignal): data = np.rollaxis(data, 0, len(data.shape)) # apply filter if filter_function is 'lfilter': filtered_data = scipy.signal.lfilter(b, a, data, axis=axis) elif filter_function is 'filtfilt': filtered_data = scipy.signal.filtfilt(b, a, data, axis=axis) else: raise ValueError( "filter_func must to be either 'filtfilt' or 'lfilter'" ) if isinstance(signal, neo.AnalogSignal): return signal.duplicate_with_new_array(np.rollaxis(filtered_data, -1, 0)) elif isinstance(signal, pq.quantity.Quantity): return filtered_data * signal.units else: return filtered_data
def hilbert(signal, N='nextpow'): ''' Apply a Hilbert transform to an AnalogSignal object in order to obtain its (complex) analytic signal. The time series of the instantaneous angle and amplitude can be obtained as the angle (np.angle) and absolute value (np.abs) of the complex analytic signal, respectively. By default, the function will zero-pad the signal to a length corresponding to the next higher power of 2. This will provide higher computational efficiency at the expense of memory. In addition, this circumvents a situation where for some specific choices of the length of the input, scipy.signal.hilbert() will not terminate. Parameters ----------- signal : neo.AnalogSignal Signal(s) to transform N : string or int Defines whether the signal is zero-padded. 'none': no padding 'nextpow': zero-pad to the next length that is a power of 2 int: directly specify the length to zero-pad to (indicates the number of Fourier components, see parameter N of scipy.signal.hilbert()). Default: 'nextpow'. Returns ------- neo.AnalogSignal Contains the complex analytic signal(s) corresponding to the input signals. The unit of the analytic signal is dimensionless. Example ------- Create a sine signal at 5 Hz with increasing amplitude and calculate the instantaneous phases >>> t = np.arange(0, 5000) * ms >>> f = 5. * Hz >>> a = neo.AnalogSignal( ... np.array( ... (1 + t.magnitude / t[-1].magnitude) * np.sin( ... 2. * np.pi * f * t.rescale(s))).reshape((-1,1))*mV, ... t_start=0*s, sampling_rate=1000*Hz) >>> analytic_signal = hilbert(a, N='nextpow') >>> angles = np.angle(analytic_signal) >>> amplitudes = np.abs(analytic_signal) >>> print angles [[-1.57079633] [-1.51334228] [-1.46047675] ..., [-1.73112977] [-1.68211683] [-1.62879501]] >>> plt.plot(t,angles) ''' # Length of input signals n_org = signal.shape[0] # Right-pad signal to desired length using the signal itself if type(N) == int: # User defined padding n = N elif N == 'nextpow': # To speed up calculation of the Hilbert transform, make sure we change # the signal to be of a length that is a power of two. Failure to do so # results in computations of certain signal lengths to not finish (or # finish in absurd time). This might be a bug in scipy (0.16), e.g., # the following code will not terminate for this value of k: # # import numpy # import scipy.signal # k=679346 # t = np.arange(0, k) / 1000. # a = (1 + t / t[-1]) * np.sin(2 * np.pi * 5 * t) # analytic_signal = scipy.signal.hilbert(a) # # For this reason, nextpow is the default setting for now. n = 2 ** (int(np.log2(n_org - 1)) + 1) elif N == 'none': # No padding n = n_org else: raise ValueError("'{}' is an unknown N.".format(N)) output = signal.duplicate_with_new_array( scipy.signal.hilbert(signal.magnitude, N=n, axis=0)[:n_org]) return output / output.units
def hilbert(signal, N='nextpow'): ''' Apply a Hilbert transform to an AnalogSignal object in order to obtain its (complex) analytic signal. The time series of the instantaneous angle and amplitude can be obtained as the angle (np.angle) and absolute value (np.abs) of the complex analytic signal, respectively. By default, the function will zero-pad the signal to a length corresponding to the next higher power of 2. This will provide higher computational efficiency at the expense of memory. In addition, this circumvents a situation where for some specific choices of the length of the input, scipy.signal.hilbert() will not terminate. Parameters ----------- signal : neo.AnalogSignal Signal(s) to transform N : string or int Defines whether the signal is zero-padded. 'none': no padding 'nextpow': zero-pad to the next length that is a power of 2 int: directly specify the length to zero-pad to (indicates the number of Fourier components, see parameter N of scipy.signal.hilbert()). Default: 'nextpow'. Returns ------- neo.AnalogSignal Contains the complex analytic signal(s) corresponding to the input signals. The unit of the analytic signal is dimensionless. Example ------- Create a sine signal at 5 Hz with increasing amplitude and calculate the instantaneous phases >>> t = np.arange(0, 5000) * ms >>> f = 5. * Hz >>> a = neo.AnalogSignal( ... np.array( ... (1 + t.magnitude / t[-1].magnitude) * np.sin( ... 2. * np.pi * f * t.rescale(s))).reshape((-1,1))*mV, ... t_start=0*s, sampling_rate=1000*Hz) >>> analytic_signal = hilbert(a, N='nextpow') >>> angles = np.angle(analytic_signal) >>> amplitudes = np.abs(analytic_signal) >>> print angles [[-1.57079633] [-1.51334228] [-1.46047675] ..., [-1.73112977] [-1.68211683] [-1.62879501]] >>> plt.plot(t,angles) ''' # Length of input signals n_org = signal.shape[0] # Right-pad signal to desired length using the signal itself if type(N) == int: # User defined padding n = N elif N == 'nextpow': # To speed up calculation of the Hilbert transform, make sure we change # the signal to be of a length that is a power of two. Failure to do so # results in computations of certain signal lengths to not finish (or # finish in absurd time). This might be a bug in scipy (0.16), e.g., # the following code will not terminate for this value of k: # # import numpy # import scipy.signal # k=679346 # t = np.arange(0, k) / 1000. # a = (1 + t / t[-1]) * np.sin(2 * np.pi * 5 * t) # analytic_signal = scipy.signal.hilbert(a) # # For this reason, nextpow is the default setting for now. n = 2**(int(np.log2(n_org - 1)) + 1) elif N == 'none': # No padding n = n_org else: raise ValueError("'{}' is an unknown N.".format(N)) output = signal.duplicate_with_new_array( scipy.signal.hilbert(signal.magnitude, N=n, axis=0)[:n_org]) return output / output.units