Skip to content

Commit 17593b7

Browse files
committed
filter report added
1 parent 913fe3b commit 17593b7

File tree

5 files changed

+80
-14
lines changed

5 files changed

+80
-14
lines changed

neurodsp/filt/checks.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def check_filter_definition(pass_type, f_range):
9090

9191

9292
def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
93-
transitions=(-20, -3), verbose=True):
93+
transitions=(-20, -3), filt_type=None, verbose=True):
9494
"""Check a filters properties, including pass band and transition band.
9595
9696
Parameters
@@ -164,7 +164,8 @@ def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
164164

165165
# Compute pass & transition bandwidth
166166
pass_bw = compute_pass_band(fs, pass_type, f_range)
167-
transition_bw = compute_transition_band(f_db, db, transitions[0], transitions[1])
167+
transition_bw, f_range_trans = compute_transition_band(f_db, db, transitions[0], transitions[1],
168+
return_freqs=True)
168169

169170
# Raise warning if transition bandwidth is too high
170171
if transition_bw > pass_bw:
@@ -174,8 +175,50 @@ def check_filter_properties(filter_coefs, a_vals, fs, pass_type, f_range,
174175

175176
# Print out transition bandwidth and pass bandwidth to the user
176177
if verbose:
177-
print('Transition bandwidth is {:.1f} Hz.'.format(transition_bw))
178-
print('Pass/stop bandwidth is {:.1f} Hz.'.format(pass_bw))
178+
179+
# Filter type (high-pass, low-pass, band-pass, band-stop, FIR, IIR)
180+
print('Pass Type: {pass_type}'.format(pass_type=pass_type))
181+
182+
# Cutoff frequency (including definition)
183+
cutoff = round(np.min(f_range) + (0.5 * transition_bw), 3)
184+
print('Cutoff (half-amplitude): {cutoff} Hz'.format(cutoff=cutoff))
185+
186+
# Filter order (or length)
187+
print('Filter order: {order}'.format(order=len(f_db)-1))
188+
189+
# Roll-off or transition bandwidth
190+
print('Transition bandwidth: {:.1f} Hz'.format(transition_bw))
191+
print('Pass/stop bandwidth: {:.1f} Hz'.format(pass_bw))
192+
193+
# Passband ripple and stopband attenuation
194+
pb_ripple = np.max(db[:np.where(f_db < f_range_trans[0])[0][-1]])
195+
sb_atten = np.max(db[np.where(f_db > f_range_trans[1])[0][0]:])
196+
print('Passband Ripple: {pb_ripple} db'.format(pb_ripple=pb_ripple))
197+
print('Stopband Attenuation: {sb_atten} db'.format(sb_atten=sb_atten))
198+
199+
# Filter delay (zero-phase, linear-phase, non-linear phase)
200+
if filt_type == 'FIR' and pass_type in ['bandstop', 'lowpass']:
201+
filt_class = 'linear-phase'
202+
elif filt_type == 'FIR' and pass_type in ['bandpass', 'highpass']:
203+
filt_class = 'zero-phase'
204+
elif filt_type == 'IIR':
205+
filt_class = 'non-linear-phase'
206+
else:
207+
filt_class = None
208+
209+
if filt_type is not None:
210+
print('Filter Class: {filt_class}'.format(filt_class=filt_class))
211+
212+
if filt_class == 'linear-phase':
213+
print('Group Delay: {delay}s'.format(delay=(len(f_db)-1) / 2 * fs))
214+
elif filt_class == 'zero-phase':
215+
print('Group Delay: 0s')
216+
217+
# Direction of computation (one-pass forward/reverse, or two-pass forward and reverse)
218+
if filt_type == 'FIR':
219+
print('Direction: one-pass reverse.')
220+
else:
221+
print('Direction: two-pass forward and reverse')
179222

180223
return passes
181224

neurodsp/filt/filter.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010

1111
def filter_signal(sig, fs, pass_type, f_range, filter_type='fir',
1212
n_cycles=3, n_seconds=None, remove_edges=True, butterworth_order=None,
13-
print_transitions=False, plot_properties=False, return_filter=False):
13+
print_transitions=False, plot_properties=False, return_filter=False,
14+
verbose=False):
1415
"""Apply a bandpass, bandstop, highpass, or lowpass filter to a neural signal.
1516
1617
Parameters
@@ -52,6 +53,8 @@ def filter_signal(sig, fs, pass_type, f_range, filter_type='fir',
5253
If True, plot the properties of the filter, including frequency response and/or kernel.
5354
return_filter : bool, optional, default: False
5455
If True, return the filter coefficients.
56+
verbose : bool, optional, default: False
57+
If True, print out detailed filter information.
5558
5659
Returns
5760
-------
@@ -74,12 +77,12 @@ def filter_signal(sig, fs, pass_type, f_range, filter_type='fir',
7477
if filter_type.lower() == 'fir':
7578
return filter_signal_fir(sig, fs, pass_type, f_range, n_cycles, n_seconds,
7679
remove_edges, print_transitions,
77-
plot_properties, return_filter)
80+
plot_properties, return_filter, verbose=verbose)
7881
elif filter_type.lower() == 'iir':
7982
_iir_checks(n_seconds, butterworth_order, remove_edges)
8083
return filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
8184
print_transitions, plot_properties,
82-
return_filter)
85+
return_filter, verbose=verbose)
8386
else:
8487
raise ValueError('Filter type not understood.')
8588

neurodsp/filt/fir.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
###################################################################################################
1515

1616
def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, remove_edges=True,
17-
print_transitions=False, plot_properties=False, return_filter=False):
17+
print_transitions=False, plot_properties=False, return_filter=False,
18+
verbose=False):
1819
"""Apply an FIR filter to a signal.
1920
2021
Parameters
@@ -48,6 +49,8 @@ def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, r
4849
If True, plot the properties of the filter, including frequency response and/or kernel.
4950
return_filter : bool, optional, default: False
5051
If True, return the filter coefficients of the FIR filter.
52+
verbose : bool, optional, default: False
53+
If True, print out detailed filter information.
5154
5255
Returns
5356
-------
@@ -78,7 +81,8 @@ def filter_signal_fir(sig, fs, pass_type, f_range, n_cycles=3, n_seconds=None, r
7881
check_filter_length(sig.shape[-1], len(filter_coefs))
7982

8083
# Check filter properties: compute transition bandwidth & run checks
81-
check_filter_properties(filter_coefs, 1, fs, pass_type, f_range, verbose=print_transitions)
84+
check_filter_properties(filter_coefs, 1, fs, pass_type, f_range, filt_type="FIR",
85+
verbose=np.any([print_transitions, verbose]))
8286

8387
# Remove any NaN on the edges of 'sig'
8488
sig, sig_nans = remove_nans(sig)

neurodsp/filt/iir.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Filter signals with IIR filters."""
22

3+
import numpy as np
34
from scipy.signal import butter, sosfiltfilt
45

56
from neurodsp.utils import remove_nans, restore_nans
@@ -10,8 +11,8 @@
1011
###################################################################################################
1112
###################################################################################################
1213

13-
def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
14-
print_transitions=False, plot_properties=False, return_filter=False):
14+
def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order, print_transitions=False,
15+
plot_properties=False, return_filter=False, verbose=False):
1516
"""Apply an IIR filter to a signal.
1617
1718
Parameters
@@ -41,6 +42,8 @@ def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
4142
If True, plot the properties of the filter, including frequency response and/or kernel.
4243
return_filter : bool, optional, default: False
4344
If True, return the second order series coefficients of the IIR filter.
45+
verbose : bool, optional, default: False
46+
If True, print out detailed filter information.
4447
4548
Returns
4649
-------
@@ -65,7 +68,8 @@ def filter_signal_iir(sig, fs, pass_type, f_range, butterworth_order,
6568
sos = design_iir_filter(fs, pass_type, f_range, butterworth_order)
6669

6770
# Check filter properties: compute transition bandwidth & run checks
68-
check_filter_properties(sos, None, fs, pass_type, f_range, verbose=print_transitions)
71+
check_filter_properties(sos, None, fs, pass_type, f_range, filt_type="IIR",
72+
verbose=np.any([print_transitions, verbose]))
6973

7074
# Remove any NaN on the edges of 'sig'
7175
sig, sig_nans = remove_nans(sig)

neurodsp/filt/utils.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ def compute_pass_band(fs, pass_type, f_range):
136136
return pass_bw
137137

138138

139-
def compute_transition_band(f_db, db, low=-20, high=-3):
139+
def compute_transition_band(f_db, db, low=-20, high=-3, return_freqs=False):
140140
"""Compute transition bandwidth of a filter.
141141
142142
Parameters
@@ -149,11 +149,15 @@ def compute_transition_band(f_db, db, low=-20, high=-3):
149149
The lower limit that defines the transition band, in dB.
150150
high : float, optional, default: -3
151151
The upper limit that defines the transition band, in dB.
152+
return_freqs : bool, optional, default: False
153+
Whether to return a tuple of (lower, upper) frequency bounds for the transition band.
152154
153155
Returns
154156
-------
155157
transition_band : float
156158
The transition bandwidth of the filter.
159+
f_range : tuple of (float, float), optional, default: False
160+
The lower and upper frequencies of the transition band.
157161
158162
Examples
159163
--------
@@ -178,7 +182,15 @@ def compute_transition_band(f_db, db, low=-20, high=-3):
178182
# This gets the indices of transitions to the values in searched for range
179183
inds = np.where(np.diff(np.logical_and(db > low, db < high)))[0]
180184
# This steps through the indices, in pairs, selecting from the vector to select from
181-
transition_band = np.max([(b - a) for a, b in zip(f_db[inds[0::2]], f_db[inds[1::2]])])
185+
transition_pairs = [(a, b) for a, b in zip(f_db[inds[0::2]], f_db[inds[1::2]])]
186+
pair_idx = np.argmax([(tran[1] - tran[0]) for tran in transition_pairs])
187+
f_lo = transition_pairs[pair_idx][0]
188+
f_hi = transition_pairs[pair_idx][1]
189+
transition_band = f_hi - f_lo
190+
191+
if return_freqs:
192+
193+
return transition_band, (f_lo, f_hi)
182194

183195
return transition_band
184196

0 commit comments

Comments
 (0)