Filtering across discontinuities
Symptom. Ringing at file joins, breaks, or buffer edges.
Symptom
Large oscillatory artifacts, or a slow swing that takes seconds to settle, at specific places in a continuous recording: where two runs were concatenated, where a segment was cut out and the remainder joined, at the start and end of the file, and — in real-time systems — at every buffer boundary. Epochs that happen to fall near those places carry the artifact; averages that include them show a slow deflection with no relation to the stimulus.
Cause
At a discontinuity the signal jumps: from the last sample of one run to the first sample of the next, from a value to zero padding at the file edge, or from the DC offset of one recording to that of another. To a filter a jump is a step, and a step response with overshoot or a long settling time produces ringing (for sharp FIR and high-order IIR filters) or a slow exponential swing (for a high-pass with a low cutoff). A zero-phase filter spreads the artifact both before and after the join. The length of the affected region is the length of the filter’s impulse response — many seconds for a 0.1 Hz high-pass.
Detect
- Keep a list of boundary events (MNE annotations of type
BAD_boundaryor equivalent) and inspect the filtered data around each one. - Look at the start and end of every file after filtering; compare with the unfiltered data.
- Compute the impulse-response length of your filter and mark that much data on each side of every boundary as suspect.
- In real-time pipelines, filter a test signal with a known step and measure the settling time of your buffer scheme.
Fix
- Filter each continuous run separately, then concatenate; never filter across a join.
- Where a join is unavoidable, annotate the boundary and exclude epochs that overlap the boundary plus the filter’s impulse-response length.
- Pad sensibly at file edges (MNE reflects the data by default; state the padding method) and discard the padded region’s worth of data from analysis.
- Real-time: carry filter state across buffers (
scipy.signal.sosfiltwithzi, or a streaming implementation) instead of filtering each buffer independently; account for the resulting group delay in the latency budget (L7.3).
Example

ds-eegbci S001, Fp1: the last 6 s of R01 and the first 6 s of R02 concatenated raw, with a −131 µV level step at the join. A 1 Hz high-pass (MNE’s default FIR, 529 taps) applied across the join spreads the step into a deflection of ±1.65 s (half the filter length) on both sides, up to 82 µV; filtering each run separately, which is what mne.concatenate_raws plus Raw.filter do by default through the EDGE annotations, leaves the join clean. Generated by data/scripts/make_figures.py (ODC-By 1.0).