v2.0.0
Loading...
Searching...
No Matches
fwd_eeg_sphere_model.cpp
Go to the documentation of this file.
1//=============================================================================================================
17
18//=============================================================================================================
19// INCLUDES
20//=============================================================================================================
21
23
26
27#include <qmath.h>
28
29#include <Eigen/Core>
30
31#include <algorithm>
32#include <vector>
33#include <Eigen/Dense>
34
35//=============================================================================================================
36// USED NAMESPACES
37//=============================================================================================================
38
39using namespace Eigen;
40using namespace UTILSLIB;
41using namespace FWDLIB;
42
43//=============================================================================================================
44// Local constants and helpers
45//=============================================================================================================
46
47namespace {
48constexpr int FAIL = -1;
49constexpr int OK = 0;
50constexpr int MAXTERMS = 1000;
51constexpr double EPS = 1e-10;
52constexpr double SIN_EPS = 1e-3;
53} // anonymous namespace
54
55//=============================================================================================================
56// DEFINE MEMBER METHODS
57//=============================================================================================================
58
60: nterms (0)
61, nfit (0)
62, scale_pos (0)
63{
64 r0.setZero();
65}
66
67//=============================================================================================================
68
70{
71 int k;
72
73 if (!p_FwdEegSphereModel.name.isEmpty())
74 this->name = p_FwdEegSphereModel.name;
75 if (p_FwdEegSphereModel.nlayer() > 0) {
76 for (k = 0; k < p_FwdEegSphereModel.nlayer(); k++)
77 this->layers.push_back(p_FwdEegSphereModel.layers[k]);
78 }
79 this->r0 = p_FwdEegSphereModel.r0;
80 if (p_FwdEegSphereModel.nterms > 0) {
81 this->fn = VectorXd(p_FwdEegSphereModel.nterms);
82 this->nterms = p_FwdEegSphereModel.nterms;
83 for (k = 0; k < p_FwdEegSphereModel.nterms; k++)
84 this->fn[k] = p_FwdEegSphereModel.fn[k];
85 }
86 if (p_FwdEegSphereModel.nfit > 0) {
87 this->mu = VectorXf(p_FwdEegSphereModel.nfit);
88 this->lambda = VectorXf(p_FwdEegSphereModel.nfit);
89 this->nfit = p_FwdEegSphereModel.nfit;
90 for (k = 0; k < p_FwdEegSphereModel.nfit; k++) {
91 this->mu[k] = p_FwdEegSphereModel.mu[k];
92 this->lambda[k] = p_FwdEegSphereModel.lambda[k];
93 }
94 }
95 this->scale_pos = p_FwdEegSphereModel.scale_pos;
96}
97
98//=============================================================================================================
99
101 int nlayer,
102 const VectorXf& rads,
103 const VectorXf& sigmas)
104/*
105 * Produce a new sphere model structure
106 */
107{
108 auto new_model = std::make_unique<FwdEegSphereModel>();
109
110 new_model->name = name;
111
112 for (int k = 0; k < nlayer; k++) {
113 FwdEegSphereLayer layer;
114 layer.rad = layer.rel_rad = rads[k];
115 layer.sigma = sigmas[k];
116 new_model->layers.push_back(layer);
117 }
118 /*
119 * Sort...
120 */
121 std::sort(new_model->layers.begin(), new_model->layers.end(), FwdEegSphereLayer::comp_layers);
122
123 /*
124 * Scale the radiuses
125 */
126 float R = new_model->layers[nlayer-1].rad;
127 float rR = new_model->layers[nlayer-1].rel_rad;
128 for (int k = 0; k < nlayer; k++) {
129 new_model->layers[k].rad = new_model->layers[k].rad/R;
130 new_model->layers[k].rel_rad = new_model->layers[k].rel_rad/rR;
131 }
132 return new_model;
133}
134
135//=============================================================================================================
136
140
141//=============================================================================================================
142
143FwdEegSphereModel::UPtr FwdEegSphereModel::setup_eeg_sphere_model(const QString& eeg_model_file, QString eeg_model_name, float eeg_sphere_rad)
144{
145 if (eeg_model_name.isEmpty())
146 eeg_model_name = QString("Default");
147
149 eeg_models->fwd_list_eeg_sphere_models();
150
151 FwdEegSphereModel::UPtr eeg_model(eeg_models->fwd_select_eeg_sphere_model(eeg_model_name));
152 if (!eeg_model) {
153 return nullptr;
154 }
155
156 if (!eeg_model->fwd_setup_eeg_sphere_model(eeg_sphere_rad,true,3)) {
157 return nullptr;
158 }
159
160 qInfo("Using EEG sphere model \"%s\" with scalp radius %7.1f mm",
161 eeg_model->name.toUtf8().constData(),1000*eeg_sphere_rad);
162 return eeg_model;
163}
164
165//=============================================================================================================
166
168
169{
170 fitUser u = new fitUserRec();
171 u->y.resize(nterms-1);
172 u->resi.resize(nterms-1);
173 u->M = MatrixXd::Zero(nterms-1,nfit-1);
174 u->uu = MatrixXd::Zero(nfit-1,nterms-1);
175 u->vv = MatrixXd::Zero(nfit-1,nfit-1);
176 u->sing.resize(nfit);
177 u->fn.resize(nterms);
178 u->w.resize(nterms);
179 u->nfit = nfit;
180 u->nterms = nterms;
181 return u;
182}
183
184//=============================================================================================================
185// fwd_multi_spherepot.c
187{
188 MatrixXd M,Mn,help,Mm;
189 static MatrixXd mat1;
190 static MatrixXd mat2;
191 static MatrixXd mat3;
192 static VectorXd c1;
193 static VectorXd c2;
194 static VectorXd cr;
195 static VectorXd cr_mult;
196 double div,div_mult;
197 double n1;
198#ifdef TEST
199 double rel1,rel2;
200 double b,c;
201#endif
202 int k;
203
204 if (this->nlayer() == 0 || this->nlayer() == 1)
205 return 1.0;
206 /*
207 * Now follows the tricky case
208 */
209#ifdef TEST
210 if (this->nlayer() == 2) {
211 rel1 = layers[0].sigma/layers[1].sigma;
212 n1 = n + 1.0;
213 div_mult = 2.0*n + 1;
214 b = pow(this->layers[0].rel_rad,div_mult);
215 return div_mult/((n1 + n*rel1) + b*n1*(rel1-1.0));
216 }
217 else if (this->nlayer() == 3) {
218 rel1 = this->layers[0].sigma/this->layers[1].sigma;
219 rel2 = this->layers[1].sigma/this->layers[2].sigma;
220 n1 = n + 1.0;
221 div_mult = 2.0*n + 1.0;
222 b = pow(this->layers[0].rel_rad,div_mult);
223 c = pow(this->layers[1].rel_rad,div_mult);
224 div_mult = div_mult*div_mult;
225 div = (b*n*n1*(rel1-1.0)*(rel2-1.0) + c*(rel1*n + n1)*(rel2*n + n1))/c +
226 n1*(b*(rel1-1.0)*(rel2*n1 + n) + c*(rel1*n + n1)*(rel2-1.0));
227 return div_mult/div;
228 }
229#endif
230 if (n == 1) {
231 /*
232 * Initialize the arrays
233 */
234 c1.resize(this->nlayer()-1);
235 c2.resize(this->nlayer()-1);
236 cr.resize(this->nlayer()-1);
237 cr_mult.resize(this->nlayer()-1);
238 for (k = 0; k < this->nlayer()-1; k++) {
239 c1[k] = this->layers[k].sigma/this->layers[k+1].sigma;
240 c2[k] = c1[k] - 1.0;
241 cr_mult[k] = this->layers[k].rel_rad;
242 cr[k] = cr_mult[k];
243 cr_mult[k] = cr_mult[k]*cr_mult[k];
244 }
245 if (mat1.cols() == 0)
246 mat1 = MatrixXd(2,2);
247 if (mat2.cols() == 0)
248 mat2 = MatrixXd(2,2);
249 if (mat3.cols() == 0)
250 mat3 = MatrixXd(2,2);
251 }
252 /*
253 * Increment the radius coefficients
254 */
255 for (k = 0; k < this->nlayer()-1; k++)
256 cr[k] = cr[k]*cr_mult[k];
257 /*
258 * Multiply the matrices
259 */
260 M = mat1;
261 Mn = mat2;
262 Mm = mat3;
263 M(0,0) = M(1,1) = 1.0;
264 M(0,1) = M(1,0) = 0.0;
265 div = 1.0;
266 div_mult = 2.0*n + 1.0;
267 n1 = n + 1.0;
268
269 for (k = this->nlayer()-2; k >= 0; k--) {
270
271 Mm(0,0) = (n + n1*c1[k]);
272 Mm(0,1) = n1*c2[k]/cr[k];
273 Mm(1,0) = n*c2[k]*cr[k];
274 Mm(1,1) = n1 + n*c1[k];
275
276 Mn(0,0) = Mm(0,0)*M(0,0) + Mm(0,1)*M(1,0);
277 Mn(0,1) = Mm(0,0)*M(0,1) + Mm(0,1)*M(1,1);
278 Mn(1,0) = Mm(1,0)*M(0,0) + Mm(1,1)*M(1,0);
279 Mn(1,1) = Mm(1,0)*M(0,1) + Mm(1,1)*M(1,1);
280 help = M;
281 M = Mn;
282 Mn = help;
283 div = div*div_mult;
284
285 }
286 return n*div/(n*M(1,1) + n1*M(1,0));
287}
288
289//=============================================================================================================
290// fwd_multi_spherepot.c
291void FwdEegSphereModel::next_legen(int n, double x, double &p0, double &p01, double &p1, double &p11)
292/*
293 * Compute the next Legendre polynomials of the
294 * first and second kind using the recursion formulas.
295 *
296 * The routine initializes automatically with the known values
297 * when n = 1
298 */
299{
300 double help0,help1;
301
302 if (n > 1) {
303 help0 = p0;
304 help1 = p1;
305 p0 = ((2*n-1)*x*help0 - (n-1)*(p01))/n;
306 p1 = ((2*n-1)*x*help1 - n*(p11))/(n-1);
307 p01 = help0;
308 p11 = help1;
309 }
310 else if (n == 0) {
311 p0 = 1.0;
312 p1 = 0.0;
313 }
314 else if (n == 1) {
315 p01 = 1.0;
316 p0 = x;
317 p11 = 0.0;
318 p1 = sqrt(1.0-x*x);
319 }
320 return;
321}
322
323//=============================================================================================================
324
325void FwdEegSphereModel::calc_pot_components(double beta, double cgamma, double &Vrp, double &Vtp, const Eigen::VectorXd& fn, int nterms)
326{
327 double Vt = 0.0;
328 double Vr = 0.0;
329 double p0,p01,p1,p11;
330 double betan,multn;
331 int n;
332
333 betan = 1.0;
334 p0 = p01 = p1 = p11 = 0.0;
335 for (n = 1; n <= nterms; n++) {
336 if (betan < EPS) {
337 break;
338 }
339 next_legen (n,cgamma,p0,p01,p1,p11);
340 multn = betan*fn[n-1]; /* The 2*n + 1 factor is included in fn */
341 Vr = Vr + multn*p0;
342 Vt = Vt + multn*p1/n;
343 betan = beta*betan;
344 }
345 Vrp = Vr;
346 Vtp = Vt;
347 return;
348}
349
350//=============================================================================================================
351// fwd_multi_spherepot.c
352int FwdEegSphereModel::fwd_eeg_multi_spherepot(const Eigen::Vector3f& rd_in, const Eigen::Vector3f& Q_in, const Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor>& el, int neeg, Eigen::VectorXf& Vval, void *client)
353/*
354 * Compute the electric potentials in a set of electrodes in spherically
355 * Symmetric head model.
356 *
357 * The code is based on the formulas presented in
358 *
359 * Z. Zhang, A fast method to compute surface potentials
360 * generated by dipoles within multilayer anisotropic spheres,
361 * Phys. Med. Biol., 40, 335 - 349, 1995.
362 *
363 * and
364 *
365 * J.C. Moscher, R.M. Leahy, and P.S. Lewis, Matrix Kernels for
366 * Modeling of EEG and MEG Data, Los Alamos Technical Report,
367 * LA-UR-96-1993, 1996.
368 *
369 * This version does not use the acceleration with help of equivalent sources
370 * in the homogeneous model
371 *
372 */
373{
374 auto* m = static_cast<FwdEegSphereModel*>(client);
375 Eigen::Vector3f rd = rd_in - m->r0;
376 Eigen::Vector3f Q = Q_in;
377 Eigen::Vector3f pos;
378 int k;
379 float pos2,rd_len,pos_len;
380 double beta,cos_gamma,Vr,Vt;
381 Eigen::Vector3f vec1, vec2;
382 float v1,v2;
383 float cos_beta,Qr,Qt,Q2,c;
384 float pi4_inv = 0.25/M_PI;
385 float sigmaM_inv;
386 /*
387 * Precompute the coefficients
388 */
389 if (m->fn.size() == 0 || m->nterms != MAXTERMS) {
390 m->fn.resize(MAXTERMS);
391 m->nterms = MAXTERMS;
392 for (k = 0; k < MAXTERMS; k++)
393 m->fn[k] = (2*k+3)*m->fwd_eeg_get_multi_sphere_model_coeff(k+1);
394 }
395 /*
396 * Move to the sphere coordinates
397 */
398 rd_len = rd.norm();
399 Q2 = Q.dot(Q);
400 /*
401 * Ignore dipoles outside the innermost sphere
402 */
403 if (rd_len >= m->layers[0].rad) {
404 for (k = 0; k < neeg; k++)
405 Vval[k] = 0.0;
406 return OK;
407 }
408 /*
409 * Special case: rd and Q are parallel
410 */
411 c = rd.dot(Q)/(rd_len*sqrt(Q2));
412 if ((1.0-c*c) < SIN_EPS) { /* Almost parallel:
413 * Q is purely radial */
414 Qr = sqrt(Q2);
415 Qt = 0.0;
416 cos_beta = 0.0;
417 v1 = 0.0;
418 vec1.setZero();
419 }
420 else {
421 vec1 = rd.cross(Q);
422 v1 = vec1.norm();
423 cos_beta = 0.0;
424 Qr = Qt = 0.0;
425 }
426 for (k = 0; k < neeg; k++) {
427 pos = el.row(k).transpose() - m->r0;
428 /*
429 * Should the position be scaled or not?
430 */
431 if (m->scale_pos) {
432 pos_len = m->layers[m->nlayer()-1].rad/pos.norm();
433#ifdef DEBUG
434 qInfo("%10.4f %10.4f %10.4f %10.4f",pos_len,1000*pos[0],1000*pos[1],1000*pos[2]);
435#endif
436 pos *= pos_len;
437 }
438 pos2 = pos.dot(pos);
439 pos_len = sqrt(pos2);
440 /*
441 * Calculate the two ingredients for the final result
442 */
443 cos_gamma = pos.dot(rd)/(rd_len*pos_len);
444 beta = rd_len/pos_len;
445 calc_pot_components(beta,cos_gamma,Vr,Vt,m->fn,m->nterms);
446 /*
447 * Then compute the combined result
448 */
449 if (v1 > 0.0) {
450 vec2 = rd.cross(pos);
451 v2 = vec2.norm();
452
453 if (v2 > 0.0)
454 cos_beta = vec1.dot(vec2)/(v1*v2);
455 else
456 cos_beta = 0.0;
457
458 Qr = Q.dot(rd)/rd_len;
459 Qt = sqrt(Q2 - Qr*Qr);
460 }
461 Vval[k] = static_cast<double>(pi4_inv)*(static_cast<double>(Qr)*Vr + static_cast<double>(Qt)*cos_beta*Vt)/pos2;
462 }
463 /*
464 * Scale by the conductivity if we have the layers
465 * defined
466 */
467 if (m->nlayer() > 0) {
468 sigmaM_inv = 1.0/m->layers[m->nlayer()-1].sigma;
469 for (k = 0; k < neeg; k++)
470 Vval[k] = Vval[k]*sigmaM_inv;
471 }
472 return OK;
473}
474
475//=============================================================================================================
476// fwd_multi_spherepot.c
477int FwdEegSphereModel::fwd_eeg_multi_spherepot_coil1(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &els, Eigen::Ref<Eigen::VectorXf> Vval, void *client) /* Client data will be the sphere model definition */
478/*
479 * Calculate the EEG in the sphere model using the fwdCoilSet structure
480 *
481 * This version does not use the acceleration with help of equivalent sources
482 * in the homogeneous model
483 *
484 */
485{
486 VectorXf vval_one;
487 float val;
488 int nvval = 0;
489 int k,c;
490 FwdCoil* el;
491
492 Vval.resize(els.ncoil());
493 for (k = 0; k < els.ncoil(); k++, el++) {
494 el = els.coils[k].get();
495 if (el->coil_class == FWD_COILC_EEG) {
496 if (el->np > nvval) {
497 vval_one.resize(el->np);
498 nvval = el->np;
499 }
500 if (fwd_eeg_multi_spherepot(rd,Q,el->rmag,el->np,vval_one,client) != OK) {
501 return FAIL;
502 }
503 for (c = 0, val = 0.0; c < el->np; c++)
504 val += el->w[c]*vval_one[c];
505 Vval[k] = val;
506 }
507 }
508 return OK;
509}
510
511//=============================================================================================================
512// fwd_multi_spherepot.c
513bool FwdEegSphereModel::fwd_eeg_spherepot_vec(const Eigen::Vector3f& rd_in, const Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor>& el, int neeg, Eigen::MatrixXf& Vval_vec, void *client)
514{
515 auto* m = static_cast<FwdEegSphereModel*>(client);
516 float fact = 0.25f / static_cast<float>(M_PI);
517 Eigen::Vector3f a_vec;
518 float a,a2,a3;
519 float rrd,rd2,rd2_inv,r,r2,ra,rda;
520 float F;
521 float c1,c2,m1,m2;
522 int k,eq;
523 Eigen::Vector3f orig_rd = rd_in - m->r0;
524 Eigen::Vector3f rd;
525 Eigen::Vector3f pos;
526 float pos_len;
527 /*
528 * Initialize the arrays
529 */
530 for (k = 0 ; k < neeg ; k++) {
531 Vval_vec(0,k) = 0.0;
532 Vval_vec(1,k) = 0.0;
533 Vval_vec(2,k) = 0.0;
534 }
535 /*
536 * Ignore dipoles outside the innermost sphere
537 */
538 if (orig_rd.norm() >= m->layers[0].rad)
539 return true;
540 /*
541 * Default to homogeneous model if no model was previously set
542 */
543#ifdef FOO
544 if (nequiv == 0) /* what to do */
545 eeg_set_homog_sphere_model();
546#endif
547 /*
548 * Make a weighted sum over the equivalence parameters
549 */
550 for (eq = 0; eq < m->nfit; eq++) {
551 /*
552 * Scale the dipole position
553 */
554 rd = m->mu[eq] * orig_rd;
555
556 rd2 = rd.dot(rd);
557 rd2_inv = 1.0/rd2;
558
559 /*
560 * Go over all electrodes
561 */
562 for (k = 0; k < neeg ; k++) {
563
564 pos = el.row(k).transpose() - m->r0;
565 /*
566 * Scale location onto the surface of the sphere
567 */
568 if (m->scale_pos) {
569 pos_len = m->layers[m->nlayer()-1].rad/pos.norm();
570 pos *= pos_len;
571 }
572
573 /* Vector from dipole to the field point */
574
575 a_vec = pos - rd;
576
577 /* Compute the dot products needed */
578
579 a2 = a_vec.dot(a_vec); a = sqrt(a2);
580 a3 = 2.0/(a2*a);
581 r2 = pos.dot(pos); r = sqrt(r2);
582 rrd = pos.dot(rd);
583 ra = r2 - rrd;
584 rda = rrd - rd2;
585
586 /* The main ingredients */
587
588 F = a*(r*a + ra);
589 c1 = a3*rda + 1.0/a - 1.0/r;
590 c2 = a3 + (a+r)/(r*F);
591
592 /* Mix them together and scale by lambda/(rd*rd) */
593
594 m1 = (c1 - c2*rrd);
595 m2 = c2*rd2;
596
597 Vval_vec(0,k) = Vval_vec(0,k) + m->lambda[eq]*rd2_inv*(m1*rd[0] + m2*pos[0]);
598 Vval_vec(1,k) = Vval_vec(1,k) + m->lambda[eq]*rd2_inv*(m1*rd[1] + m2*pos[1]);
599 Vval_vec(2,k) = Vval_vec(2,k) + m->lambda[eq]*rd2_inv*(m1*rd[2] + m2*pos[2]);
600 } /* All electrodes done */
601 } /* All equivalent dipoles done */
602 /*
603 * Finish by scaling by 1/(4*M_PI);
604 */
605 for (k = 0; k < neeg; k++) {
606 Vval_vec(0,k) = fact*Vval_vec(0,k);
607 Vval_vec(1,k) = fact*Vval_vec(1,k);
608 Vval_vec(2,k) = fact*Vval_vec(2,k);
609 }
610 return true;
611}
612
613//=============================================================================================================
614// fwd_multi_spherepot.c
615int FwdEegSphereModel::fwd_eeg_spherepot_coil_vec(const Eigen::Vector3f& rd, FwdCoilSet& els, Eigen::Ref<Eigen::MatrixXf> Vval_vec, void *client)
616{
617 Eigen::MatrixXf vval_one;
618 float val;
619 int nvval = 0;
620 int k,c,p;
621 FwdCoil* el;
622
623 for (k = 0; k < els.ncoil(); k++, el++) {
624 el = els.coils[k].get();
625 if (el->coil_class == FWD_COILC_EEG) {
626 if (el->np > nvval) {
627 vval_one.resize(3, el->np);
628 nvval = el->np;
629 }
630 if (!fwd_eeg_spherepot_vec(rd,el->rmag,el->np,vval_one,client)) {
631 return FAIL;
632 }
633 for (p = 0; p < 3; p++) {
634 for (c = 0, val = 0.0; c < el->np; c++)
635 val += el->w[c]*vval_one(p,c);
636 Vval_vec(p,k) = val;
637 }
638 }
639 }
640 return OK;
641}
642
643//=============================================================================================================
644
645int FwdEegSphereModel::fwd_eeg_spherepot_grad_coil(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &coils, Eigen::Ref<Eigen::VectorXf> Vval, Eigen::Ref<Eigen::VectorXf> xgrad, Eigen::Ref<Eigen::VectorXf> ygrad, Eigen::Ref<Eigen::VectorXf> zgrad, void *client) /* Client data to be passed to some foward modelling routines */
646/*
647 * Quick and dirty solution: use differences
648 *
649 * This routine uses the acceleration with help of equivalent sources
650 * in the homogeneous sphere.
651 *
652 */
653{
654 Eigen::Vector3f my_rd;
655 float step = 0.0005;
656 float step2 = 2*step;
657 int p,q;
658
659 Eigen::Ref<Eigen::VectorXf>* grads[3] = { &xgrad, &ygrad, &zgrad };
660
661 for (p = 0; p < 3; p++) {
662 my_rd = rd;
663 my_rd[p] += step;
664 if (fwd_eeg_spherepot_coil(my_rd,Q,coils,*grads[p],client) == FAIL)
665 return FAIL;
666 my_rd = rd;
667 my_rd[p] -= step;
668 if (fwd_eeg_spherepot_coil(my_rd,Q,coils,Vval,client) == FAIL)
669 return FAIL;
670 for (q = 0; q < coils.ncoil(); q++)
671 (*grads[p])[q] = ((*grads[p])[q]-Vval[q])/step2;
672 }
673 if (fwd_eeg_spherepot_coil(rd,Q,coils,Vval,client) == FAIL)
674 return FAIL;
675 return OK;
676}
677
678//=============================================================================================================
679// fwd_multi_spherepot.c
680int FwdEegSphereModel::fwd_eeg_spherepot( const Eigen::Vector3f& rd_in,
681 const Eigen::Vector3f& Q_in,
682 const Eigen::Matrix<float, Eigen::Dynamic, 3, Eigen::RowMajor>& el,
683 int neeg,
684 VectorXf& Vval,
685 void *client)
686/*
687 * This routine calculates the potentials for a specific dipole direction
688 *
689 * This routine uses the acceleration with help of equivalent sources
690 * in the homogeneous sphere.
691 */
692{
693 auto* m = static_cast<FwdEegSphereModel*>(client);
694 float fact = 0.25f/M_PI;
695 Eigen::Vector3f a_vec;
696 float a,a2,a3;
697 float rrd,rd2,rd2_inv,r,r2,ra,rda;
698 float F;
699 float c1,c2,m1,m2,f1,f2;
700 int k,eq;
701 Eigen::Vector3f orig_rd = rd_in - m->r0;
702 Eigen::Vector3f rd;
703 Eigen::Vector3f Q = Q_in;
704 Eigen::Vector3f pos;
705 float pos_len;
706 /*
707 * Initialize the arrays
708 */
709 for (k = 0 ; k < neeg ; k++)
710 Vval[k] = 0.0;
711 /*
712 * Ignore dipoles outside the innermost sphere
713 */
714 if (orig_rd.norm() >= m->layers[0].rad)
715 return true;
716 /*
717 * Default to homogeneous model if no model was previously set
718 */
719#ifdef FOO
720 if (nequiv == 0) /* what to do */
721 eeg_set_homog_sphere_model();
722#endif
723 /*
724 * Make a weighted sum over the equivalence parameters
725 */
726 for (eq = 0; eq < m->nfit; eq++) {
727 /*
728 * Scale the dipole position
729 */
730 rd = m->mu[eq] * orig_rd;
731
732 rd2 = rd.dot(rd);
733 rd2_inv = 1.0/rd2;
734
735 f1 = rd.dot(Q);
736 /*
737 * Go over all electrodes
738 */
739 for (k = 0; k < neeg ; k++) {
740
741 pos = el.row(k).transpose() - m->r0;
742 /*
743 * Scale location onto the surface of the sphere
744 */
745 if (m->scale_pos) {
746 pos_len = m->layers[m->nlayer()-1].rad/pos.norm();
747 pos *= pos_len;
748 }
749
750 /* Vector from dipole to the field point */
751
752 a_vec = pos - rd;
753
754 /* Compute the dot products needed */
755
756 a2 = a_vec.dot(a_vec); a = sqrt(a2);
757 a3 = 2.0/(a2*a);
758 r2 = pos.dot(pos); r = sqrt(r2);
759 rrd = pos.dot(rd);
760 ra = r2 - rrd;
761 rda = rrd - rd2;
762
763 /* The main ingredients */
764
765 F = a*(r*a + ra);
766 c1 = a3*rda + 1.0/a - 1.0/r;
767 c2 = a3 + (a+r)/(r*F);
768
769 /* Mix them together and scale by lambda/(rd*rd) */
770
771 m1 = (c1 - c2*rrd);
772 m2 = c2*rd2;
773
774 f2 = pos.dot(Q);
775 Vval[k] = Vval[k] + m->lambda[eq]*rd2_inv*(m1*f1 + m2*f2);
776 } /* All electrodes done */
777 } /* All equivalent dipoles done */
778 /*
779 * Finish by scaling by 1/(4*M_PI);
780 */
781 for (k = 0; k < neeg; k++)
782 Vval[k] = fact*Vval[k];
783 return OK;
784}
785
786//=============================================================================================================
787// fwd_multi_spherepot.c
788int FwdEegSphereModel::fwd_eeg_spherepot_coil(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& els, Eigen::Ref<Eigen::VectorXf> Vval, void *client)
789{
790 VectorXf vval_one;
791 float val;
792 int nvval = 0;
793 int k,c;
794 FwdCoil* el;
795
796 for (k = 0; k < els.ncoil(); k++, el++) {
797 el = els.coils[k].get();
798 if (el->coil_class == FWD_COILC_EEG) {
799 if (el->np > nvval) {
800 vval_one.resize(el->np);
801 nvval = el->np;
802 }
803 if (fwd_eeg_spherepot(rd,Q,el->rmag,el->np,vval_one,client) != OK) {
804 return FAIL;
805 }
806 for (c = 0, val = 0.0; c < el->np; c++)
807 val += el->w[c]*vval_one[c];
808 Vval[k] = val;
809 }
810 }
811 return OK;
812}
813
814//=============================================================================================================
815// fwd_eeg_sphere_models.c
816bool FwdEegSphereModel::fwd_setup_eeg_sphere_model(float rad, bool fit_berg_scherg, int nfit)
817{
818 int nterms = 200;
819 float rv;
820
821 /*
822 * Scale the relative radiuses
823 */
824 for (int k = 0; k < this->nlayer(); k++)
825 this->layers[k].rad = rad*this->layers[k].rel_rad;
826
827 if (fit_berg_scherg) {
828 if (this->fwd_eeg_fit_berg_scherg(nterms,nfit,rv)) {
829 qInfo("Equiv. model fitting -> RV = %g %%",100*rv);
830 for (int k = 0; k < nfit; k++)
831 qInfo("mu%d = %g\tlambda%d = %g", k+1,this->mu[k],k+1,this->layers[this->nlayer()-1].sigma*this->lambda[k]);
832 }
833 else
834 return false;
835 }
836
837 qInfo("Defined EEG sphere model with rad = %7.2f mm", 1000.0*rad);
838 return true;
839}
840
841static void compute_svd(Eigen::MatrixXd& mat,
842 Eigen::VectorXd& sing,
843 Eigen::MatrixXd& uu,
844 Eigen::MatrixXd* vv)
845/*
846 * Compute the SVD of mat.
847 * Results are stored in sing, uu, and optionally vv.
848 * mat is not modified.
849 */
850{
851 int udim = std::min(static_cast<int>(mat.rows()), static_cast<int>(mat.cols()));
852
853 Eigen::JacobiSVD<Eigen::MatrixXd> svd(mat, Eigen::ComputeFullU | Eigen::ComputeFullV);
854
855 sing = svd.singularValues();
856 uu = svd.matrixU().transpose().topRows(udim);
857
858 if (vv != nullptr)
859 *vv = svd.matrixV().transpose();
860}
861
862/*
863 * Include the simplex and SVD code here.
864 * It is not too much of a problem
865 */
866
867
868namespace FWDLIB
869{
870
875 double lambda;
876 double mu;
877};
878
879} // namespace
880
881static void sort_parameters(VectorXd& mu,VectorXd& lambda,int nfit)
882{
883 std::vector<BergSchergPar> pars(nfit);
884
885 for (int k = 0; k < nfit; k++) {
886 pars[k].mu = mu[k];
887 pars[k].lambda = lambda[k];
888 }
889
890 std::sort(pars.begin(), pars.end(), [](const BergSchergPar& a, const BergSchergPar& b) {
891 return a.mu > b.mu;
892 });
893
894 for (int k = 0; k < nfit; k++) {
895 mu[k] = pars[k].mu;
896 lambda[k] = pars[k].lambda;
897 }
898}
899
900static bool report_fit(int loop,
901 const VectorXd &fitpar,
902 double Smin,
903 double /*fval_hi*/,
904 double /*par_diff*/)
905{
906#ifdef LOG_FIT
907 for (int k = 0; k < fitpar.size(); k++)
908 qInfo("%g ",mu[k]);
909 qInfo("%g",Smin);
910#endif
911 return true;
912}
913
914static MatrixXd get_initial_simplex(const VectorXd &pars,
915 double simplex_size)
916
917{
918 int npar = pars.size();
919
920 MatrixXd simplex = MatrixXd::Zero(npar+1,npar);
921
922 simplex.rowwise() += pars.transpose();
923
924 for (int k = 1; k < npar+1; k++)
925 simplex(k,k-1) += simplex_size;
926
927 return simplex;
928}
929
931{
932 double mu1n,k1;
933 int k,p;
934 /*
935 * y is the data to be fitted (nterms-1 x 1)
936 * M is the model matrix (nterms-1 x nfit-1)
937 */
938 for (k = 0; k < u->nterms-1; k++) {
939 k1 = k + 1;
940 mu1n = pow(mu[0],k1);
941 u->y[k] = u->w[k]*(u->fn[k+1] - mu1n*u->fn[0]);
942 for (p = 0; p < u->nfit-1; p++)
943 u->M(k,p) = u->w[k]*(pow(mu[p+1],k1)-mu1n);
944 }
945}
946
947// fwd_fit_berg_scherg.c
949 VectorXd& lambda,
950 fitUser u)
951/*
952 * Compute the best-fitting linear parameters
953 * Return the corresponding RV
954 */
955{
956 int k,p,q;
957 VectorXd vec(u->nfit-1);
958 double sum;
959
961
962 compute_svd(u->M, u->sing, u->uu, &u->vv);
963 /*
964 * Compute the residuals
965 */
966 for (k = 0; k < u->nterms-1; k++)
967 u->resi[k] = u->y[k];
968
969 for (p = 0; p < u->nfit-1; p++) {
970 vec[p] = u->uu.row(p).head(u->nterms-1).dot(u->y.head(u->nterms-1));
971 for (k = 0; k < u->nterms-1; k++)
972 u->resi[k] = u->resi[k] - u->uu(p,k)*vec[p];
973 vec[p] = vec[p]/u->sing[p];
974 }
975
976 for (p = 0; p < u->nfit-1; p++) {
977 for (q = 0, sum = 0.0; q < u->nfit-1; q++)
978 sum += u->vv(q,p)*vec[q];
979 lambda[p+1] = sum;
980 }
981 for (p = 1, sum = 0.0; p < u->nfit; p++)
982 sum += lambda[p];
983 lambda[0] = u->fn[0] - sum;
984 return u->resi.head(u->nterms-1).squaredNorm() / u->y.head(u->nterms-1).squaredNorm();
985}
986
987// fwd_fit_berg_scherg.c
988double FwdEegSphereModel::one_step (const VectorXd& mu, const void *user_data)
989/*
990 * Evaluate the residual sum of squares fit for one set of
991 * mu values
992 */
993{
994 int k,p;
995 double dot;
996 fitUser u = (fitUser)user_data;
997
998 for (k = 0; k < u->nfit; k++) {
999 if (std::fabs(mu[k]) > 1.0)
1000 return 1.0;
1001 }
1002 /*
1003 * Compose the data for the linear fitting
1004 */
1006 /*
1007 * Compute SVD
1008 */
1009 compute_svd(u->M, u->sing, u->uu, nullptr);
1010 /*
1011 * Compute the residuals
1012 */
1013 for (k = 0; k < u->nterms-1; k++)
1014 u->resi[k] = u->y[k];
1015 for (p = 0; p < u->nfit-1; p++) {
1016 dot = u->uu.row(p).head(u->nterms-1).dot(u->y.head(u->nterms-1));
1017 for (k = 0; k < u->nterms-1; k++)
1018 u->resi[k] = u->resi[k] - u->uu(p,k)*dot;
1019 }
1020 /*
1021 * Return their sum of squares
1022 */
1023 return u->resi.head(u->nterms-1).squaredNorm();
1024}
1025
1026// fwd_fit_berg_scherg.c
1027bool FwdEegSphereModel::fwd_eeg_fit_berg_scherg(int nterms, /* Number of terms to use in the series expansion
1028 * when fitting the parameters */
1029 int nfit, /* Number of equivalent dipoles to fit */
1030 float &rv)
1031/*
1032 * This routine fits the Berg-Scherg equivalent spherical model
1033 * dipole parameters by minimizing the difference between the
1034 * actual and approximative series expansions
1035 */
1036{
1037 bool res = false;
1038 int k;
1039 double rd,R,f;
1040 double simplex_size = 0.01;
1041 MatrixXd simplex;
1042 VectorXd func_val;
1043 double ftol = 1e-9;
1044 VectorXd lambda;
1045 VectorXd mu;
1046 int neval;
1047 int max_eval = 1000;
1048 int report = 1;
1050
1051 if (nfit < 2) {
1052 qWarning("fwd_fit_berg_scherg does not work with less than two equivalent sources.");
1053 return false;
1054 }
1055
1056 /*
1057 * (1) Calculate the coefficients of the true expansion
1058 */
1059 for (k = 0; k < nterms; k++)
1060 u->fn[k] = this->fwd_eeg_get_multi_sphere_model_coeff(k+1);
1061
1062 /*
1063 * (2) Calculate the weighting
1064 */
1065 rd = R = this->layers[0].rad;
1066 for (k = 1; k < this->nlayer(); k++) {
1067 if (this->layers[k].rad > R)
1068 R = this->layers[k].rad;
1069 if (this->layers[k].rad < rd)
1070 rd = this->layers[k].rad;
1071 }
1072 f = rd/R;
1073
1074#ifdef ZHANG
1075 /*
1076 * This is the Zhang weighting
1077 */
1078 for (k = 1; k < nterms; k++)
1079 u->w[k-1] = pow(f,k);
1080#else
1081 /*
1082 * This is the correct weighting
1083 */
1084 for (k = 1; k < nterms; k++)
1085 u->w[k-1] = sqrt((2.0*k+1)*(3.0*k+1.0)/k)*pow(f,(k-1.0));
1086#endif
1087
1088 /*
1089 * (3) Prepare for simplex minimization
1090 */
1091 func_val = VectorXd(nfit+1);
1092 lambda = VectorXd(nfit);
1093 mu = VectorXd(nfit);
1094 /*
1095 * (4) Rather arbitrary initial guess
1096 */
1097 for (k = 0; k < nfit; k++) {
1098 /*
1099 mu[k] = (k+1)*0.1*f;
1100 */
1101 mu[k] = (rand() / (RAND_MAX + 1.0))*f;//replacement for: mu[k] = drand48()*f;
1102 }
1103
1104 simplex = get_initial_simplex(mu,simplex_size);
1105 for (k = 0; k < nfit+1; k++)
1106 func_val[k] = one_step(VectorXd(simplex.row(k).transpose()),u);
1107
1108 // Capture user data in type-safe lambda — no void* needed
1109 auto cost = [u](const VectorXd& x) -> double { return one_step(x, u); };
1110
1111 /*
1112 * (5) Do the nonlinear minimization
1113 */
1115 func_val,
1116 ftol,
1117 0.0,
1118 cost,
1119 max_eval,
1120 neval,
1121 report,
1122 report_fit);
1123
1124 if (res) {
1125 for (k = 0; k < nfit; k++)
1126 mu[k] = simplex(0,k);
1127
1128 /*
1129 * (6) Do the final step: calculation of the linear parameters
1130 */
1132
1133 sort_parameters(mu,lambda,nfit);
1134#ifdef LOG_FIT
1135 qInfo("RV = %g %%",100*rv);
1136#endif
1137 this->mu.resize(nfit);
1138 this->lambda.resize(nfit);
1139 this->nfit = nfit;
1140 for (k = 0; k < nfit; k++) {
1141 this->mu[k] = mu[k];
1142 /*
1143 * This division takes into account the actual conductivities
1144 */
1145 this->lambda[k] = lambda[k]/this->layers[this->nlayer()-1].sigma;
1146#ifdef LOG_FIT
1147 qInfo("lambda%d = %g\tmu%d = %g",k+1,lambda[k],k+1,mu[k]);
1148#endif
1149 }
1150 }
1151
1152 delete u;
1153 return res;
1154}
constexpr double EPS
Named container of FwdEegSphereModel objects loaded from an mne_setup_eeg_sphere_model parameter file...
Multi-shell spherical head model with Berg-Scherg equivalent-source approximation for fast EEG forwar...
constexpr int FAIL
constexpr int OK
#define M_PI
Eigen::Matrix3f R
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
Header-only Nelder–Mead simplex minimiser with pluggable cost and report callables.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
Forward modelling — BEM solver, spherical models, sensor/coil definitions and the lead-field assembly...
Definition compute_fwd.h:83
constexpr int FWD_COILC_EEG
Definition fwd_coil.h:69
fitUserRec * fitUser
Single MEG sensor coil or EEG electrode — stores the coil-local frame and the (r_mag,...
Definition fwd_coil.h:88
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > rmag
Definition fwd_coil.h:170
Eigen::VectorXf w
Definition fwd_coil.h:172
Container of FwdCoil instances acting both as the in-memory image of the coil_def....
std::vector< FwdCoil::UPtr > coils
One concentric shell (outer radius rad, conductivity sigma and the derived ratios) of a multi-shell d...
static bool comp_layers(const FwdEegSphereLayer &v1, const FwdEegSphereLayer &v2)
Berg-Scherg parameter pair (magnitude and distance multiplier) for an equivalent dipole in the EEG sp...
Workspace for the linear least-squares fit of Berg-Scherg parameters in the EEG sphere model (SVD mat...
static double compute_linear_parameters(const Eigen::VectorXd &mu, Eigen::VectorXd &lambda, fitUser u)
static int fwd_eeg_multi_spherepot_coil1(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > Vval, void *client)
static FwdEegSphereModel::UPtr setup_eeg_sphere_model(const QString &eeg_model_file, QString eeg_model_name, float eeg_sphere_rad)
static void calc_pot_components(double beta, double cgamma, double &Vrp, double &Vtp, const Eigen::VectorXd &fn, int nterms)
static fitUser new_fit_user(int nfit, int nterms)
static int fwd_eeg_spherepot(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > &el, int neeg, Eigen::VectorXf &Vval, void *client)
static bool fwd_eeg_spherepot_vec(const Eigen::Vector3f &rd, const Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > &el, int neeg, Eigen::MatrixXf &Vval_vec, void *client)
bool fwd_eeg_fit_berg_scherg(int nterms, int nfit, float &rv)
static void compose_linear_fitting_data(const Eigen::VectorXd &mu, fitUser u)
static int fwd_eeg_spherepot_grad_coil(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > Vval, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)
static int fwd_eeg_multi_spherepot(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > &el, int neeg, Eigen::VectorXf &Vval, void *client)
static int fwd_eeg_spherepot_coil_vec(const Eigen::Vector3f &rd, FwdCoilSet &els, Eigen::Ref< Eigen::MatrixXf > Vval_vec, void *client)
static FwdEegSphereModel::UPtr fwd_create_eeg_sphere_model(const QString &name, int nlayer, const Eigen::VectorXf &rads, const Eigen::VectorXf &sigmas)
std::vector< FwdEegSphereLayer > layers
static void next_legen(int n, double x, double &p0, double &p01, double &p1, double &p11)
std::unique_ptr< FwdEegSphereModel > UPtr
static int fwd_eeg_spherepot_coil(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > Vval, void *client)
double fwd_eeg_get_multi_sphere_model_coeff(int n)
bool fwd_setup_eeg_sphere_model(float rad, bool fit_berg_scherg, int nfit)
static double one_step(const Eigen::VectorXd &mu, const void *user_data)
static FwdEegSphereModelSet * fwd_load_eeg_sphere_models(const QString &p_sFileName, FwdEegSphereModelSet *now)
std::unique_ptr< FwdEegSphereModelSet > UPtr
static bool simplex_minimize(Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &p, Eigen::Matrix< T, Eigen::Dynamic, 1 > &y, T ftol, T stol, CostFunc &&func, int max_eval, int &neval, int report, ReportFunc &&report_func)