Problem
FilterBankLayer can now run IIR filtering stably in float64 (thanks for the fix in #1067), but its IIR path still applies only zero-phase forward/backward filtering via filtfilt.
That leaves no way to reproduce source implementations that use causal/forward-only filter-bank preprocessing (scipy.signal.lfilter). In downstream reproducibility work, this forces us to keep Braindecode's default FIR zero-phase bank as the best available approximation for some models, even when the released source uses causal IIR/FIR filtering.
Current Braindecode behavior
In the current dev install I checked (braindecode 1.6.1dev0), FilterBankLayer._apply_iir is:
filtered = filtfilt(
x.double(),
a_coeffs=a_coeffs.double().to(x.device),
b_coeffs=b_coeffs.double().to(x.device),
clamp=False,
)
So even if a user passes filter_parameters={"method": "iir", ...}, the filtering is always zero-phase. There is a phase argument on FilterBankLayer, but it does not provide a causal IIR lfilter mode.
Why this matters for model fidelity
Several filter-bank model sources use forward-only causal filtering before the network sees the data.
FBMSNet
The released FBMSNet source uses 9 non-overlapping 4-Hz bands from 4-40 Hz, Chebyshev type-II IIR coefficients, and applies them with scipy.signal.lfilter:
self.f_trans = 2
self.f_pass = np.arange(4,40,4)
self.f_width = 4
self.gpass = 3
self.gstop = 30
order, wn = cheb2ord(wp, ws, self.gpass, self.gstop)
b, a = signal.cheby2(order, self.gstop, ws, btype='bandpass')
eeg_data_filtered = np.asarray([signal.lfilter(b,a,eeg_data[j,:,:]) for j in range(n_trials)])
Braindecode's FBMSNet already exposes filter_parameters, but there is no causal option to express the source filter bank. In our reproduction, trying a stable zero-phase Cheby2 IIR bank regressed substantially relative to the default FIR bank, which is consistent with the phase/filter implementation not matching the source. We therefore keep the default FIR zero-phase bank as an approximation, but it is not source-faithful.
Current Braindecode-side model details relevant to this issue:
model: FBMSNet
filter bank: 9 bands, 4-40 Hz
n_filters_spat: 36
temporal_layer: LogVarLayer
stride_factor: 4
dilatability: 8
kernels_weights: [15, 31, 63, 125]
cnn_max_norm: 2
linear_max_norm: 0.5
FBCNet
The FBCNet/centralRepo source filter-bank helper also defaults to causal filtering:
def __init__(self, filtBank, fs, filtAllowance=2, axis=1, filtType='filter'):
...
# Cheby2 design via cheb2ord/cheby2
if filtType == 'filtfilt':
dataOut = signal.filtfilt(b, a, data, axis=axis)
else:
dataOut = signal.lfilter(b, a, data, axis=axis)
Braindecode's FBCNet also uses FilterBankLayer(..., **filter_parameters), so it has the same expressivity gap.
FBLightConvNet
The released LightConvNet preprocessing builds the same 9-band 4-40 Hz filter bank and calls a helper whose default is also filtType='filter', falling back to signal.lfilter unless filtfilt is explicitly requested.
IFNet
The released IFNet preprocessing uses a two-band filter bank and causal Butterworth filtering:
for bank in config.DATA.FILTER_BANK: # [(4, 16), (16, 40)]
parameter = signal.butter(N=5, Wn=bank, btype='bandpass', fs=config.DATA.FS)
EEG_filtered = signal.lfilter(parameter[0], parameter[1], EEG)
Braindecode's IFNet also delegates filtering to FilterBankLayer, so source-faithful IFNet configs need the same causal filtering support.
Requested enhancement
Add a causal filtering mode to FilterBankLayer that can reproduce source implementations using scipy.signal.lfilter semantics, for example one of:
- support
phase="forward" or phase="causal" for method="iir" and apply a one-pass IIR recursion;
- add an explicit
filter_mode={"zero_phase", "causal"} / causal=True option;
- expose a separate causal filter-bank module if that is cleaner for Torch/torchaudio constraints.
For source fidelity, it would also help if this worked for both IIR and FIR coefficient paths, because the upstream source helpers often expose both filter and filtfilt modes.
Suggested test shape
A minimal regression test could compare FilterBankLayer(..., method="iir", phase="causal") against scipy.signal.lfilter(b, a, x, axis=-1) on a fixed small tensor and fixed coefficients, while preserving the existing zero-phase behavior by default.
Related
This is separate from #1067. #1067 fixed numerical stability for the current zero-phase IIR path. This issue is about expressivity: source-faithful causal/forward-only filter banks cannot currently be represented.
Problem
FilterBankLayercan now run IIR filtering stably in float64 (thanks for the fix in #1067), but its IIR path still applies only zero-phase forward/backward filtering viafiltfilt.That leaves no way to reproduce source implementations that use causal/forward-only filter-bank preprocessing (
scipy.signal.lfilter). In downstream reproducibility work, this forces us to keep Braindecode's default FIR zero-phase bank as the best available approximation for some models, even when the released source uses causal IIR/FIR filtering.Current Braindecode behavior
In the current dev install I checked (
braindecode 1.6.1dev0),FilterBankLayer._apply_iiris:So even if a user passes
filter_parameters={"method": "iir", ...}, the filtering is always zero-phase. There is aphaseargument onFilterBankLayer, but it does not provide a causal IIRlfiltermode.Why this matters for model fidelity
Several filter-bank model sources use forward-only causal filtering before the network sees the data.
FBMSNet
The released FBMSNet source uses 9 non-overlapping 4-Hz bands from 4-40 Hz, Chebyshev type-II IIR coefficients, and applies them with
scipy.signal.lfilter:Braindecode's
FBMSNetalready exposesfilter_parameters, but there is no causal option to express the source filter bank. In our reproduction, trying a stable zero-phase Cheby2 IIR bank regressed substantially relative to the default FIR bank, which is consistent with the phase/filter implementation not matching the source. We therefore keep the default FIR zero-phase bank as an approximation, but it is not source-faithful.Current Braindecode-side model details relevant to this issue:
FBCNet
The FBCNet/centralRepo source filter-bank helper also defaults to causal filtering:
Braindecode's
FBCNetalso usesFilterBankLayer(..., **filter_parameters), so it has the same expressivity gap.FBLightConvNet
The released LightConvNet preprocessing builds the same 9-band 4-40 Hz filter bank and calls a helper whose default is also
filtType='filter', falling back tosignal.lfilterunlessfiltfiltis explicitly requested.IFNet
The released IFNet preprocessing uses a two-band filter bank and causal Butterworth filtering:
Braindecode's
IFNetalso delegates filtering toFilterBankLayer, so source-faithful IFNet configs need the same causal filtering support.Requested enhancement
Add a causal filtering mode to
FilterBankLayerthat can reproduce source implementations usingscipy.signal.lfiltersemantics, for example one of:phase="forward"orphase="causal"formethod="iir"and apply a one-pass IIR recursion;filter_mode={"zero_phase", "causal"}/causal=Trueoption;For source fidelity, it would also help if this worked for both IIR and FIR coefficient paths, because the upstream source helpers often expose both
filterandfiltfiltmodes.Suggested test shape
A minimal regression test could compare
FilterBankLayer(..., method="iir", phase="causal")againstscipy.signal.lfilter(b, a, x, axis=-1)on a fixed small tensor and fixed coefficients, while preserving the existing zero-phase behavior by default.Related
This is separate from #1067. #1067 fixed numerical stability for the current zero-phase IIR path. This issue is about expressivity: source-faithful causal/forward-only filter banks cannot currently be represented.