|
98 | 98 | print(f"Strongest peak at {t_peak * 1000:.1f} ms") |
99 | 99 |
|
100 | 100 | # Here we store the evoked data for each dipole at the peak amplitude. |
101 | | -data = [] |
| 101 | +evokeds = [] |
102 | 102 | for ii in event_id: |
103 | 103 | evoked = epochs[str(ii)][1:-1].average().crop(t_peak, t_peak) |
104 | | - data.append(evoked.data[:, 0]) |
105 | | - |
106 | | -# Finally, we store the evokeds from all dipoles as an evoked, |
107 | | -# treating every dipole as a timepoint. |
108 | | -evoked = mne.EvokedArray(np.array(data).T, evoked.info, tmin=0.0) |
| 104 | + evoked = mne.EvokedArray(np.array(evoked.data), evoked.info, tmin=0.0) |
| 105 | + evokeds.append(evoked) |
109 | 106 |
|
110 | 107 | # Next, we need to compute the noise covariance to capture the sensor noise structure. |
111 | 108 | # We use the baseline window to estimate covariance. |
|
120 | 117 | fetch_phantom("otaniemi", subjects_dir=subjects_dir) |
121 | 118 | sphere = mne.make_sphere_model(r0=(0.0, 0.0, 0.0), head_radius=0.08) |
122 | 119 |
|
| 120 | +dip_all, residuals_all = [], [] |
123 | 121 | # Let's finally fit all 32 phantom dipoles. |
124 | | -dip, residual = fit_dipole(evoked, cov, sphere, n_jobs=1) |
| 122 | +for evoked in evokeds: |
| 123 | + dip, residual = fit_dipole(evoked, cov, sphere, n_jobs=1) |
| 124 | + dip_all.append(dip) |
| 125 | + residuals_all.append(residual) |
125 | 126 | # %% |
126 | 127 | # Let's visualize the explained variance. |
127 | 128 | # The dipole object stores the goodness of fit (GOF) for each dipole. |
128 | | -gof = dip.gof # array of GOF values |
| 129 | +gof = [dip.gof[0] for dip in dip_all] # array of GOF values |
129 | 130 |
|
130 | 131 | # Define colorblind-friendly colors |
131 | 132 | colors = ["#E69F00" if val < 60 else "#0072B2" for val in gof] |
|
142 | 143 | # Finally, we compare the estimated to the true dipole locations. |
143 | 144 | actual_pos, actual_ori = mne.dipole.get_phantom_dipoles() |
144 | 145 | actual_amp = 200.0 # nAm |
| 146 | +# dipole estimations |
| 147 | +dip_pos = [dip.pos[0] for dip in dip_all] |
| 148 | +dip_ori = [dip.ori[0] for dip in dip_all] |
| 149 | +dip_amplitude = [dip.amplitude[0] for dip in dip_all] |
145 | 150 |
|
146 | 151 | fig, (ax1, ax2, ax3) = plt.subplots( |
147 | 152 | nrows=3, ncols=1, figsize=(6, 7), layout="constrained" |
148 | 153 | ) |
149 | 154 |
|
150 | 155 | # Here we calculate the euclidean distance between estimated and true positions. |
151 | 156 | # We multiply by 1000 to convert from meter to millimeter. |
152 | | -diffs = 1000 * np.sqrt(np.sum((dip.pos - actual_pos) ** 2, axis=-1)) |
| 157 | +diffs = 1000 * np.sqrt(np.sum((dip_pos - actual_pos) ** 2, axis=-1)) |
153 | 158 | print(f"mean(position error) = {np.mean(diffs):0.1f} mm") |
154 | 159 | ax1.bar(event_id, diffs) |
155 | 160 | ax1.set_xlabel("Dipole index") |
156 | 161 | ax1.set_ylabel("Loc. error (mm)") |
157 | 162 |
|
158 | 163 | # Next we calculate the angle between estimated and true orientation. |
159 | 164 | # We convert radians to degrees. |
160 | | -angles = np.rad2deg(np.arccos(np.abs(np.sum(dip.ori * actual_ori, axis=1)))) |
| 165 | +angles = np.rad2deg(np.arccos(np.abs(np.sum(dip_ori * actual_ori, axis=1)))) |
161 | 166 | print(f"mean(angle error) = {np.mean(angles):0.1f}°") |
162 | 167 | ax2.bar(event_id, angles) |
163 | 168 | ax2.set_xlabel("Dipole index") |
164 | 169 | ax2.set_ylabel("Angle error (°)") |
165 | 170 |
|
166 | 171 | # Finally we compare amplitudes by subtracting estimated from true amplitude. |
167 | | -amps = actual_amp - dip.amplitude / 1e-9 |
| 172 | +amps = actual_amp - np.array(dip_amplitude) / 1e-9 |
168 | 173 | print(f"mean(abs amplitude error) = {np.mean(np.abs(amps)):0.1f} nAm") |
169 | 174 | ax3.bar(event_id, amps) |
170 | 175 | ax3.set_xlabel("Dipole index") |
|
0 commit comments