|
43 | 43 | # ------------------------- |
44 | 44 |
|
45 | 45 | # The data were collected with an Elekta Neuromag VectorView system |
46 | | -# at 1000 Hz and low-pass filtered at 330 Hz. |
47 | | -# |
48 | | -# The dataset contains recordings at three current amplitudes (20, 200, and 2000 nAm). |
| 46 | +# at 1000 Hz, low-pass filtered at 330 Hz and contains recordings |
| 47 | +# at three current amplitudes (20, 200, and 2000 nAm). |
49 | 48 | # Here we load the medium-amplitude condition. |
50 | 49 | data_path = bst_phantom_elekta.data_path(verbose=True) |
51 | 50 | raw_fname = data_path / "kojak_all_200nAm_pp_no_chpi_no_ms_raw.fif" |
|
55 | 54 | raw.info["bads"] = ["MEG1933", "MEG2421"] |
56 | 55 |
|
57 | 56 | events = find_events(raw, "STI201") |
58 | | - |
59 | 57 | # The first 32 events correspond to dipole activations. |
60 | 58 |
|
61 | 59 | # %% |
62 | 60 | # Epoch the data and plot evokeds |
63 | 61 | # ------------------------------- |
64 | 62 | # |
65 | | -# We epoch the data around dipole events and apply baseline correction. |
| 63 | +# We epoch and baseline correct the data around the dipole events. |
66 | 64 |
|
67 | 65 | bmax = -0.05 |
68 | 66 | tmin, tmax = -0.1, 0.8 |
|
72 | 70 | raw, events, event_id, tmin, tmax, baseline=(None, bmax), preload=False |
73 | 71 | ) |
74 | 72 |
|
75 | | -# Plot evoked response for the first clean dipole |
| 73 | +# Here we plot the evoked response for the first clean dipole |
76 | 74 | epochs["1"][1:-1].average().plot(time_unit="s") |
77 | 75 | # %% |
78 | 76 | # In this data the phantom was set to produce 20 Hz sinusoidal bursts of current. |
79 | | -# You can see that the burst envelope repeats at approximately 3 Hz. |
| 77 | +# The burst envelope repeats at approximately 3 Hz. |
80 | 78 | # |
81 | 79 | # Determine peak activation using Global Field Power (GFP) |
82 | 80 | # -------------------------------------------------------- |
|
86 | 84 | evoked_tmp = epochs["1"][1:-1].average() |
87 | 85 | gfp = np.std(evoked_tmp.data, axis=0) |
88 | 86 | times = evoked_tmp.times |
89 | | - |
90 | 87 | # Restrict to first burst window |
91 | 88 | time_mask = (times > 0) & (times <= 0.1) |
92 | | - |
93 | 89 | peaks, _ = find_peaks(gfp[time_mask]) |
94 | 90 | peak_indices = np.where(time_mask)[0][peaks] |
95 | | - |
96 | 91 | # Select the strongest peak |
97 | 92 | strongest_peak_idx = peak_indices[np.argmax(gfp[peak_indices])] |
98 | 93 | t_peak = times[strongest_peak_idx] |
99 | | - |
100 | 94 | print(f"Strongest peak at {t_peak * 1000:.1f} ms") |
101 | 95 | # %% |
102 | | -# Here we store the evoked data for each dipole at the peak amplitude. |
| 96 | +# Here we crop the data at the peak amplitude and store the evoked data for each dipole. |
103 | 97 | evokeds = [] |
104 | 98 | for ii in event_id: |
105 | 99 | evoked = epochs[str(ii)][1:-1].average().crop(t_peak, t_peak) |
106 | 100 | evoked = mne.EvokedArray(np.array(evoked.data), evoked.info, tmin=0.0) |
107 | 101 | evokeds.append(evoked) |
108 | 102 | # %% |
109 | 103 | # Next, we need to compute the noise covariance to capture the sensor noise structure. |
110 | | -# |
111 | 104 | # We use the baseline window to estimate covariance. |
112 | | -# |
113 | 105 | # You can explore the covariance tutorial for details: :ref:`tut-compute-covariance`. |
114 | 106 |
|
115 | 107 | cov = mne.compute_covariance(epochs, tmax=bmax) |
116 | | - |
117 | 108 | del epochs # delete to save memory |
118 | 109 | # %% |
119 | 110 | # We use a :ref:`sphere head geometry model <eeg_sphere_model>` |
|
123 | 114 | sphere = mne.make_sphere_model(r0=(0.0, 0.0, 0.0), head_radius=0.08) |
124 | 115 |
|
125 | 116 | # %% |
126 | | -# Fit dipoles |
| 117 | +# Dipole fitting |
127 | 118 | # ----------- |
128 | 119 |
|
129 | | -# We fit dipoles for each phantom and store them in a list. |
130 | | -dip_all, residuals_all = [], [] |
| 120 | +# Finally, we fit dipoles for each phantom and store them in a list. |
| 121 | +dip_all = [] |
131 | 122 |
|
132 | 123 | for evoked in evokeds: |
133 | 124 | dip, residual = fit_dipole(evoked, cov, sphere, n_jobs=1) |
134 | 125 | dip_all.append(dip) |
135 | | - residuals_all.append(residual) |
136 | 126 | # %% |
137 | 127 | # Evaluate goodness of fit |
138 | 128 | # ------------------------ |
|
144 | 134 | plt.xlabel("Phantom dipole estimation") |
145 | 135 | plt.ylabel("Goodness of fit (%)") |
146 | 136 | plt.show() |
147 | | - |
| 137 | +# |
148 | 138 | # %% |
149 | | -# We can see that GOF varies between 50 % and up to 95 %. |
| 139 | +# We can see that GOF varies between simulated dipoles from 50 % up to 95 %. |
150 | 140 | # |
151 | 141 | # Compare estimated and true dipoles |
152 | 142 | # ---------------------------------- |
153 | 143 |
|
| 144 | +# We get the true dipole positions from the phantoms |
154 | 145 | actual_pos, actual_ori = mne.dipole.get_phantom_dipoles() |
155 | 146 | actual_amp = 200.0 # nAm |
156 | 147 |
|
|
189 | 180 | # The dipole fits closely match the true phantom data, |
190 | 181 | # achieving sub-centimeter accuracy (mean position error 2.7mm). |
191 | 182 | # |
192 | | -# Visualise estimated and true dipoles |
| 183 | +# Visualise estimated and true dipole fits |
193 | 184 | # ------------------------------------ |
194 | 185 |
|
195 | 186 | actual_amp = np.ones(len(dip)) # fake amp, needed to create Dipole instance |
|
0 commit comments