Skip to content

Commit 93be3a6

Browse files
committed
fix 3D plotting (very slow, discuss with Dan)
1 parent 5046a47 commit 93be3a6

1 file changed

Lines changed: 37 additions & 29 deletions

File tree

tutorials/inverse/80_brainstorm_phantom_elekta.py

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# sphinx_gallery_thumbnail_number = 9
2323

2424
# Authors: Eric Larson <larson.eric.d@gmail.com>
25+
# Carina Forster <carinaforster0611@gmail.com>
2526
#
2627
# License: BSD-3-Clause
2728
# Copyright the MNE-Python contributors.
@@ -39,8 +40,9 @@
3940
from mne.io import read_raw_fif
4041

4142
print(__doc__)
42-
4343
# %%
44+
# Load and prepare the data
45+
# -------------------------------
4446
# The data were collected with an Elekta Neuromag VectorView system
4547
# at 1000 Hz and low-pass filtered at 330 Hz.
4648
# The dataset has recordings at 3 different current amplitudes (20, 200, and 2000 nAm),
@@ -49,13 +51,11 @@
4951
raw_fname = data_path / "kojak_all_200nAm_pp_no_chpi_no_ms_raw.fif"
5052
raw = read_raw_fif(raw_fname)
5153
raw.info["bads"] = ["MEG1933", "MEG2421"] # known bad channels
52-
#
53-
# Here we store the events for one stimulus channel.
5454
events = find_events(raw, "STI201")
5555
# The first 32 events are the dipole phantoms recorded.
5656
# The remaining event IDs are not relevant for this tutorial.
57-
58-
# Next, we epoch the data for each dipole event
57+
# %%
58+
# Here we epoch the data around dipole events
5959
# and baseline correct the epochs from -100 ms to -0.05 ms before stimulus onset.
6060
bmax = -0.05
6161
tmin, tmax = -0.1, 0.8
@@ -66,59 +66,65 @@
6666
# %%
6767
# Dipole fitting on phantom data
6868
# -------------------------------
69-
# We use the epoched data to fit dipoles,
70-
# then compare them to the known dipole locations built into the phantom.
69+
# Next, we fit dipoles based on the epoched phantom recordings and
70+
# compare the estimated to the known dipole locations built into the phantom.
7171

7272
# We use the first dipole event to plot the evoked signal
73-
# and skip first and last epoch as they are corrupted.
73+
# and skip the first and the last epoch as they are corrupted.
74+
7475
epochs["1"][1:-1].average().plot(time_unit="s")
75-
evoked_tmp = epochs["1"][1:-1].average()
76-
# We averaged over 18 simulated events for the first dipole.
76+
77+
# We average over 18 simulated events for the first dipole.
7778
# In this data the phantom was set to produce 20 Hz sinusoidal bursts of current.
7879
# You can see that the burst envelope repeats at approximately 3 Hz.
79-
80+
# %%
8081
# First, we need to determine the timepoint of the peak amplitude.
81-
# To find this timepoint, we compute Global Field Power (GFP)
82+
# To find this timepoint, we compute Global Field Power (GFP),
83+
# which is the standard deviation of all EEG electrode voltages at a
84+
# specific timepoint.
85+
evoked_tmp = epochs["1"][1:-1].average()
8286
gfp = np.std(evoked_tmp.data, axis=0)
8387
times = evoked_tmp.times
8488

85-
# We wamt the peak for the first bursting event
89+
# We restrict the time window to find the peak
90+
# for the first bursting event.
8691
time_mask = (times > 0) & (times <= 0.1)
87-
88-
# Find peaks in that window
8992
peaks, _ = find_peaks(gfp[time_mask])
90-
9193
# Convert to original indices
9294
peak_indices = np.where(time_mask)[0][peaks]
93-
9495
# Select the strongest peak (max GFP)
9596
strongest_peak_idx = peak_indices[np.argmax(gfp[peak_indices])]
9697
t_peak = times[strongest_peak_idx]
9798

9899
print(f"Strongest peak at {t_peak * 1000:.1f} ms")
99-
100+
# %%
100101
# Here we store the evoked data for each dipole at the peak amplitude.
101102
evokeds = []
102103
for ii in event_id:
103104
evoked = epochs[str(ii)][1:-1].average().crop(t_peak, t_peak)
104105
evoked = mne.EvokedArray(np.array(evoked.data), evoked.info, tmin=0.0)
105106
evokeds.append(evoked)
106-
107+
# %%
107108
# Next, we need to compute the noise covariance to capture the sensor noise structure.
108109
# We use the baseline window to estimate covariance.
109110
# You can explore the covariance tutorial for details :ref:`tut-compute-covariance`.
111+
110112
cov = mne.compute_covariance(epochs, tmax=bmax)
111113

112114
del epochs # delete to save memory
113-
115+
# %%
114116
# We use a :ref:`sphere head geometry model <eeg_sphere_model>`
115117
# to fit our phantom head model.
116118
subjects_dir = data_path
117119
fetch_phantom("otaniemi", subjects_dir=subjects_dir)
118120
sphere = mne.make_sphere_model(r0=(0.0, 0.0, 0.0), head_radius=0.08)
119121

122+
# %%
123+
# We finally fit all 32 phantom dipoles and store them
124+
# as well as the residuals in a list.
125+
120126
dip_all, residuals_all = [], []
121-
# Let's finally fit all 32 phantom dipoles.
127+
122128
for evoked in evokeds:
123129
dip, residual = fit_dipole(evoked, cov, sphere, n_jobs=1)
124130
dip_all.append(dip)
@@ -136,14 +142,16 @@
136142
plt.xlabel("Phantom dipole estimation")
137143
plt.ylabel("Goodness of fit (%)")
138144
plt.show()
145+
# %%
139146
# We can see that GOF varies between 50 and more than 95 %
140147
# variance explained for dipoles.
141148
# %%
142-
149+
# Estimated vs true dipole locations
150+
# -------------------------------
143151
# Finally, we compare the estimated to the true dipole locations.
144152
actual_pos, actual_ori = mne.dipole.get_phantom_dipoles()
145153
actual_amp = 200.0 # nAm
146-
# dipole estimations
154+
# estimated dipoles
147155
dip_pos = [dip.pos[0] for dip in dip_all]
148156
dip_ori = [dip.ori[0] for dip in dip_all]
149157
dip_amplitude = [dip.amplitude[0] for dip in dip_all]
@@ -168,14 +176,13 @@
168176
ax2.set_xlabel("Dipole index")
169177
ax2.set_ylabel("Angle error (°)")
170178

171-
# Finally we compare amplitudes by subtracting estimated from true amplitude.
179+
# Here we compare amplitudes by subtracting estimated from true amplitude.
172180
amps = actual_amp - np.array(dip_amplitude) / 1e-9
173181
print(f"mean(abs amplitude error) = {np.mean(np.abs(amps)):0.1f} nAm")
174182
ax3.bar(event_id, amps)
175183
ax3.set_xlabel("Dipole index")
176184
ax3.set_ylabel("Amplitude error (nAm)")
177185
# %%
178-
# TODO: fix dipole vis and format the docs
179186
# The dipole fits closely match the true phantom data,
180187
# achieving sub-centimeter accuracy (mean position error 2.7mm).
181188
#
@@ -203,10 +210,11 @@
203210
fig = mne.viz.plot_dipole_locations(
204211
dipoles=dip_true, mode="arrow", subject=subject, color=(0.0, 0.0, 0.0), fig=fig
205212
)
206-
# Plot the position and the orientation of the estimated dipole in green
207-
fig = mne.viz.plot_dipole_locations(
208-
dipoles=dip, mode="arrow", subject=subject, color=(0.2, 1.0, 0.5), fig=fig
209-
)
213+
for dip in dip_all:
214+
# Plot the position and the orientation of the estimated dipole in green
215+
fig = mne.viz.plot_dipole_locations(
216+
dipoles=dip, mode="arrow", subject=subject, color=(0.2, 1.0, 0.5), fig=fig
217+
)
210218
mne.viz.set_3d_view(figure=fig, azimuth=70, elevation=80, distance=0.5)
211219
# %%
212220
# We can see that the dipoles overlap, have approximately the same magnitude

0 commit comments

Comments
 (0)