v2.0.0
Loading...
Searching...
No Matches
inv_hpi_fit_data.cpp
Go to the documentation of this file.
1//=============================================================================================================
18
19//=============================================================================================================
20// INCLUDES
21//=============================================================================================================
22
23#include "inv_hpi_fit_data.h"
24#include "inv_hpi_fit.h"
25#include "inv_sensor_set.h"
26
27#include <math/linalg.h>
28
29#include <iostream>
30#include <algorithm>
31
32//=============================================================================================================
33// EIGEN INCLUDES
34//=============================================================================================================
35
36//=============================================================================================================
37// QT INCLUDES
38//=============================================================================================================
39
40#include <qmath.h>
41
42//=============================================================================================================
43// USED NAMESPACES
44//=============================================================================================================
45
46using namespace INVLIB;
47
48//=============================================================================================================
49// DEFINE GLOBAL METHODS
50//=============================================================================================================
51
52//=============================================================================================================
53// DEFINE MEMBER METHODS
54//=============================================================================================================
55
60
61//=============================================================================================================
62
64{
65 // Initialize variables
66 Eigen::RowVectorXd vecCurrentCoil = this->m_coilPos;
67 Eigen::VectorXd vecCurrentData = this->m_sensorData;
68 InvSensorSet currentSensors = this->m_sensors;
69
70 int iDisplay = 0;
71 int iMaxiter = m_iMaxIterations;
72 int iSimplexNumitr = 0;
73
74 this->m_coilPos = fminsearch(vecCurrentCoil,
75 iMaxiter,
76 2 * iMaxiter * vecCurrentCoil.cols(),
77 iDisplay,
78 vecCurrentData,
79 this->m_matProjector,
80 currentSensors,
81 iSimplexNumitr);
82
83 this->m_errorInfo = dipfitError(this->m_coilPos,
84 vecCurrentData,
85 currentSensors,
86 this->m_matProjector);
87
88 this->m_errorInfo.numIterations = iSimplexNumitr;
89}
90
91//=============================================================================================================
92
93Eigen::MatrixXd InvHpiFitData::magnetic_dipole(Eigen::MatrixXd matPos,
94 Eigen::MatrixXd matPnt,
95 Eigen::MatrixXd matOri)
96{
97 double u0 = 1e-7;
98 int iNchan;
99 Eigen::MatrixXd r, r2, r5, x, y, z, mx, my, mz, Tx, Ty, Tz, lf;
100
101 iNchan = matPnt.rows();
102
103 // Shift the magnetometers so that the dipole is in the origin
104 matPnt.array().col(0) -= matPos(0);
105 matPnt.array().col(1) -= matPos(1);
106 matPnt.array().col(2) -= matPos(2);
107
108 r = matPnt.array().square().rowwise().sum().sqrt();
109
110 r2 = r5 = x = y = z = mx = my = mz = Tx = Ty = Tz = lf = Eigen::MatrixXd::Zero(iNchan, 3);
111
112 for (int i = 0; i < iNchan; i++) {
113 r2.row(i).array().fill(pow(r(i), 2));
114 r5.row(i).array().fill(pow(r(i), 5));
115 }
116
117 for (int i = 0; i < iNchan; i++) {
118 x.row(i).array().fill(matPnt(i, 0));
119 y.row(i).array().fill(matPnt(i, 1));
120 z.row(i).array().fill(matPnt(i, 2));
121 }
122
123 mx.col(0).array().fill(1);
124 my.col(1).array().fill(1);
125 mz.col(2).array().fill(1);
126
127 Tx = 3 * x.cwiseProduct(matPnt) - mx.cwiseProduct(r2);
128 Ty = 3 * y.cwiseProduct(matPnt) - my.cwiseProduct(r2);
129 Tz = 3 * z.cwiseProduct(matPnt) - mz.cwiseProduct(r2);
130
131 for (int i = 0; i < iNchan; i++) {
132 lf(i, 0) = Tx.row(i).dot(matOri.row(i));
133 lf(i, 1) = Ty.row(i).dot(matOri.row(i));
134 lf(i, 2) = Tz.row(i).dot(matOri.row(i));
135 }
136
137 for (int i = 0; i < iNchan; i++) {
138 for (int j = 0; j < 3; j++) {
139 lf(i, j) = u0 * lf(i, j) / (4 * M_PI * r5(i, j));
140 }
141 }
142
143 return lf;
144}
145
146//=============================================================================================================
147
148Eigen::MatrixXd InvHpiFitData::compute_leadfield(const Eigen::MatrixXd& matPos, const InvSensorSet& sensors)
149{
150 Eigen::MatrixXd matPnt, matOri, matLf;
151 matPnt = sensors.rmag(); // position of each integrationpoint
152 matOri = sensors.cosmag(); // mOrientation of each coil
153
154 matLf = magnetic_dipole(matPos, matPnt, matOri);
155
156 return matLf;
157}
158
159//=============================================================================================================
160
161DipFitError InvHpiFitData::dipfitError(const Eigen::MatrixXd& matPos,
162 const Eigen::MatrixXd& matData,
163 const InvSensorSet& sensors,
164 const Eigen::MatrixXd& matProjectors)
165{
166 // Variable Declaration
167 struct DipFitError e;
168 Eigen::MatrixXd matLfSensor, matDif;
169 Eigen::MatrixXd matLf(matData.size(), 3);
170 int iNp = sensors.np();
171
172 // calculate lf for all sensorpoints
173 matLfSensor = compute_leadfield(matPos, sensors);
174
175 // apply averaging per coil
176 for (int i = 0; i < sensors.ncoils(); i++) {
177 matLf.row(i) = sensors.w(i) * matLfSensor.block(i * iNp, 0, iNp, matLfSensor.cols());
178 }
179 //matLf = sensors.tra * matLf;
180
181 // Compute lead field for a magnetic dipole in infinite vacuum
182 e.moment = UTILSLIB::Linalg::pinv(matLf) * matData;
183
184 //matDif = matData - matLf * e.moment;
185 matDif = matData - matProjectors * matLf * e.moment;
186
187 e.error = matDif.array().square().sum() / matData.array().square().sum();
188
189 e.numIterations = 0;
190
191 return e;
192}
193
194//=============================================================================================================
195
197{
198 return (a.base_arr < b.base_arr);
199}
200
201//=============================================================================================================
202
203Eigen::MatrixXd InvHpiFitData::fminsearch(const Eigen::MatrixXd& matPos,
204 int iMaxiter,
205 int iMaxfun,
206 [[maybe_unused]] int iDisplay,
207 const Eigen::MatrixXd& matData,
208 const Eigen::MatrixXd& matProjectors,
209 const InvSensorSet& sensors,
210 int& iSimplexNumitr)
211{
212 double tolx, tolf, rho, chi, psi, sigma, func_evals, usual_delta, zero_term_delta, temp1, temp2;
213 std::string header, how;
214 int n, itercount;
215 Eigen::MatrixXd onesn, two2np1, one2n, v, y, v1, tempX1, tempX2, xbar, xr, x, xe, xc, xcc, xin, posCopy;
216 std::vector<double> fv, fv1;
217 std::vector<int> idx;
218
219 DipFitError tempdip, fxr, fxe, fxc, fxcc;
220
221 tolx = tolf = m_fAbortError;
222
223 header = " Iteration Func-count min f(x) Procedure";
224
225 posCopy = matPos;
226
227 n = posCopy.cols();
228
229 // Initialize parameters
230 rho = 1;
231 chi = 2;
232 psi = 0.5;
233 sigma = 0.5;
234 onesn = Eigen::MatrixXd::Ones(1, n);
235 two2np1 = one2n = Eigen::MatrixXd::Zero(1, n);
236
237 for (int i = 0; i < n; i++) {
238 two2np1(i) = 1 + i;
239 one2n(i) = i;
240 }
241
242 v = v1 = Eigen::MatrixXd::Zero(n, n + 1);
243 fv.resize(n + 1);
244 idx.resize(n + 1);
245 fv1.resize(n + 1);
246
247 for (int i = 0; i < n; i++) {
248 v(i, 0) = posCopy(i);
249 }
250
251 tempdip = dipfitError(posCopy, matData, sensors, matProjectors);
252 fv[0] = tempdip.error;
253
254 func_evals = 1;
255 itercount = 0;
256 how = "";
257
258 // Continue setting up the initial simplex.
259 // Following improvement suggested by L.Pfeffer at Stanford
260 usual_delta = 0.05; // 5 percent deltas for non-zero terms
261 zero_term_delta = 0.00025; // Even smaller delta for zero elements of x
262 xin = posCopy.transpose();
263
264 for (int j = 0; j < n; j++) {
265 y = xin;
266
267 if (y(j) != 0) {
268 y(j) = (1 + usual_delta) * y(j);
269 } else {
270 y(j) = zero_term_delta;
271 }
272
273 v.col(j + 1).array() = y;
274 posCopy = y.transpose();
275 tempdip = dipfitError(posCopy, matData, sensors, matProjectors);
276 fv[j + 1] = tempdip.error;
277 }
278
279 // Sort elements of fv
280 std::vector<HPISortStruct> vecSortStruct;
281
282 for (int i = 0; i < static_cast<int>(fv.size()); i++) {
283 HPISortStruct structTemp;
284 structTemp.base_arr = fv[i];
285 structTemp.idx = i;
286 vecSortStruct.push_back(structTemp);
287 }
288
289 std::sort(vecSortStruct.begin(), vecSortStruct.end(), compare);
290
291 for (int i = 0; i < static_cast<int>(vecSortStruct.size()); i++) {
292 idx[i] = vecSortStruct[i].idx;
293 }
294
295 for (int i = 0; i < n + 1; i++) {
296 v1.col(i) = v.col(idx[i]);
297 fv1[i] = fv[idx[i]];
298 }
299
300 v = v1;
301 fv = fv1;
302
303 how = "initial simplex";
304 itercount = itercount + 1;
305 func_evals = n + 1;
306
307 tempX1 = Eigen::MatrixXd::Zero(1, n);
308
309 while ((func_evals < iMaxfun) && (itercount < iMaxiter)) {
310 for (int i = 0; i < n; i++) {
311 tempX1(i) = std::fabs(fv[0] - fv[i + 1]);
312 }
313
314 temp1 = tempX1.maxCoeff();
315
316 tempX2 = Eigen::MatrixXd::Zero(n, n);
317
318 for (int i = 0; i < n; i++) {
319 tempX2.col(i) = v.col(i + 1) - v.col(0);
320 }
321
322 tempX2 = tempX2.array().abs();
323
324 temp2 = tempX2.maxCoeff();
325
326 if ((temp1 <= tolf) && (temp2 <= tolx)) {
327 break;
328 }
329
330 xbar = v.block(0, 0, n, n).rowwise().sum();
331 xbar /= n;
332
333 xr = (1 + rho) * xbar - rho * v.block(0, n, v.rows(), 1);
334
335 x = xr.transpose();
336 //std::cout << "Iteration Count: " << itercount << ":" << x << std::endl;
337
338 fxr = dipfitError(x, matData, sensors, matProjectors);
339
340 func_evals = func_evals + 1;
341
342 if (fxr.error < fv[0]) {
343 // Calculate the expansion point
344 xe = (1 + rho * chi) * xbar - rho * chi * v.col(v.cols() - 1);
345 x = xe.transpose();
346 fxe = dipfitError(x, matData, sensors, matProjectors);
347 func_evals = func_evals + 1;
348
349 if (fxe.error < fxr.error) {
350 v.col(v.cols() - 1) = xe;
351 fv[n] = fxe.error;
352 how = "expand";
353 } else {
354 v.col(v.cols() - 1) = xr;
355 fv[n] = fxr.error;
356 how = "reflect";
357 }
358 } else {
359 if (fxr.error < fv[n - 1]) {
360 v.col(v.cols() - 1) = xr;
361 fv[n] = fxr.error;
362 how = "reflect";
363 } else { // fxr.error >= fv[:,n-1]
364 // Perform contraction
365 if (fxr.error < fv[n]) {
366 // Perform an outside contraction
367 xc = (1 + psi * rho) * xbar - psi * rho * v.col(v.cols() - 1);
368 x = xc.transpose();
369 fxc = dipfitError(x, matData, sensors, matProjectors);
370 func_evals = func_evals + 1;
371
372 if (fxc.error <= fxr.error) {
373 v.col(v.cols() - 1) = xc;
374 fv[n] = fxc.error;
375 how = "contract outside";
376 } else {
377 // perform a shrink
378 how = "shrink";
379 }
380 } else {
381 xcc = (1 - psi) * xbar + psi * v.col(v.cols() - 1);
382 x = xcc.transpose();
383 fxcc = dipfitError(x, matData, sensors, matProjectors);
384 func_evals = func_evals + 1;
385 if (fxcc.error < fv[n]) {
386 v.col(v.cols() - 1) = xcc;
387 fv[n] = fxcc.error;
388 how = "contract inside";
389 } else {
390 // perform a shrink
391 how = "shrink";
392 }
393 }
394
395 if (how.compare("shrink") == 0) {
396 for (int j = 1; j < n + 1; j++) {
397 v.col(j).array() = v.col(0).array() + sigma * (v.col(j).array() - v.col(0).array());
398 x = v.col(j).array().transpose();
399 tempdip = dipfitError(x, matData, sensors, matProjectors);
400 fv[j] = tempdip.error;
401 }
402 }
403 }
404 }
405
406 // Sort elements of fv
407 vecSortStruct.clear();
408
409 for (int i = 0; i < static_cast<int>(fv.size()); i++) {
410 HPISortStruct structTemp;
411 structTemp.base_arr = fv[i];
412 structTemp.idx = i;
413 vecSortStruct.push_back(structTemp);
414 }
415
416 std::sort(vecSortStruct.begin(), vecSortStruct.end(), compare);
417 for (int i = 0; i < static_cast<int>(vecSortStruct.size()); i++) {
418 idx[i] = vecSortStruct[i].idx;
419 }
420
421 for (int i = 0; i < n + 1; i++) {
422 v1.col(i) = v.col(idx[i]);
423 fv1[i] = fv[idx[i]];
424 }
425
426 v = v1;
427 fv = fv1;
428 itercount = itercount + 1;
429 }
430
431 x = v.col(0).transpose();
432
433 // Seok
434 iSimplexNumitr = itercount;
435
436 return x;
437}
#define M_PI
Per-coil magnetic-dipole fitting workspace — Nelder-Mead optimiser plus leadfield computation for HPI...
Compact MEG sensor-geometry container (positions, orientations, integration weights) used by the HPI ...
HPI (Head Position Indicator) fitting — estimates the MEG dewar-to-head transform from coil-current s...
Static linear-algebra helpers: SVD-based conditioning, block-diagonal assembly, sorted index pairs.
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Residual error and moment vector from a single magnetic dipole fit iteration.
Eigen::MatrixXd moment
Helper for sorting HPI coil dipole fits by matching each fit to the nearest expected coil position.
Eigen::RowVectorXd m_sensorData
DipFitError dipfitError(const Eigen::MatrixXd &matPos, const Eigen::MatrixXd &matData, const InvSensorSet &sensors, const Eigen::MatrixXd &matProjectors)
Eigen::MatrixXd magnetic_dipole(Eigen::MatrixXd matPos, Eigen::MatrixXd matPnt, Eigen::MatrixXd matOri)
Eigen::MatrixXd m_matProjector
static bool compare(HPISortStruct a, HPISortStruct b)
Eigen::MatrixXd m_coilPos
Eigen::MatrixXd fminsearch(const Eigen::MatrixXd &matPos, int iMaxiter, int iMaxfun, int iDisplay, const Eigen::MatrixXd &matData, const Eigen::MatrixXd &matProjectors, const InvSensorSet &sensors, int &iSimplexNumitr)
Eigen::MatrixXd compute_leadfield(const Eigen::MatrixXd &matPos, const InvSensorSet &sensors)
Stores MEG sensor geometry (positions, orientations, weights, coil count) for a single sensor type.
Eigen::MatrixXd rmag(int iSensor) const
Eigen::RowVectorXd w(int iSensor) const
Eigen::MatrixXd cosmag(int iSensor) const
static Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > pinv(const Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &a)
Definition linalg.h:399