Source code for neurodsp.filt.checks

"""Checker functions for filtering."""

from warnings import warn

import numpy as np

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

[docs]def check_filter_definition(pass_type, f_range): """Check a filter definition for validity, and get filter frequency range of the filter. Parameters ---------- pass_type : {'bandpass', 'bandstop', 'lowpass', 'highpass'} Which kind of filter to apply: * 'bandpass': apply a bandpass filter * 'bandstop': apply a bandstop (notch) filter * 'lowpass': apply a lowpass filter * 'highpass' : apply a highpass filter f_range : tuple of (float, float) or float Cutoff frequency(ies) used for filter, specified as f_lo & f_hi. For 'bandpass' & 'bandstop', must be a tuple. For 'lowpass' or 'highpass', can be a float that specifies pass frequency, or can be a tuple and is assumed to be (None, f_hi) for 'lowpass', and (f_lo, None) for 'highpass'. Returns ------- f_lo : float or None The lower frequency range of the filter, specifying the highpass frequency, if specified. f_hi : float or None The higher frequency range of the filter, specifying the lowpass frequency, if specified. Raises ------ ValueError If the filter passtype it not understood, or cutoff frequencies are incompatible. Examples -------- Check a filter definition, and get the cutoff frequencies returned, if the filter is valid: >>> f_hi, f_lo = check_filter_definition(pass_type='bandpass', f_range=(5, 25)) Check a filter definition, for an invalid filter. This example fails since a bandpass filter requires two values for ``f_range``. >>> try: ... f_hi, f_lo = check_filter_definition(pass_type='bandpass', f_range=(20)) ... except ValueError: ... print("The filter definition is invalid.") The filter definition is invalid. """ if pass_type not in ['bandpass', 'bandstop', 'lowpass', 'highpass']: raise ValueError('Filter passtype not understood.') # Check that frequency cutoff inputs are appropriate # For band filters, 2 inputs required & second entry must be > first if pass_type in ('bandpass', 'bandstop'): if isinstance(f_range, (tuple, list, np.ndarray)) and f_range[0] >= f_range[1]: raise ValueError('Second cutoff frequency must be greater than first.') elif isinstance(f_range, (int, float, np.number)) or len(f_range) != 2: raise ValueError('Two cutoff frequencies required for bandpass and bandstop filters.') # Map f_range to f_lo and f_hi f_lo, f_hi = f_range # For lowpass and highpass can be tuple or int/float if pass_type == 'lowpass': if isinstance(f_range, (int, float, np.number)): f_hi = f_range elif isinstance(f_range, (tuple, list, np.ndarray)): f_hi = f_range[1] f_lo = None if pass_type == 'highpass': if isinstance(f_range, (int, float, np.number)): f_lo = f_range elif isinstance(f_range, (tuple, list, np.ndarray)): f_lo = f_range[0] f_hi = None # Make sure pass freqs are floats f_lo = float(f_lo) if f_lo else f_lo f_hi = float(f_hi) if f_hi else f_hi return f_lo, f_hi
[docs]def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range, transitions=(-20, -3), verbose=True): """Check a filters properties, including pass band and transition band. Parameters ---------- filter_coefs : 1d or 2d array If 1d, interpreted as the B-value filter coefficients. If 2d, interpreted as the second-order (sos) filter coefficients. a_vals : 1d array or None The A-value filter coefficients for a filter. If second-order filter coefficients are provided in `filter_coefs`, must be None. fs : float Sampling rate, in Hz. pass_type : {'bandpass', 'bandstop', 'lowpass', 'highpass'} Which kind of filter to apply: * 'bandpass': apply a bandpass filter * 'bandstop': apply a bandstop (notch) filter * 'lowpass': apply a lowpass filter * 'highpass' : apply a highpass filter f_range : tuple of (float, float) or float Cutoff frequency(ies) used for filter, specified as f_lo & f_hi. For 'bandpass' & 'bandstop', must be a tuple. For 'lowpass' or 'highpass', can be a float that specifies pass frequency, or can be a tuple and is assumed to be (None, f_hi) for 'lowpass', and (f_lo, None) for 'highpass'. transitions : tuple of (float, float), optional, default: (-20, -3) Cutoffs, in dB, that define the transition band. verbose : bool, optional, default: True Whether to print out transition and pass bands. Returns ------- passes : bool Whether all the checks pass. False if one or more checks fail. Examples -------- Check the properties of an FIR filter: >>> from neurodsp.filt.fir import design_fir_filter >>> fs, pass_type, f_range = 500, 'bandpass', (1, 25) >>> filter_coefs = design_fir_filter(fs, pass_type, f_range) >>> passes = check_filter_properties(filter_coefs, 1, fs, pass_type, f_range) Transition bandwidth is 0.5 Hz. Pass/stop bandwidth is 24.0 Hz. """ # Import utility functions inside function to avoid circular imports from neurodsp.filt.utils import (compute_frequency_response, compute_pass_band, compute_transition_band) # Initialize variable to keep track if all checks pass passes = True # Compute the frequency response f_db, db = compute_frequency_response(filter_coefs, a_vals, fs) # Check that frequency response goes below transition level (has significant attenuation) if np.min(db) >= transitions[0]: passes = False warn('The filter attenuation never goes below {} dB. '\ 'Increase filter length.'.format(transitions[0])) # If there is no attenuation, cannot calculate bands, so return here return passes # Check that both sides of a bandpass have significant attenuation if pass_type == 'bandpass': if db[0] >= transitions[0] or db[-1] >= transitions[0]: passes = False warn('The low or high frequency stopband never gets attenuated by '\ 'more than {} dB. Increase filter length.'.format(abs(transitions[0]))) # Compute pass & transition bandwidth pass_bw = compute_pass_band(fs, pass_type, f_range) transition_bw = compute_transition_band(f_db, db, transitions[0], transitions[1]) # Raise warning if transition bandwidth is too high if transition_bw > pass_bw: passes = False warn('Transition bandwidth is {:.1f} Hz. This is greater than the desired '\ 'pass/stop bandwidth of {:.1f} Hz'.format(transition_bw, pass_bw)) # Print out transition bandwidth and pass bandwidth to the user if verbose: print('Transition bandwidth is {:.1f} Hz.'.format(transition_bw)) print('Pass/stop bandwidth is {:.1f} Hz.'.format(pass_bw)) return passes
def check_filter_length(sig_length, filt_len): """Check the length of a filter against the length of a signal. Parameters ---------- sig_length : int The length of the signal to be filtered. filt_len : int The length of the filter. Raises ------ ValueError If the signal length is too short for the filter length. """ if filt_len >= sig_length: raise ValueError( 'The designed filter (length: {:d}) is longer than the signal '\ '(length: {:d}). The filter needs to be shortened by decreasing '\ 'the n_cycles or n_seconds parameter. However, this will decrease '\ 'the frequency resolution of the filter.'.format(filt_len, sig_length))