|
41 | 41 | # %% |
42 | 42 | # Load and prepare the data |
43 | 43 | # ------------------------- |
44 | | - |
| 44 | +# TODO: convert to text from code comment |
45 | 45 | # The data were collected with an Elekta Neuromag VectorView system |
46 | | -# at 1000 Hz, low-pass filtered at 330 Hz and contains recordings |
| 46 | +# at 1000 Hz, low-pass filtered at 330 Hz and contain recordings |
47 | 47 | # at three current amplitudes (20, 200, and 2000 nAm). |
| 48 | + |
48 | 49 | # Here we load the medium-amplitude condition. |
49 | 50 | data_path = bst_phantom_elekta.data_path(verbose=True) |
50 | 51 | raw_fname = data_path / "kojak_all_200nAm_pp_no_chpi_no_ms_raw.fif" |
|
69 | 70 | epochs = mne.Epochs( |
70 | 71 | raw, events, event_id, tmin, tmax, baseline=(None, bmax), preload=False |
71 | 72 | ) |
72 | | - |
73 | | -# Here we plot the evoked response for the first clean dipole |
74 | | -epochs["1"][1:-1].average().plot(time_unit="s") |
| 73 | +# We drop the first and last epoch as they contain artefacts |
| 74 | +epochs_clean = epochs[1:-1 |
| 75 | +# We select the first simulated dipole for visualisation purposes |
| 76 | +epochs_firstdip = epochs_clean["1"] |
| 77 | +# Let's look at the evoked response for the first clean dipole |
| 78 | +# We can see that the phantom was set to produce 20 Hz sinusoidal bursts of current. |
| 79 | +# and the burst envelope repeats at approximately 3 Hz. |
| 80 | +epochs_firstdip.average().plot(time_unit="s") |
| 81 | + |
75 | 82 | # %% |
76 | | -# In this data the phantom was set to produce 20 Hz sinusoidal bursts of current. |
77 | | -# The burst envelope repeats at approximately 3 Hz. |
78 | | -# |
79 | 83 | # Determine peak activation using Global Field Power (GFP) |
80 | 84 | # -------------------------------------------------------- |
81 | 85 |
|
82 | 86 | # GFP is the standard deviation across sensors at each time |
83 | 87 | # point, providing a reference-independent measure of signal strength. |
84 | | -evoked_tmp = epochs["1"][1:-1].average() |
| 88 | +evoked_tmp = epochs_firstdip.average() |
85 | 89 | gfp = np.std(evoked_tmp.data, axis=0) |
86 | 90 | times = evoked_tmp.times |
87 | 91 | # Restrict to first burst window |
|
96 | 100 | # Here we crop the data at the peak amplitude and store the evoked data for each dipole. |
97 | 101 | evokeds = [] |
98 | 102 | for ii in event_id: |
99 | | - evoked = epochs[str(ii)][1:-1].average().crop(t_peak, t_peak) |
| 103 | + evoked = epochs_clean[str(ii)].average().crop(t_peak, t_peak) |
100 | 104 | evoked = mne.EvokedArray(np.array(evoked.data), evoked.info, tmin=0.0) |
101 | 105 | evokeds.append(evoked) |
102 | 106 | # %% |
103 | 107 | # Next, we need to compute the noise covariance to capture the sensor noise structure. |
104 | 108 | # We use the baseline window to estimate covariance. |
105 | 109 | # You can explore the covariance tutorial for details: :ref:`tut-compute-covariance`. |
106 | 110 |
|
107 | | -cov = mne.compute_covariance(epochs, tmax=bmax) |
| 111 | +cov = mne.compute_covariance(epochs_clean, tmax=bmax) |
108 | 112 | del epochs # delete to save memory |
109 | 113 | # %% |
| 114 | +# TODO: explain why this head model is used |
110 | 115 | # We use a :ref:`sphere head geometry model <eeg_sphere_model>` |
111 | 116 | # to fit our phantom head model. |
112 | 117 | subjects_dir = data_path |
|
127 | 132 | # Evaluate goodness of fit |
128 | 133 | # ------------------------ |
129 | 134 |
|
| 135 | +# TODO: explain drop in GOF at regular intervals |
130 | 136 | # The dipole object stores the goodness of fit (GOF) for each dipole. |
131 | 137 | gof = [dip.gof[0] for dip in dip_all] |
132 | 138 | colors = ["#E69F00" if val < 60 else "#0072B2" for val in gof] |
|
136 | 142 | plt.show() |
137 | 143 | # |
138 | 144 | # %% |
139 | | -# We can see that GOF varies between simulated dipoles from 50 % up to 95 %. |
140 | | -# |
141 | 145 | # Compare estimated and true dipoles |
142 | 146 | # ---------------------------------- |
143 | 147 |
|
| 148 | +# The dipole fits closely match the true phantom data, |
| 149 | +# achieving sub-centimeter accuracy (mean position error 2.7mm). |
| 150 | + |
144 | 151 | # We get the true dipole positions from the phantoms |
145 | 152 | actual_pos, actual_ori = mne.dipole.get_phantom_dipoles() |
146 | | -actual_amp = 200.0 # nAm |
| 153 | +actual_amp = 100.0 # nAm |
147 | 154 |
|
148 | 155 | # estimated dipoles |
149 | 156 | dip_pos = [dip.pos[0] for dip in dip_all] |
|
177 | 184 | ax3.set_xlabel("Dipole index") |
178 | 185 | ax3.set_ylabel("Amplitude error (nAm)") |
179 | 186 | # %% |
180 | | -# The dipole fits closely match the true phantom data, |
181 | | -# achieving sub-centimeter accuracy (mean position error 2.7mm). |
182 | | -# |
183 | | -# Visualise estimated and true dipole fits |
184 | | -# ---------------------------------------- |
| 187 | +# Visualise estimated and true dipole locations |
| 188 | +# --------------------------------------------- |
185 | 189 |
|
| 190 | + |
| 191 | +# We can see that the dipoles overlap, have approximately the same magnitude |
| 192 | +# and point in the same direction. |
| 193 | + |
186 | 194 | actual_amp = np.ones(len(dip)) # fake amp, needed to create Dipole instance |
187 | 195 | actual_gof = np.ones(len(dip)) # fake goodness-of-fit (GOF) |
188 | 196 | # setup dipole objects for true and estimated dipoles |
|
214 | 222 | dipoles=dip_estimated, mode="arrow", subject=subject, color=(0.2, 1.0, 0.5), fig=fig |
215 | 223 | ) |
216 | 224 | mne.viz.set_3d_view(figure=fig, azimuth=70, elevation=80, distance=0.5) |
217 | | -# %% |
218 | | -# We can see that the dipoles overlap, have approximately the same magnitude |
219 | | -# and point in the same direction. |
220 | 225 |
|
221 | 226 | # %% |
222 | 227 | # References |
|
0 commit comments