Skip to content

Commit bbfabd5

Browse files
committed
add reduce_ringing() function
1 parent c6ca64a commit bbfabd5

11 files changed

Lines changed: 143 additions & 28 deletions

File tree

examples/example_dering.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""
2+
Ringing artifact reduction example
3+
==================================
4+
5+
This example shows how to subtract the impulse response from a filter to
6+
reduce ringing artifacts.
7+
8+
"""
9+
import matplotlib.pyplot as plt
10+
import numpy as np
11+
12+
from scipy.signal import butter, lfilter
13+
14+
from meegkit.detrend import reduce_ringing
15+
16+
# import config # plotting utils
17+
18+
np.random.seed(9)
19+
20+
###############################################################################
21+
# Detrending
22+
# =============================================================================
23+
24+
###############################################################################
25+
# Basic example with a linear trend
26+
# -----------------------------------------------------------------------------
27+
# Simulate the effect of filtering a signal containing a discontinuity, and try
28+
# to remove the resulting ringing artifact by subtracing the opposite of the
29+
# impulse response.
30+
31+
x = np.arange(1000) < 1
32+
[b, a] = butter(6, 0.2) # Butterworth filter design
33+
x = lfilter(b, a, x) * 50 # Filter data using above filter
34+
x = np.roll(x, 500)
35+
x = x[:, None] + np.random.randn(1000, 2)
36+
y = reduce_ringing(x, samples=np.array([500]))
37+
38+
plt.figure()
39+
plt.plot(x + np.array([-10, 10]), 'C0', label='before')
40+
plt.plot(y + np.array([-10, 10]), 'C1:', label='after')
41+
plt.legend()
42+
plt.show()

meegkit/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
"""M/EEG denoising utilities in python."""
22
from . import utils
3-
from . import dss, sns, tspca, star, cca
3+
from . import dss, sns, tspca, star, cca, detrend
4+
5+
__all__ = ['cca', 'detrend', 'dss', 'sns', 'star']

meegkit/cca.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,6 @@ def nt_cca(X=None, Y=None, lags=None, C=None, m=None, thresh=1e-12, sfreq=1):
263263
.. warning:: A, B are scaled so that (X * A)^2 and (Y * B)^2 are identity
264264
matrices (differs from sklearn).
265265
266-
See Also
267-
--------
268-
nt_cov_lags, nt_relshift, nt_cov, nt_pca in NoiseTools.
269-
270266
"""
271267
if (X is None and Y is not None) or (Y is None and X is not None):
272268
raise AttributeError('Either *both* X and Y should be defined, or C!')

meegkit/detrend.py

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
"""Robust detrending."""
22
import numpy as np
33

4+
from scipy.signal import lfilter
5+
46
from .utils import demean, mrdivide, pca, unfold
57
from .utils.matrix import _check_weights
8+
from .utils.sig import stmcb
69

710

811
def detrend(x, order, w=None, basis='polynomials', threshold=3, n_iter=4,
@@ -209,6 +212,66 @@ def regress(x, r, w=None, threshold=1e-7, return_mean=False):
209212
return b, z
210213

211214

215+
def reduce_ringing(X, samples, order=10, n_samples=100, extra=50, threshold=3,
216+
show=False):
217+
"""Subtract filter impulse response from signal at given samples.
218+
219+
Parameters
220+
----------
221+
X: ndarray, shape=(n_times, n_chans[, n_trials])
222+
Data containing ringing artifacts.
223+
samples : list of ints
224+
Sample indices where to find ringing artifacts.
225+
order : int
226+
Order of polynomial trend (default=10).
227+
n_samples = 100
228+
Number of samples over which to estimate impulse response
229+
(default=100).
230+
extra : int
231+
Samples before stimulus to anchor trend (default=50).
232+
threshold: float
233+
Threshold for robust detrending (default=3).
234+
235+
Returns
236+
-------
237+
y : ndarray, shape=(n_times, n_chans[, n_trials])
238+
Clean data.
239+
240+
"""
241+
NNUM = 8
242+
NDEN = 8 # number of filter coeffs
243+
244+
# remove samples too close to beginning or end
245+
samples = samples[samples > extra]
246+
samples = samples[samples < X.shape[0] - n_samples]
247+
248+
y = X.copy()
249+
for i, s in enumerate(samples):
250+
for c in range(X.shape[1]):
251+
# select portion to fit filter response, remove polynomial trend
252+
response = X[s - extra:s + n_samples, c]
253+
# response = detrend(response, order, threshold)
254+
response = response[extra:]
255+
256+
# estimate filter parameters - helps ensure stable filter
257+
response = np.r_[(response, np.zeros(response.shape))]
258+
[B, A] = stmcb(response, q=NNUM, p=NDEN, niter=20)
259+
260+
# estimate filter response to event
261+
pulse = np.arange(n_samples) < 1
262+
model = lfilter(B, A, pulse)
263+
idx = s + np.arange(model.shape[0])
264+
y[idx, c] = X[idx, c] - model
265+
266+
if show:
267+
w = np.zeros((X.shape[0], X.shape[1]))
268+
for s in samples:
269+
w[s:s+n_samples, :] = 1
270+
_plot_detrend(X, y, w)
271+
272+
return y
273+
274+
212275
def _plot_detrend(x, y, w):
213276
"""Plot detrending results."""
214277
import matplotlib.pyplot as plt
@@ -228,8 +291,9 @@ def _plot_detrend(x, y, w):
228291
ax1.legend()
229292

230293
ax2 = f.add_subplot(gs[3, 0])
231-
ax2.imshow(w.T, aspect='auto', cmap='Greys')
232-
ax2.set_yticks(np.arange(1, n_chans + 1, 1))
294+
ax2.pcolormesh(w.T, cmap='Greys')
295+
ax2.set_yticks(np.arange(0, n_chans) + 0.5)
296+
ax2.set_yticklabels(['ch{}'.format(i) for i in np.arange(n_chans)])
233297
ax2.set_xlim(0, n_times)
234298
ax2.set_ylabel('ch. weights')
235299
ax2.set_xlabel('samples')

meegkit/dss.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Denoising source separation."""
12
import numpy as np
23
from scipy import linalg
34

meegkit/sns.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Sensor noise suppression."""
12
import numpy as np
23

34
from .tspca import tsr

meegkit/star.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Sparse time-artefact removal."""
12
import numpy as np
23
from scipy.signal import filtfilt
34

meegkit/tspca.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Time-shift PCA."""
12
import numpy as np
23

34
from .utils import (demean, fold, multishift, normcol, pca, regcov, tscov,

meegkit/utils/denoise.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
from __future__ import division, print_function
2-
31
import matplotlib.pyplot as plt
42
import numpy as np
53
from matplotlib import gridspec
6-
from scipy import linalg
4+
from scipy import linalg, signal
75

86
from .matrix import demean, fold, theshapeof, unfold
97

meegkit/utils/sig.py

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -603,19 +603,14 @@ def prony(h, nb, na):
603603
If the largest order specified is greater than the length of H, H is padded
604604
with zeros.
605605
606-
Examples
607-
--------
608-
Fit an IIR model to an impulse response of a lowpass filter.
609-
610-
>>> [b,a] = butter(4,0.2);
611-
>>> impulseResp = impz(b,a); % obtain impulse response
612-
>>> denOrder=4; numOrder=4; % system function of order 4
613-
>>> [Num,Den]=prony(impulseResp,numOrder,denOrder);
614-
>>> subplot(211); % impulse response and input
615-
>>> stem(impz(Num,Den,length(impulseResp)));
616-
>>> title('Impulse Response with Prony Design');
617-
>>> subplot(212);
618-
>>> stem(impulseResp); title('Input Impulse Response');
606+
Parameters
607+
----------
608+
h : array
609+
Impulse response.
610+
nb : int
611+
Numerator order.
612+
na : int
613+
Denominator order.
619614
620615
References
621616
----------

0 commit comments

Comments
 (0)