Skip to content

Commit 69c8b6a

Browse files
larsonerdrammock
authored andcommitted
ENH: Add polyphase resampling (mne-tools#12268)
Co-authored-by: Daniel McCloy <[email protected]>
1 parent 390912b commit 69c8b6a

File tree

14 files changed

+409
-269
lines changed

14 files changed

+409
-269
lines changed

doc/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,6 @@ doctest:
7676
"results in _build/doctest/output.txt."
7777

7878
view:
79-
@python -c "import webbrowser; webbrowser.open_new_tab('file://$(PWD)/_build/html/index.html')"
79+
@python -c "import webbrowser; webbrowser.open_new_tab('file://$(PWD)/_build/html/sg_execution_times.html')"
8080

8181
show: view

doc/changes/devel.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Enhancements
3636
~~~~~~~~~~~~
3737
- Speed up export to .edf in :func:`mne.export.export_raw` by using ``edfio`` instead of ``EDFlib-Python`` (:gh:`12218` by :newcontrib:`Florian Hofer`)
3838
- We added type hints for the return values of :func:`mne.read_evokeds` and :func:`mne.io.read_raw`. Development environments like VS Code or PyCharm will now provide more help when using these functions in your code. (:gh:`12250` by `Richard Höchenberger`_ and `Eric Larson`_)
39+
- Add ``method="polyphase"`` to :meth:`mne.io.Raw.resample` and related functions to allow resampling using :func:`scipy.signal.upfirdn` (:gh:`12268` by `Eric Larson`_)
3940

4041
Bugs
4142
~~~~

examples/datasets/spm_faces_dataset_sgskip.py renamed to examples/datasets/spm_faces_dataset.py

Lines changed: 28 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,15 @@
55
From raw data to dSPM on SPM Faces dataset
66
==========================================
77
8-
Runs a full pipeline using MNE-Python:
9-
10-
- artifact removal
11-
- averaging Epochs
12-
- forward model computation
13-
- source reconstruction using dSPM on the contrast : "faces - scrambled"
14-
15-
.. note:: This example does quite a bit of processing, so even on a
16-
fast machine it can take several minutes to complete.
8+
Runs a full pipeline using MNE-Python. This example does quite a bit of processing, so
9+
even on a fast machine it can take several minutes to complete.
1710
"""
1811
# Authors: Alexandre Gramfort <[email protected]>
1912
# Denis Engemann <[email protected]>
2013
#
2114
# License: BSD-3-Clause
2215
# Copyright the MNE-Python contributors.
2316

24-
# %%
25-
26-
# sphinx_gallery_thumbnail_number = 10
27-
28-
import matplotlib.pyplot as plt
29-
3017
import mne
3118
from mne import combine_evoked, io
3219
from mne.datasets import spm_face
@@ -40,114 +27,76 @@
4027
spm_path = data_path / "MEG" / "spm"
4128

4229
# %%
43-
# Load and filter data, set up epochs
30+
# Load data, filter it, and fit ICA.
4431

4532
raw_fname = spm_path / "SPM_CTF_MEG_example_faces1_3D.ds"
46-
4733
raw = io.read_raw_ctf(raw_fname, preload=True) # Take first run
4834
# Here to save memory and time we'll downsample heavily -- this is not
4935
# advised for real data as it can effectively jitter events!
50-
raw.resample(120.0, npad="auto")
51-
52-
picks = mne.pick_types(raw.info, meg=True, exclude="bads")
53-
raw.filter(1, 30, method="fir", fir_design="firwin")
36+
raw.resample(100)
37+
raw.filter(1.0, None) # high-pass
38+
reject = dict(mag=5e-12)
39+
ica = ICA(n_components=0.95, max_iter="auto", random_state=0)
40+
ica.fit(raw, reject=reject)
41+
# compute correlation scores, get bad indices sorted by score
42+
eog_epochs = create_eog_epochs(raw, ch_name="MRT31-2908", reject=reject)
43+
eog_inds, eog_scores = ica.find_bads_eog(eog_epochs, ch_name="MRT31-2908")
44+
ica.plot_scores(eog_scores, eog_inds) # see scores the selection is based on
45+
ica.plot_components(eog_inds) # view topographic sensitivity of components
46+
ica.exclude += eog_inds[:1] # we saw the 2nd ECG component looked too dipolar
47+
ica.plot_overlay(eog_epochs.average()) # inspect artifact removal
5448

49+
# %%
50+
# Epoch data and apply ICA.
5551
events = mne.find_events(raw, stim_channel="UPPT001")
56-
57-
# plot the events to get an idea of the paradigm
58-
mne.viz.plot_events(events, raw.info["sfreq"])
59-
6052
event_ids = {"faces": 1, "scrambled": 2}
61-
6253
tmin, tmax = -0.2, 0.6
63-
baseline = None # no baseline as high-pass is applied
64-
reject = dict(mag=5e-12)
65-
6654
epochs = mne.Epochs(
6755
raw,
6856
events,
6957
event_ids,
7058
tmin,
7159
tmax,
72-
picks=picks,
73-
baseline=baseline,
60+
picks="meg",
61+
baseline=None,
7462
preload=True,
7563
reject=reject,
7664
)
77-
78-
# Fit ICA, find and remove major artifacts
79-
ica = ICA(n_components=0.95, max_iter="auto", random_state=0)
80-
ica.fit(raw, decim=1, reject=reject)
81-
82-
# compute correlation scores, get bad indices sorted by score
83-
eog_epochs = create_eog_epochs(raw, ch_name="MRT31-2908", reject=reject)
84-
eog_inds, eog_scores = ica.find_bads_eog(eog_epochs, ch_name="MRT31-2908")
85-
ica.plot_scores(eog_scores, eog_inds) # see scores the selection is based on
86-
ica.plot_components(eog_inds) # view topographic sensitivity of components
87-
ica.exclude += eog_inds[:1] # we saw the 2nd ECG component looked too dipolar
88-
ica.plot_overlay(eog_epochs.average()) # inspect artifact removal
65+
del raw
8966
ica.apply(epochs) # clean data, default in place
90-
9167
evoked = [epochs[k].average() for k in event_ids]
92-
9368
contrast = combine_evoked(evoked, weights=[-1, 1]) # Faces - scrambled
94-
9569
evoked.append(contrast)
96-
9770
for e in evoked:
9871
e.plot(ylim=dict(mag=[-400, 400]))
9972

100-
plt.show()
101-
102-
# estimate noise covarariance
103-
noise_cov = mne.compute_covariance(epochs, tmax=0, method="shrunk", rank=None)
104-
10573
# %%
106-
# Visualize fields on MEG helmet
107-
108-
# The transformation here was aligned using the dig-montage. It's included in
109-
# the spm_faces dataset and is named SPM_dig_montage.fif.
110-
trans_fname = spm_path / "SPM_CTF_MEG_example_faces1_3D_raw-trans.fif"
111-
112-
maps = mne.make_field_map(
113-
evoked[0], trans_fname, subject="spm", subjects_dir=subjects_dir, n_jobs=None
114-
)
115-
116-
evoked[0].plot_field(maps, time=0.170, time_viewer=False)
117-
118-
# %%
119-
# Look at the whitened evoked daat
74+
# Estimate noise covariance and look at the whitened evoked data
12075

76+
noise_cov = mne.compute_covariance(epochs, tmax=0, method="shrunk", rank=None)
12177
evoked[0].plot_white(noise_cov)
12278

12379
# %%
12480
# Compute forward model
12581

82+
trans_fname = spm_path / "SPM_CTF_MEG_example_faces1_3D_raw-trans.fif"
12683
src = subjects_dir / "spm" / "bem" / "spm-oct-6-src.fif"
12784
bem = subjects_dir / "spm" / "bem" / "spm-5120-5120-5120-bem-sol.fif"
12885
forward = mne.make_forward_solution(contrast.info, trans_fname, src, bem)
12986

13087
# %%
131-
# Compute inverse solution
88+
# Compute inverse solution and plot
89+
90+
# sphinx_gallery_thumbnail_number = 8
13291

13392
snr = 3.0
13493
lambda2 = 1.0 / snr**2
135-
method = "dSPM"
136-
137-
inverse_operator = make_inverse_operator(
138-
contrast.info, forward, noise_cov, loose=0.2, depth=0.8
139-
)
140-
141-
# Compute inverse solution on contrast
142-
stc = apply_inverse(contrast, inverse_operator, lambda2, method, pick_ori=None)
143-
# stc.save('spm_%s_dSPM_inverse' % contrast.comment)
144-
145-
# Plot contrast in 3D with mne.viz.Brain if available
94+
inverse_operator = make_inverse_operator(contrast.info, forward, noise_cov)
95+
stc = apply_inverse(contrast, inverse_operator, lambda2, method="dSPM", pick_ori=None)
14696
brain = stc.plot(
14797
hemi="both",
14898
subjects_dir=subjects_dir,
14999
initial_time=0.170,
150100
views=["ven"],
151101
clim={"kind": "value", "lims": [3.0, 6.0, 9.0]},
152102
)
153-
# brain.save_image('dSPM_map.png')

examples/decoding/receptive_field_mtrf.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
.. _figure 1: https://www.frontiersin.org/articles/10.3389/fnhum.2016.00604/full#F1
1818
.. _figure 2: https://www.frontiersin.org/articles/10.3389/fnhum.2016.00604/full#F2
1919
.. _figure 5: https://www.frontiersin.org/articles/10.3389/fnhum.2016.00604/full#F5
20-
""" # noqa: E501
20+
"""
2121

2222
# Authors: Chris Holdgraf <[email protected]>
2323
# Eric Larson <[email protected]>
@@ -26,9 +26,6 @@
2626
# License: BSD-3-Clause
2727
# Copyright the MNE-Python contributors.
2828

29-
# %%
30-
# sphinx_gallery_thumbnail_number = 3
31-
3229
from os.path import join
3330

3431
import matplotlib.pyplot as plt
@@ -58,8 +55,8 @@
5855
speech = data["envelope"].T
5956
sfreq = float(data["Fs"].item())
6057
sfreq /= decim
61-
speech = mne.filter.resample(speech, down=decim, npad="auto")
62-
raw = mne.filter.resample(raw, down=decim, npad="auto")
58+
speech = mne.filter.resample(speech, down=decim, method="polyphase")
59+
raw = mne.filter.resample(raw, down=decim, method="polyphase")
6360

6461
# Read in channel positions and create our MNE objects from the raw data
6562
montage = mne.channels.make_standard_montage("biosemi128")
@@ -131,6 +128,8 @@
131128
# across the scalp. We will recreate `figure 1`_ and `figure 2`_ from
132129
# :footcite:`CrosseEtAl2016`.
133130

131+
# sphinx_gallery_thumbnail_number = 3
132+
134133
# Print mean coefficients across all time delays / channels (see Fig 1)
135134
time_plot = 0.180 # For highlighting a specific time.
136135
fig, ax = plt.subplots(figsize=(4, 8), layout="constrained")

examples/time_frequency/source_power_spectrum_opm.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,16 @@
5858
raw_erms = dict()
5959
new_sfreq = 60.0 # Nyquist frequency (30 Hz) < line noise freq (50 Hz)
6060
raws["vv"] = mne.io.read_raw_fif(vv_fname, verbose="error") # ignore naming
61-
raws["vv"].load_data().resample(new_sfreq)
61+
raws["vv"].load_data().resample(new_sfreq, method="polyphase")
6262
raws["vv"].info["bads"] = ["MEG2233", "MEG1842"]
6363
raw_erms["vv"] = mne.io.read_raw_fif(vv_erm_fname, verbose="error")
64-
raw_erms["vv"].load_data().resample(new_sfreq)
64+
raw_erms["vv"].load_data().resample(new_sfreq, method="polyphase")
6565
raw_erms["vv"].info["bads"] = ["MEG2233", "MEG1842"]
6666

6767
raws["opm"] = mne.io.read_raw_fif(opm_fname)
68-
raws["opm"].load_data().resample(new_sfreq)
68+
raws["opm"].load_data().resample(new_sfreq, method="polyphase")
6969
raw_erms["opm"] = mne.io.read_raw_fif(opm_erm_fname)
70-
raw_erms["opm"].load_data().resample(new_sfreq)
70+
raw_erms["opm"].load_data().resample(new_sfreq, method="polyphase")
7171
# Make sure our assumptions later hold
7272
assert raws["opm"].info["sfreq"] == raws["vv"].info["sfreq"]
7373

mne/cuda.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,7 @@ def _fft_resample(x, new_len, npads, to_removes, cuda_dict=None, pad="reflect_li
330330
Number of samples to remove after resampling.
331331
cuda_dict : dict
332332
Dictionary constructed using setup_cuda_multiply_repeated().
333-
%(pad)s
333+
%(pad_resample)s
334334
The default is ``'reflect_limited'``.
335335
336336
.. versionadded:: 0.15

0 commit comments

Comments
 (0)