Source code for pyfar.dsp.filter.band_filter

"""Module for creating and applying digital IIR filters."""
import numpy as np
import scipy.signal as spsignal
import pyfar as pf


[docs] def butterworth(signal, N, frequency, btype='lowpass', sampling_rate=None): """ Create and apply a digital Butterworth IIR filter. This is a wrapper for :py:func:`scipy.signal.butter`. Which creates digital Butterworth filter coefficients in second-order sections (SOS). Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. N : int The order of the Butterworth filter frequency : number, array like The cut off-frequency in Hz if `btype` is lowpass or highpass. An array like containing the lower and upper cut-off frequencies in Hz if `btype` is bandpass or bandstop. btype : str One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``, ``'bandstop'``. The default is ``'lowpass'``. sampling_rate : None, number The sampling rate in Hz. Only required if signal is ``None``. The default is ``None``. Returns ------- signal : Signal The filtered signal. Only returned if ``sampling_rate = None``. filter : FilterSOS SOS Filter object. Only returned if ``signal = None``. """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be none.') # sampling frequency in Hz fs = signal.sampling_rate if sampling_rate is None else sampling_rate # normalized frequency (half-cycle / per sample) frequency_norm = np.asarray(frequency) / fs * 2 # get filter coefficients sos = spsignal.butter(N, frequency_norm, btype, analog=False, output='sos') # generate filter object filt = pf.FilterSOS(sos, fs) filt.comment = (f"Butterworth {btype} of order {N}. " f"Cut-off frequency {frequency} Hz.") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal signal_filt = filt.process(signal) return signal_filt
[docs] def chebyshev1(signal, N, ripple, frequency, btype='lowpass', sampling_rate=None): """ Create and apply digital Chebyshev Type I IIR filter. This is a wrapper for :py:func:`scipy.signal.cheby1`. Which creates digital Chebyshev Type I filter coefficients in second-order sections (SOS). Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. N : int The order of the Chebychev filter. ripple : number The passband ripple in dB. frequency : number, array like The cut off-frequency in Hz if `btype` is ``'lowpass'`` or ``'highpass'``. An array like containing the lower and upper cut-off frequencies in Hz if `btype` is ``'bandpass'`` or ``'bandstop'``. btype : str One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``, ``'bandstop'``. The default is ``'lowpass'``. sampling_rate : None, number The sampling rate in Hz. Only required if signal is ``None``. The default is ``None``. Returns ------- signal : Signal The filtered signal. Only returned if ``sampling_rate = None``. filter : FilterSOS SOS Filter object. Only returned if ``signal = None``. """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be none.') # sampling frequency in Hz fs = signal.sampling_rate if sampling_rate is None else sampling_rate # normalized frequency (half-cycle / per sample) frequency_norm = np.asarray(frequency) / fs * 2 # get filter coefficients sos = spsignal.cheby1(N, ripple, frequency_norm, btype, analog=False, output='sos') # generate filter object filt = pf.FilterSOS(sos, fs) filt.comment = (f"Chebychev Type I {btype} of order {N}. " f"Cut-off frequency {frequency} Hz. " f"Pass band ripple {ripple} dB.") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal signal_filt = filt.process(signal) return signal_filt
[docs] def chebyshev2(signal, N, attenuation, frequency, btype='lowpass', sampling_rate=None): """ Create and apply digital Chebyshev Type II IIR filter. This is a wrapper for :py:func:`scipy.signal.cheby2`. Which creates digital Chebyshev Type II filter coefficients in second-order sections (SOS). Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. N : int The order of the Chebychev filter. attenuation : number The minimum stop band attenuation in dB. frequency : number, array like The frequency in Hz where the `attenuatoin` is first reached if `btype` is ``'lowpass'`` or ``'highpass'``. An array like containing the lower and upper frequencies in Hz if `btype` is ``'bandpass'`` or ``'bandstop'``. btype : str One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``, ``'bandstop'``. The default is ``'lowpass'``. sampling_rate : None, number The sampling rate in Hz. Only required if signal is ``None``. The default is ``None``. Returns ------- signal : Signal The filtered signal. Only returned if ``sampling_rate = None``. filter : FilterSOS SOS Filter object. Only returned if ``signal = None``. """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be none.') # sampling frequency in Hz fs = signal.sampling_rate if sampling_rate is None else sampling_rate # normalized frequency (half-cycle / per sample) frequency_norm = np.asarray(frequency) / fs * 2 # get filter coefficients sos = spsignal.cheby2(N, attenuation, frequency_norm, btype, analog=False, output='sos') # generate filter object filt = pf.FilterSOS(sos, fs) filt.comment = (f"Chebychev Type II {btype} of order {N}. " f"Cut-off frequency {frequency} Hz. " f"Stop band attenuation {attenuation} dB.") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal signal_filt = filt.process(signal) return signal_filt
[docs] def elliptic(signal, N, ripple, attenuation, frequency, btype='lowpass', sampling_rate=None): """ Create and apply digital Elliptic (Cauer) IIR filter. This is a wrapper for :py:func:`scipy.signal.ellip`. Which creates digital Elliptic (Cauer) filter coefficients in second-order sections (SOS). Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. N : int The order of the Elliptic filter. ripple : number The passband ripple in dB. attenuation : number The minimum stop band attenuation in dB. frequency : number, array like The cut off-frequency in Hz if `btype` is ``'lowpass'`` or ``'highpass'``. An array like containing the lower and upper cut-off frequencies in Hz if `btype` is ``'bandpass'`` or ``'bandstop'``. btype : str One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``, ``'bandstop'``. The default is ``'lowpass'``. sampling_rate : None, number The sampling rate in Hz. Only required if signal is ``None``. The default is ``None``. Returns ------- signal : Signal The filtered signal. Only returned if ``sampling_rate = None``. filter : FilterSOS SOS Filter object. Only returned if ``signal = None``. """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be none.') # sampling frequency in Hz fs = signal.sampling_rate if sampling_rate is None else sampling_rate # normalized frequency (half-cycle / per sample) frequency_norm = np.asarray(frequency) / fs * 2 # get filter coefficients sos = spsignal.ellip(N, ripple, attenuation, frequency_norm, btype, analog=False, output='sos') # generate filter object filt = pf.FilterSOS(sos, fs) filt.comment = (f"Elliptic (Cauer) {btype} of order {N}. " f"Cut-off frequency {frequency} Hz. " f"Pass band ripple {ripple} dB. " f"Stop band attenuation {attenuation} dB.") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal signal_filt = filt.process(signal) return signal_filt
[docs] def bessel(signal, N, frequency, btype='lowpass', norm='phase', sampling_rate=None): """ Create and apply digital Bessel/Thomson IIR filter. This is a wrapper for :py:func:`scipy.signal.bessel`. Which creates digital Bessel filter coefficients in second-order sections (SOS). Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. N : int The order of the Bessel/Thomson filter. frequency : number, array like The cut off-frequency in Hz if `btype` is ``'lowpass'`` or ``'highpass'``. An array like containing the lower and upper cut-off frequencies in Hz if `btype` is bandpass or bandstop. btype : str One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``, ``'bandstop'``. The default is ``'lowpass'``. norm : str Critical frequency normalization: ``'phase'`` The filter is normalized such that the phase response reaches its midpoint at angular (e.g. rad/s) frequency `Wn`. This happens for both low-pass and high-pass filters, so this is the "phase-matched" case. The magnitude response asymptotes are the same as a Butterworth filter of the same order with a cutoff of `Wn`. This is the default, and matches MATLAB's implementation. ``'delay'`` The filter is normalized such that the group delay in the passband is 1/`Wn` (e.g., seconds). This is the "natural" type obtained by solving Bessel polynomials. ``'mag'`` The filter is normalized such that the gain magnitude is -3 dB at the angular frequency `Wn`. The default is 'phase'. sampling_rate : None, number The sampling rate in Hz. Only required if signal is None. The default is None. Returns ------- signal : Signal The filtered signal. Only returned if ``sampling_rate = None``. filter : FilterSOS SOS Filter object. Only returned if ``signal = None``. """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be none.') # sampling frequency in Hz fs = signal.sampling_rate if sampling_rate is None else sampling_rate # normalized frequency (half-cycle / per sample) frequency_norm = np.asarray(frequency) / fs * 2 # get filter coefficients sos = spsignal.bessel(N, frequency_norm, btype, analog=False, output='sos', norm=norm) # generate filter object filt = pf.FilterSOS(sos, fs) filt.comment = (f"Bessel/Thomson {btype} of order {N} and '{norm}' " f"normalization. Cut-off frequency {frequency} Hz.") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal signal_filt = filt.process(signal) return signal_filt
[docs] def crossover(signal, N, frequency, sampling_rate=None): """ Create and apply Linkwitz-Riley crossover network. Linkwitz-Riley crossover filters ([#]_, [#]_) are designed by cascading Butterworth filters of order `N/2`. where `N` must be even. Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. N : int The order of the Linkwitz-Riley crossover network, must be even. frequency : number, array-like Characteristic frequencies of the crossover network. If a single number is passed, the network consists of a single lowpass and highpass. If `M` frequencies are passed, the network consists of 1 lowpass, M-1 bandpasses, and 1 highpass. sampling_rate : None, number The sampling rate in Hz. Only required if `signal` is ``None``. The default is ``None``. Returns ------- signal : Signal The filtered signal. Only returned if ``sampling_rate = None``. filter : FilterSOS Filter object. Only returned if ``signal = None``. References ---------- .. [#] S. H. Linkwitz, 'Active crossover networks for noncoincident drivers,' J. Audio Eng. Soc., vol. 24, no. 1, pp. 2–8, Jan. 1976. .. [#] D. Bohn, 'Linkwitz Riley crossovers: A primer,' Rane, RaneNote 160, 2005. """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be none.') if N % 2: raise ValueError("The order 'N' must be an even number.") # sampling frequency in Hz fs = signal.sampling_rate if sampling_rate is None else sampling_rate # order of Butterworth filters N = int(N/2) # normalized frequency (half-cycle / per sample) freq = np.atleast_1d(np.asarray(frequency)) / fs * 2 # init neutral SOS matrix of shape (freq.size+1, SOS_dim_2, 6) n_sos = int(np.ceil(N / 2)) # number of lowpass sos SOS_dim_2 = n_sos if freq.size == 1 else 2 * n_sos SOS = np.tile(np.array([1, 0, 0, 1, 0, 0], dtype='float64'), (freq.size + 1, SOS_dim_2, 1)) # get filter coefficients for lowpass sos = spsignal.butter(N, freq[0], 'lowpass', analog=False, output='sos') SOS[0, 0:n_sos] = sos # get filter coefficients for the bandpass if more than one frequency is # provided for n in range(1, freq.size): sos_high = spsignal.butter( N, freq[n-1], 'highpass', analog=False, output='sos') sos_low = spsignal.butter( N, freq[n], 'lowpass', analog=False, output='sos') SOS[n] = np.concatenate((sos_high, sos_low)) # get filter coefficients for the highpass sos = spsignal.butter( N, freq[-1], 'highpass', analog=False, output='sos') SOS[-1, 0:n_sos] = sos # Apply every Butterworth filter twice SOS = np.tile(SOS, (1, 2, 1)) # invert phase in every second channel if the Butterworth order is odd # (realized by reversing b-coefficients of the first sos) if N % 2: SOS[np.arange(1, freq.size + 1, 2), 0, 0:3] *= -1 # generate filter object filt = pf.FilterSOS(SOS, fs) freq_list = [str(f) for f in np.array(frequency, ndmin=1)] filt.comment = (f"Linkwitz-Riley cross over network of order {N*2} at " f"{', '.join(freq_list)} Hz.") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal signal_filt = filt.process(signal) return signal_filt
[docs] def notch(signal, center_frequency, quality, sampling_rate=None): r""" Create and apply or return a second order IIR notch filter. A notch filter is a band-stop filter with a narrow bandwidth (high quality factor). It rejects a narrow frequency band around the center frequency with a gain of 0 (:math:`-\infty` dB) at the center frequency and leaves the rest of the spectrum little changed with gains close to 1 (0 dB) [#]_. Wrapper for :py:func:`scipy.signal.iirnotch`. Parameters ---------- signal : Signal, None The Signal to be filtered. Pass ``None`` to create the filter without applying it. center_frequency : number Frequency in Hz at which the magnitude response will be 0 (:math:`-\infty` dB). quality : number The quality characterizes notch filter -3 dB bandwidth relative to its center frequency (both in Hz), i.e, ``quality = center_frequency/bandwidth``. sampling_rate : None, number The sampling rate in Hz. Only required if `signal` is ``None``. The default is ``None``. Returns ------- output : Signal, FilterIIR The function returns a filtered version of the input signal if ``sampling_rate = None`` or the filter itself if ``signal = None``. References ---------- .. [#] S. J. Orfanidis, “Introduction To Signal Processing”, Prentice-Hall, 1996 """ # check input if (signal is None and sampling_rate is None) \ or (signal is not None and sampling_rate is not None): raise ValueError('Either signal or sampling_rate must be None.') fs = signal.sampling_rate if sampling_rate is None else sampling_rate # get filter coefficients b, a = spsignal.iirnotch(center_frequency, quality, fs) ba = np.vstack((b, a)) # generate filter object filt = pf.FilterIIR(ba, fs) filt.comment = ("Second order notch filter at " f"{center_frequency} Hz (Quality = {quality}).") # return the filter object if signal is None: # return the filter object return filt else: # return the filtered signal return filt.process(signal)