Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MRG] Filter Report #235

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
24 changes: 19 additions & 5 deletions neurodsp/filt/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ def check_filter_definition(pass_type, f_range):
return f_lo, f_hi


def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
transitions=(-20, -3), verbose=True):
def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range, transitions=(-20, -3),
return_properties=False, verbose=True):
"""Check a filters properties, including pass band and transition band.

Parameters
Expand All @@ -117,13 +117,18 @@ def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
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.
return_properties : bool, optional, default: False
Returns the frequency response, pass band and 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.
properties : dict
The frequency response, pass band and transition band.
Only returned if return_properties is True.

Examples
--------
Expand All @@ -138,8 +143,8 @@ def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
"""

# Import utility functions inside function to avoid circular imports
from neurodsp.filt.utils import (compute_frequency_response,
compute_pass_band, compute_transition_band)
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
Expand All @@ -164,7 +169,8 @@ def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,

# 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])
transition_bw, f_range_trans = compute_transition_band(f_db, db, transitions[0], transitions[1],
return_freqs=True)

# Raise warning if transition bandwidth is too high
if transition_bw > pass_bw:
Expand All @@ -177,6 +183,14 @@ def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
print('Transition bandwidth is {:.1f} Hz.'.format(transition_bw))
ryanhammonds marked this conversation as resolved.
Show resolved Hide resolved
print('Pass/stop bandwidth is {:.1f} Hz.'.format(pass_bw))

# Return the filter properties
if return_properties:

properties = {'f_db': f_db, 'db': db, 'pass_bw': pass_bw, 'transition_bw': transition_bw,
'f_range_trans': f_range_trans}

return passes, properties

return passes


Expand Down
16 changes: 9 additions & 7 deletions neurodsp/filt/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
###################################################################################################
###################################################################################################

def filter_signal(sig, fs, pass_type, f_range, filter_type='fir',
n_cycles=3, n_seconds=None, remove_edges=True, butterworth_order=None,
print_transitions=False, plot_properties=False, return_filter=False):
def filter_signal(sig, fs, pass_type, f_range, filter_type='fir', n_cycles=3, n_seconds=None,
remove_edges=True, butterworth_order=None, print_transitions=False,
plot_properties=False, return_filter=False, save_report=None):
"""Apply a bandpass, bandstop, highpass, or lowpass filter to a neural signal.

Parameters
Expand Down Expand Up @@ -52,6 +52,8 @@ def filter_signal(sig, fs, pass_type, f_range, filter_type='fir',
If True, plot the properties of the filter, including frequency response and/or kernel.
return_filter : bool, optional, default: False
If True, return the filter coefficients.
save_report : str, optional, default: None
Path, including file name, to save a filter report to as a pdf.

Returns
-------
Expand All @@ -73,13 +75,13 @@ def filter_signal(sig, fs, pass_type, f_range, filter_type='fir',

if filter_type.lower() == 'fir':
return filter_signal_fir(sig, fs, pass_type, f_range, n_cycles, n_seconds,
remove_edges, print_transitions,
plot_properties, return_filter)
remove_edges, print_transitions, plot_properties,
return_filter, save_report)
elif filter_type.lower() == 'iir':
_iir_checks(n_seconds, butterworth_order, remove_edges)
return filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
print_transitions, plot_properties,
return_filter)
print_transitions, plot_properties, return_filter,
save_report)
else:
raise ValueError('Filter type not understood.')

Expand Down
26 changes: 22 additions & 4 deletions neurodsp/filt/fir.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@
from neurodsp.utils import remove_nans, restore_nans
from neurodsp.utils.decorators import multidim
from neurodsp.plts.filt import plot_filter_properties
from neurodsp.filt.utils import compute_frequency_response, remove_filter_edges
from neurodsp.filt.utils import compute_frequency_response, remove_filter_edges, save_filt_report
from neurodsp.filt.checks import (check_filter_definition, check_filter_properties,
check_filter_length)

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

def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, remove_edges=True,
print_transitions=False, plot_properties=False, return_filter=False):
print_transitions=False, plot_properties=False, return_filter=False,
save_report=None):
"""Apply an FIR filter to a signal.

Parameters
Expand Down Expand Up @@ -48,6 +49,8 @@ def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, r
If True, plot the properties of the filter, including frequency response and/or kernel.
return_filter : bool, optional, default: False
If True, return the filter coefficients of the FIR filter.
save_report : str, optional, default: None
Path, including file name, to save a filter report to as a pdf.

Returns
-------
Expand Down Expand Up @@ -78,7 +81,8 @@ def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, r
check_filter_length(sig.shape[-1], len(filter_coefs))

# Check filter properties: compute transition bandwidth & run checks
check_filter_properties(filter_coefs, 1, fs, pass_type, f_range, verbose=print_transitions)
_, properties = check_filter_properties(filter_coefs, 1, fs, pass_type, f_range,
return_properties=True, verbose=print_transitions)

# Remove any NaN on the edges of 'sig'
sig, sig_nans = remove_nans(sig)
Expand All @@ -93,11 +97,25 @@ def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, r
# Add NaN back on the edges of 'sig', if there were any at the beginning
sig_filt = restore_nans(sig_filt, sig_nans)

# Unpack filter properties
if plot_properties or save_report is not None:
f_db = properties['f_db']
db = properties['db']

if save_report is not None:
pass_bw = properties['pass_bw']
transition_bw = properties['transition_bw']
f_range_trans = properties['f_range_trans']

# Plot filter properties, if specified
if plot_properties:
f_db, db = compute_frequency_response(filter_coefs, 1, fs)
plot_filter_properties(f_db, db, fs, filter_coefs)

# Save a pdf filter report containing plots and parameters
if save_report is not None:
save_filt_report(save_report, pass_type, 'IIR', fs, f_db, db, pass_bw, transition_bw,
f_range, f_range_trans, len(f_db)-1, filter_coefs=filter_coefs)

if return_filter:
return sig_filt, filter_coefs
else:
Expand Down
28 changes: 23 additions & 5 deletions neurodsp/filt/iir.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
from scipy.signal import butter, sosfiltfilt

from neurodsp.utils import remove_nans, restore_nans
from neurodsp.filt.utils import compute_nyquist, compute_frequency_response
from neurodsp.filt.utils import compute_nyquist, compute_frequency_response, save_filt_report
from neurodsp.filt.checks import check_filter_definition, check_filter_properties
from neurodsp.plts.filt import plot_frequency_response

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

def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
print_transitions=False, plot_properties=False, return_filter=False):
def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order, print_transitions=False,
plot_properties=False, return_filter=False, save_report=None):
"""Apply an IIR filter to a signal.

Parameters
Expand Down Expand Up @@ -41,6 +41,8 @@ def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
If True, plot the properties of the filter, including frequency response and/or kernel.
return_filter : bool, optional, default: False
If True, return the second order series coefficients of the IIR filter.
save_report : str, optional, default: None
Path, including file name, to save a filter report to as a pdf.

Returns
-------
Expand All @@ -65,7 +67,8 @@ def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
sos = design_iir_filter(fs, pass_type, f_range, butterworth_order)

# Check filter properties: compute transition bandwidth & run checks
check_filter_properties(sos, None, fs, pass_type, f_range, verbose=print_transitions)
_, properties = check_filter_properties(sos, None, fs, pass_type, f_range,
return_properties=True, verbose=print_transitions)

# Remove any NaN on the edges of 'sig'
sig, sig_nans = remove_nans(sig)
Expand All @@ -76,11 +79,26 @@ def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
# Add NaN back on the edges of 'sig', if there were any at the beginning
sig_filt = restore_nans(sig_filt, sig_nans)

# Unpack filter properties
if plot_properties or save_report is not None:
f_db = properties['f_db']
db = properties['db']

if save_report is not None:
pass_bw = properties['pass_bw']
transition_bw = properties['transition_bw']
f_range_trans = properties['f_range_trans']

# Plot frequency response, if desired
if plot_properties:
f_db, db = compute_frequency_response(sos, None, fs)
plot_frequency_response(f_db, db)

# Save a pdf filter report containing plots and parameters
if save_report is not None:

save_filt_report(save_report, pass_type, 'IIR', fs, f_db, db, pass_bw,
transition_bw, f_range, f_range_trans, butterworth_order)

if return_filter:
return sig_filt, sos
else:
Expand Down
Loading