Skip to content
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))
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