Skip to content

Commit 019e008

Browse files
committed
clean up
1 parent 244e7ae commit 019e008

File tree

5 files changed

+59
-99
lines changed

5 files changed

+59
-99
lines changed

doc/references.bib

+2-2
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ @article{NolteEtAl2004
203203
year = {2004}
204204
}
205205

206-
@INPROCEEDINGS{li_linear_2017,
206+
@INPROCEEDINGS{LiEtAl2017,
207207
author = {Li, Adam and Gunnarsdottir, Kristin M. and Inati, Sara and Zaghloul, Kareem and Gale, John and Bulacio, Juan and Martinez-Gonzalez, Jorge and Sarma, Sridevi V.},
208208
booktitle = {2017 39th Annual International Conference of the IEEE Engineering in Medicine and Biology Society (EMBC)},
209209
title = {Linear time-varying model characterizes invasive EEG signals generated from complex epileptic networks},
@@ -227,7 +227,7 @@ @article{ColcloughEtAl2015
227227
pages = {439--448}
228228
}
229229

230-
@Article{LiAdam2021,
230+
@Article{LiEtAl2021,
231231
author = {Adam Li and Chester Huynh and Zachary Fitzgerald and Iahn Cajigas and Damian Brusko and Jonathan Jagid and Angel O. Claudio and Andres M. Kanner and Jennifer Hopp and Stephanie Chen and Jennifer Haagensen and Emily Johnson and William Anderson and Nathan Crone and Sara Inati and Kareem A. Zaghloul and Juan Bulacio and Jorge Gonzalez-Martinez and Sridevi V. Sarma},
232232
doi = {10.1038/s41593-021-00901-w},
233233
issn = {15461726},

examples/dynamic/mne_var_connectivity.py

+46-90
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,21 @@
66
===================================================
77
88
Compute a VAR (linear system) model from time-series
9-
activity :footcite:`li_linear_2017` using a
10-
continuous iEEG recording.
9+
activity :footcite:`LiEtAl2017`.
1110
1211
In this example, we will demonstrate how to compute
1312
a VAR model with different statistical assumptions.
1413
"""
1514

1615
# Authors: Adam Li <[email protected]>
16+
# Alex Rockhill <[email protected]>
1717
#
1818
# License: BSD (3-clause)
19+
1920
import numpy as np
2021

2122
import mne
22-
from mne import make_fixed_length_epochs
23-
from mne_bids import BIDSPath, read_raw_bids
23+
from mne.datasets import sample
2424

2525
import matplotlib.pyplot as plt
2626

@@ -29,80 +29,31 @@
2929
# %%
3030
# Load the data
3131
# -------------
32-
# Here, we first download an ECoG dataset that was recorded from a patient with
33-
# epilepsy. To facilitate loading the data, we use `mne-bids
34-
# <https://mne.tools/mne-bids/>`_.
32+
# Here, we first download a somatosensory dataset.
3533
#
3634
# Then, we will do some basic filtering and preprocessing using MNE-Python.
3735

38-
# paths to mne datasets - sample ECoG
39-
bids_root = mne.datasets.epilepsy_ecog.data_path()
40-
41-
# first define the BIDS path
42-
bids_path = BIDSPath(root=bids_root, subject='pt1', session='presurgery',
43-
task='ictal', datatype='ieeg', extension='vhdr')
36+
data_path = sample.data_path()
37+
raw_fname = data_path / 'MEG' / 'sample' / \
38+
'sample_audvis_filt-0-40_raw.fif'
39+
event_fname = data_path / 'MEG' / 'sample' / \
40+
'sample_audvis_filt-0-40_raw-eve.fif'
4441

45-
# Then we'll use it to load in the sample dataset. Here we use a format (iEEG)
46-
# that is only available in MNE-BIDS 0.7+, so it will emit a warning on
47-
# versions <= 0.6
48-
raw = read_raw_bids(bids_path=bids_path, verbose=False)
42+
# Setup for reading the raw data
43+
raw = mne.io.read_raw_fif(raw_fname)
44+
events = mne.read_events(event_fname)
4945

50-
line_freq = raw.info['line_freq']
51-
print(f'Data has a power line frequency at {line_freq}.')
46+
# Add a bad channel
47+
raw.info['bads'] += ['MEG 2443']
5248

53-
# Pick only the ECoG channels, removing the ECG channels
54-
raw.pick_types(ecog=True)
49+
# Pick MEG gradiometers
50+
picks = mne.pick_types(raw.info, meg='grad', eeg=False, stim=False, eog=True,
51+
exclude='bads')
5552

56-
# Load the data
57-
raw.load_data()
58-
59-
# Then we remove line frequency interference
60-
raw.notch_filter(line_freq)
61-
62-
# drop bad channels
63-
raw.drop_channels(raw.info['bads'])
64-
65-
# %%
66-
# Crop the data for this example
67-
# ------------------------------
68-
#
69-
# We find the onset time of the seizure and remove all data after that time.
70-
# In this example, we are only interested in analyzing the interictal
71-
# (non-seizure) data period.
72-
#
73-
# One might be interested in analyzing the seizure period also, which we
74-
# leave as an exercise for our readers!
75-
76-
# Find the annotated events
77-
events, event_id = mne.events_from_annotations(raw)
78-
79-
# get sample at which seizure starts
80-
onset_id = event_id['onset']
81-
onset_idx = np.argwhere(events[:, 2] == onset_id)
82-
onset_sample = events[onset_idx, 0].squeeze()
83-
onset_sec = onset_sample / raw.info['sfreq']
84-
85-
# remove all data after the seizure onset
86-
raw = raw.crop(tmin=0, tmax=onset_sec, include_tmax=False)
87-
88-
# %%
89-
# Create Windows of Data (Epochs) Using MNE-Python
90-
# ------------------------------------------------
91-
# We have a continuous iEEG snapshot that is about 60 seconds long
92-
# (after cropping). We would like to estimate a VAR model over a sliding window
93-
# of 500 milliseconds with a 250 millisecond step size.
94-
#
95-
# We can use `mne.make_fixed_length_epochs` to create an Epochs data structure
96-
# representing this sliding window.
97-
98-
epochs = make_fixed_length_epochs(raw=raw, duration=0.5, overlap=0.25)
99-
times = epochs.times
100-
ch_names = epochs.ch_names
101-
102-
print(epochs)
103-
print(epochs.times)
104-
print(epochs.event_id)
105-
print(epochs.events)
53+
# Create epochs for the visual condition
54+
event_id, tmin, tmax = 3, -0.2, 1.5 # need a long enough epoch for 5 cycles
55+
epochs = mne.Epochs(raw, events, event_id, tmin, tmax, picks=picks,
56+
baseline=(None, 0), reject=dict(grad=4000e-13, eog=150e-6))
10657

10758

10859
# %%
@@ -114,7 +65,7 @@
11465
# time-varying linear system.
11566

11667
conn = vector_auto_regression(
117-
data=epochs.get_data(), times=times, names=ch_names)
68+
data=epochs.get_data(), times=epochs.times, names=epochs.ch_names)
11869

11970
# this returns a connectivity structure over time
12071
print(conn)
@@ -149,6 +100,15 @@
149100
).squeeze(0)
150101
rescov = np.cov(sampled_residuals)
151102

103+
# Next, we visualize the covariance of residuals.
104+
# Here we will see that because we use ordinary
105+
# least-squares as an estimation method, the residuals
106+
# should come with low covariances.
107+
fig, ax = plt.subplots()
108+
cax = fig.add_axes([0.27, 0.8, 0.5, 0.05])
109+
im = ax.imshow(rescov, cmap='viridis', aspect='equal', interpolation='none')
110+
fig.colorbar(im, cax=cax, orientation='horizontal')
111+
152112
# %%
153113
# Estimate significant connections using a time-shuffled null distribution
154114
# ------------------------------------------------------------------------
@@ -160,7 +120,7 @@
160120
null_dist = list()
161121
data = epochs.get_data()
162122
rng = np.random.default_rng(99)
163-
for niter in range(20): # 1000 or more would be reasonable for a real analysis
123+
for niter in range(10): # 1000 or more would be reasonable for a real analysis
164124
print(f'Computing null connectivity {niter}')
165125
for epo_idx in range(data.shape[0]):
166126
for ch_idx in range(data.shape[1]):
@@ -170,31 +130,27 @@
170130
[data[epo_idx, ch_idx, start_idx:],
171131
data[epo_idx, ch_idx, :start_idx]])
172132
null_dist.append(vector_auto_regression(
173-
data=data, times=times, names=ch_names).get_data())
133+
data=data, times=epochs.times, names=epochs.ch_names).get_data())
134+
135+
null_dist = np.array(null_dist)
174136

175137
# %%
176138
# Visualize significant connections over time with animation
177139
# ----------------------------------------------------------
178140
# Let's animate over time to visualize the significant connections at each
179-
# epoch. As mentioned in https://openneuro.org/datasets/ds003029 and :footcite:`LiAdam2021`, the ``AST`` channel region was considered epileptic by clinicians for this sample subject and surgically resected. It is interesting to therefore see that there are consistently strong connections from this region to others.
141+
# epoch.
180142

181143
con_data = conn.get_data()
182-
# collapse across epochs since this is inter-ictal/resting state,
183-
# bonferroni correct across epochs
184-
threshes = np.quantile(abs(np.array(null_dist)),
185-
1 - (0.05 / con_data.shape[0]),
144+
145+
# to bonferroni correct across epochs, use the following:
146+
threshes = np.quantile(abs(null_dist), 1 - (0.05 / con_data.shape[0]),
186147
axis=(0, 1))
187-
n_lines = np.sum(abs(con_data) > threshes, axis=(1, 2))
188-
anim, ax = conn.plot_circle(n_lines=n_lines)
189148

190-
# Next, we visualize the covariance of residuals.
191-
# Here we will see that because we use ordinary
192-
# least-squares as an estimation method, the residuals
193-
# should come with low covariances.
194-
fig, ax = plt.subplots()
195-
cax = fig.add_axes([0.27, 0.8, 0.5, 0.05])
196-
im = ax.imshow(rescov, cmap='viridis', aspect='equal', interpolation='none')
197-
fig.colorbar(im, cax=cax, orientation='horizontal')
149+
# now, plot the connectivity as it changes for each epoch
150+
n_lines = np.sum(abs(con_data) > threshes, axis=(1, 2))
151+
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'), figsize=(10, 10))
152+
anim, ax = conn.plot_circle(n_lines=n_lines, fontsize_names=4,
153+
fig=fig, ax=ax)
198154

199155
# %%
200156
# Compute one VAR model using all epochs
@@ -205,7 +161,7 @@
205161
# epochs.
206162

207163
conn = vector_auto_regression(
208-
data=epochs.get_data(), times=times, names=ch_names,
164+
data=epochs.get_data(), times=epochs.times, names=epochs.ch_names,
209165
model='avg-epochs')
210166

211167
# this returns a connectivity structure over time

examples/sensor_connectivity.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
#
1313
# License: BSD (3-clause)
1414

15-
import os.path as op
16-
1715
import mne
1816
from mne_connectivity import spectral_connectivity_epochs
1917
from mne.datasets import sample
@@ -24,10 +22,10 @@
2422
###############################################################################
2523
# Set parameters
2624
data_path = sample.data_path()
27-
raw_fname = op.join(data_path, 'MEG', 'sample',
28-
'sample_audvis_filt-0-40_raw.fif')
29-
event_fname = op.join(data_path, 'MEG', 'sample',
30-
'sample_audvis_filt-0-40_raw-eve.fif')
25+
raw_fname = data_path / 'MEG' / 'sample' / \
26+
'sample_audvis_filt-0-40_raw.fif'
27+
event_fname = data_path / 'MEG' / 'sample' / \
28+
'sample_audvis_filt-0-40_raw-eve.fif'
3129

3230
# Setup for reading the raw data
3331
raw = mne.io.read_raw_fif(raw_fname)

mne_connectivity/viz/circle.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ def update_connectivity(i):
169169
interactive=interactive, node_linewidth=node_linewidth,
170170
show=show)
171171
# circle is size 10
172-
ax.text(3 * np.pi / 4, 25, f't = {i}', color='white',
172+
ax.text(3 * np.pi / 4, 25, f'epoch = {i}', color='white',
173173
clip_on=False)
174174

175175
anim = FuncAnimation(fig, update_connectivity,

mne_connectivity/viz/tests/test_circle.py

+6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import matplotlib.pyplot as plt
1111
from mne.viz import circular_layout
1212

13+
from mne_connectivity import EpochConnectivity
1314
from mne_connectivity.viz import plot_connectivity_circle
1415

1516

@@ -92,4 +93,9 @@ def test_plot_connectivity_circle():
9293
group_boundaries=[-1])
9394
pytest.raises(ValueError, circular_layout, label_names, node_order,
9495
group_boundaries=[20, 0])
96+
97+
# test animation
98+
conn = EpochConnectivity(data=np.repeat(con.flatten()[None], 10, axis=0),
99+
n_nodes=68)
100+
anim, ax = conn.plot_circle(n_lines=3)
95101
plt.close('all')

0 commit comments

Comments
 (0)