mne-python: Potential problem with causal filters
Description of the problem
While filtering a rather long recording, I came across (to me) unexpected behavior in causal filters: they do not remove the DC offset for me. I compared the behavior to the zero-phase filter and there the results are as expected. (Baseline correction is not an option for my use case.)
I discussed this briefly with @larsoner already, who suggested to test on a synthetic data example:
Steps to reproduce
import mne
import numpy as np
data = np.zeros((100, 10000))
data[:] = np.linspace(-1, 1, 100)[:, np.newaxis]
info = mne.create_info(100, 1000, ch_types='mag')
# causal filter
evoked = mne.EvokedArray(data, info, tmin=0.0)
evoked.filter(0.5, None, phase='minimum')
evoked.plot();
# non-causal filter
evoked = mne.EvokedArray(data, info, tmin=0.0)
evoked.filter(0.5, None)
evoked.plot();
Expected results
The zero-phase non-causal filter behaves as expected:
Actual results
The causal minimum phase filter does not behave as epxected:
This is in line with what I observe on actual MEG data.
Additional information
I am in a fresh conda env and on main with scipy version 1.11.4.
@larsoner predicts this to be a problem with scipy.signal.minimum_phase and that is indeed the difference in our filter.py code between causal and non-causal filters.
I can look into this more - but could use some guidance on how to decide how/where a fix would be necessary (if this is indeed in need of a fix).
About this issue
- Original URL
- State: closed
- Created 7 months ago
- Comments: 15 (15 by maintainers)
Fixed by https://github.com/scipy/scipy/pull/19706, will backport to
mne.fixesonce it landsCan you post the filter design specs for both causal and non-causal filters (including amplitude and phase responses and/or impulse responses)? Maybe there’s something wrong with our design (e.g. filter order too small to achieve the desired properties etc.)? It might also be that our input (e.g. the linear-phase filter) does not fulfill the requirements (e.g. the docs mention an odd number of taps)?
If the only difference is
scipy.signal.minimum_phase, it might also be worth trying to design the filter with only SciPy (it could be a bug in this function). It might also be worth trying to replicate the example in the SciPy docs, and see how it behaves when you add more and more MNE stuff (starting with your artifical toy data maybe).