v2.0.0
Loading...
Searching...
No Matches
fwd_bem_model.cpp
Go to the documentation of this file.
1//=============================================================================================================
17
18//=============================================================================================================
19// INCLUDES
20//=============================================================================================================
21
22#include "fwd_bem_model.h"
23#include "fwd_bem_solution.h"
25#include <mne/mne_surface.h>
26#include <mne/mne_triangle.h>
28
29#include "fwd_comp_data.h"
30
31#include <memory>
32
33#include "fwd_thread_arg.h"
34
35#include <fiff/fiff_stream.h>
37
38#include <QFile>
39#include <QList>
40#include <QThread>
41#include <QtConcurrent>
42
43#define _USE_MATH_DEFINES
44#include <math.h>
45
46#include <Eigen/Dense>
47
48static const Eigen::Vector3f Qx(1.0f, 0.0f, 0.0f);
49static const Eigen::Vector3f Qy(0.0f, 1.0f, 0.0f);
50static const Eigen::Vector3f Qz(0.0f, 0.0f, 1.0f);
51
52//=============================================================================================================
53// Local constants
54//=============================================================================================================
55
56namespace {
57constexpr int X = 0;
58constexpr int Y = 1;
59constexpr int Z = 2;
60constexpr int FAIL = -1;
61constexpr int OK = 0;
62constexpr int LOADED = 1; // fwd_bem_load_solution: successfully loaded
63constexpr int NOT_FOUND = 0; // fwd_bem_load_solution: solution not available
64
65constexpr auto BEM_SUFFIX = "-bem.fif";
66constexpr auto BEM_SOL_SUFFIX = "-bem-sol.fif";
67constexpr float EPS = 1e-5f; // Points closer to origin than this are considered at the origin
68constexpr float CEPS = 1e-5f;
69}
70
71namespace FWDLIB
72{
73
74struct SurfExpl {
75 int kind;
76 const QString name;
77};
78
79struct MethodExpl {
80 int method;
81 const QString name;
82};
83
84} // namespace FWDLIB
85
86static FWDLIB::SurfExpl surf_expl[] = { { FIFFV_BEM_SURF_ID_BRAIN , "inner skull" },
87{ FIFFV_BEM_SURF_ID_SKULL , "outer skull" },
88{ FIFFV_BEM_SURF_ID_HEAD , "scalp" },
89{ -1 , "unknown" } };
90
91static FWDLIB::MethodExpl method_expl[] = { { FWDLIB::FWD_BEM_CONSTANT_COLL , "constant collocation" },
92{ FWDLIB::FWD_BEM_LINEAR_COLL , "linear collocation" },
93{ -1 , "unknown" } };
94
95static QString strip_from(const QString& s, const QString& suffix)
96{
97 QString res;
98
99 if (s.endsWith(suffix)) {
100 res = s;
101 res.chop(suffix.size());
102 }
103 else
104 res = s;
105
106 return res;
107}
108
109
110namespace FWDLIB
111{
112
117 int frame;
118 const QString name;
119};
120
121}
122
123const QString mne_coord_frame_name_40(int frame)
124
125{
126 static FWDLIB::FrameNameRec frames[] = {
127 {FIFFV_COORD_UNKNOWN,"unknown"},
128 {FIFFV_COORD_DEVICE,"MEG device"},
129 {FIFFV_COORD_ISOTRAK,"isotrak"},
130 {FIFFV_COORD_HPI,"hpi"},
131 {FIFFV_COORD_HEAD,"head"},
132 {FIFFV_COORD_MRI,"MRI (surface RAS)"},
133 {FIFFV_MNE_COORD_MRI_VOXEL, "MRI voxel"},
134 {FIFFV_COORD_MRI_SLICE,"MRI slice"},
135 {FIFFV_COORD_MRI_DISPLAY,"MRI display"},
136 {FIFFV_MNE_COORD_CTF_DEVICE,"CTF MEG device"},
137 {FIFFV_MNE_COORD_CTF_HEAD,"CTF/4D/KIT head"},
138 {FIFFV_MNE_COORD_RAS,"RAS (non-zero origin)"},
139 {FIFFV_MNE_COORD_MNI_TAL,"MNI Talairach"},
140 {FIFFV_MNE_COORD_FS_TAL_GTZ,"Talairach (MNI z > 0)"},
141 {FIFFV_MNE_COORD_FS_TAL_LTZ,"Talairach (MNI z < 0)"},
142 {-1,"unknown"}
143 };
144 int k;
145 for (k = 0; frames[k].frame != -1; k++) {
146 if (frame == frames[k].frame)
147 return frames[k].name;
148 }
149 return frames[k].name;
150}
151
152//=============================================================================================================
153// USED NAMESPACES
154//=============================================================================================================
155
156using namespace Eigen;
157using namespace FIFFLIB;
158using namespace MNELIB;
159using namespace FWDLIB;
160
161//=============================================================================================================
162// DEFINE MEMBER METHODS
163//=============================================================================================================
164
174
175//=============================================================================================================
176
178{
179 // surfs cleaned up automatically via shared_ptr
180 this->fwd_bem_free_solution();
181}
182
183//=============================================================================================================
184
186{
187 this->solution.resize(0, 0);
188 this->sol_name.clear();
189 this->v0.resize(0);
191 this->nsol = 0;
192}
193
194//=============================================================================================================
195
196QString FwdBemModel::fwd_bem_make_bem_sol_name(const QString& name)
197/*
198 * Make a standard BEM solution file name
199 */
200{
201 QString s1, s2;
202
203 s1 = strip_from(name,".fif");
204 s2 = strip_from(s1,"-sol");
205 s1 = strip_from(s2,"-bem");
206 s2 = QString("%1%2").arg(s1).arg(BEM_SOL_SUFFIX);
207 return s2;
208}
209
210//=============================================================================================================
211
213{
214 int k;
215
216 for (k = 0; surf_expl[k].kind >= 0; k++)
217 if (surf_expl[k].kind == kind)
218 return surf_expl[k].name;
219
220 return surf_expl[k].name;
221}
222
223//=============================================================================================================
224
225const QString& FwdBemModel::fwd_bem_explain_method(int method)
226
227{
228 int k;
229
230 for (k = 0; method_expl[k].method >= 0; k++)
231 if (method_expl[k].method == method)
232 return method_expl[k].name;
233
234 return method_expl[k].name;
235}
236
237//=============================================================================================================
238
239int FwdBemModel::get_int(FiffStream::SPtr &stream, const FiffDirNode::SPtr &node, int what, int *res)
240/*
241 * Wrapper to get int's
242 */
243{
244 FiffTag::UPtr t_pTag;
245 if(node->find_tag(stream, what, t_pTag)) {
246 if (t_pTag->getType() != FIFFT_INT) {
247 qWarning("Expected an integer tag : %d (found data type %d instead)",what,t_pTag->getType() );
248 return FAIL;
249 }
250 *res = *t_pTag->toInt();
251 return OK;
252 }
253 return FAIL;
254}
255
256//=============================================================================================================
257
259{
260 for (int k = 0; k < this->nsurf; k++)
261 if (this->surfs[k]->id == kind)
262 return this->surfs[k].get();
263 qWarning("Desired surface (%d = %s) not found.", kind,fwd_bem_explain_surface(kind).toUtf8().constData());
264 return nullptr;
265}
266
267//=============================================================================================================
268
269FwdBemModel::UPtr FwdBemModel::fwd_bem_load_surfaces(const QString &name, const std::vector<int>& kinds)
270/*
271 * Load a set of surfaces
272 */
273{
274 std::vector<std::shared_ptr<MNESurface>> surfs;
275 const int nkind = static_cast<int>(kinds.size());
276 Eigen::VectorXf sigma_tmp(nkind);
277 int j,k;
278
279 if (nkind <= 0) {
280 qCritical("No surfaces specified to fwd_bem_load_surfaces");
281 return nullptr;
282 }
283
284 for (k = 0; k < nkind; k++) {
285 float cond = -1.0f;
286 auto s = MNESurface::read_bem_surface(name,kinds[k],true,cond);
287 if (!s)
288 return nullptr;
289 if (cond < 0.0) {
290 qCritical("No conductivity available for surface %s",fwd_bem_explain_surface(kinds[k]).toUtf8().constData());
291 return nullptr;
292 }
293 if (s->coord_frame != FIFFV_COORD_MRI) {
294 qCritical("FsSurface %s not specified in MRI coordinates.",fwd_bem_explain_surface(kinds[k]).toUtf8().constData());
295 return nullptr;
296 }
297 sigma_tmp[k] = cond;
298 surfs.push_back(std::move(s));
299 }
300 auto m = std::make_unique<FwdBemModel>();
301
302 m->surf_name = name;
303 m->nsurf = nkind;
304 m->surfs = std::move(surfs);
305 m->sigma = sigma_tmp;
306 m->ntri.resize(nkind);
307 m->np.resize(nkind);
308 m->gamma.resize(nkind, nkind);
309 m->source_mult.resize(nkind);
310 m->field_mult.resize(nkind);
311 /*
312 * Build a shifted conductivity array with sigma[-1] = 0 (outside)
313 */
314 Eigen::VectorXf sigma1(nkind + 1);
315 sigma1[0] = 0.0f;
316 sigma1.tail(nkind) = m->sigma;
317 // sigma[j] below refers to sigma1[j+1], sigma[j-1] to sigma1[j]
318 /*
319 * Gamma factors and multipliers
320 */
321 for (j = 0; j < m->nsurf; j++) {
322 m->ntri[j] = m->surfs[j]->ntri;
323 m->np[j] = m->surfs[j]->np;
324 m->source_mult[j] = 2.0f / (sigma1[j+1] + sigma1[j]);
325 m->field_mult[j] = sigma1[j+1] - sigma1[j];
326 for (k = 0; k < m->nsurf; k++)
327 m->gamma(j, k) = (sigma1[k+1] - sigma1[k]) / (sigma1[j+1] + sigma1[j]);
328 }
329
330 return m;
331}
332
333//=============================================================================================================
334
336/*
337 * Load surfaces for the homogeneous model
338 */
339{
341}
342
343//=============================================================================================================
344
346/*
347 * Load surfaces for three-layer model
348 */
349{
351}
352
353//=============================================================================================================
354
356/*
357 * Load the potential solution matrix and attach it to the model
358 */
359{
360 QFile file(name);
361 FiffStream::SPtr stream(new FiffStream(&file));
362
363 FiffDirNode::SPtr bem_node;
364 int method;
365 FiffTag::UPtr t_pTag;
366 int nsol;
367
368 if(!stream->open()) {
369 stream->close();
370 return NOT_FOUND;
371 }
372
373 /*
374 * Find the BEM data
375 */
376 {
377 QList<FiffDirNode::SPtr> nodes = stream->dirtree()->dir_tree_find(FIFFB_BEM);
378
379 if (nodes.size() == 0) {
380 qWarning("No BEM data in %s",name.toUtf8().constData());
381 stream->close();
382 return NOT_FOUND;
383 }
384 bem_node = nodes[0];
385 }
386 /*
387 * Approximation method
388 */
389 if (get_int(stream,bem_node,FIFF_BEM_APPROX,&method) != OK) {
390 stream->close();
391 return NOT_FOUND;
392 }
393 if (method == FIFFV_BEM_APPROX_CONST)
394 method = FWD_BEM_CONSTANT_COLL;
395 else if (method == FIFFV_BEM_APPROX_LINEAR)
396 method = FWD_BEM_LINEAR_COLL;
397 else {
398 qWarning("Cannot handle BEM approximation method : %d",method);
399 stream->close();
400 return FAIL;
401 }
402 if (bem_method != FWD_BEM_UNKNOWN && method != bem_method) {
403 qWarning("Approximation method in file : %d desired : %d",method,bem_method);
404 stream->close();
405 return NOT_FOUND;
406 }
407 {
408 int dim,k;
409
410 if (!bem_node->find_tag(stream, FIFF_BEM_POT_SOLUTION, t_pTag)) {
411 stream->close();
412 return FAIL;
413 }
414 qint32 ndim;
415 QVector<qint32> dims;
416 t_pTag->getMatrixDimensions(ndim, dims);
417
418 if (ndim != 2) {
419 qWarning("Expected a two-dimensional solution matrix instead of a %d dimensional one",ndim);
420 stream->close();
421 return FAIL;
422 }
423 for (k = 0, dim = 0; k < nsurf; k++)
424 dim = dim + ((method == FWD_BEM_LINEAR_COLL) ? surfs[k]->np : surfs[k]->ntri);
425 if (dims[0] != dim || dims[1] != dim) {
426 qWarning("Expected a %d x %d solution matrix instead of a %d x %d one",dim,dim,dims[0],dims[1]);
427 stream->close();
428 return NOT_FOUND;
429 }
430
431 MatrixXf tmp_sol = t_pTag->toFloatMatrix().transpose();
432 nsol = dims[1];
433 }
435 sol_name = name;
436 solution = t_pTag->toFloatMatrix().transpose();
437 this->nsol = nsol;
438 this->bem_method = method;
439 stream->close();
440
441 return LOADED;
442}
443
444//=============================================================================================================
445
447/*
448 * Set the coordinate transformation
449 */
450{
451 if (t.from == FIFFV_COORD_HEAD && t.to == FIFFV_COORD_MRI) {
452 head_mri_t = t;
453 return OK;
454 }
455 else if (t.from == FIFFV_COORD_MRI && t.to == FIFFV_COORD_HEAD) {
456 head_mri_t = t.inverted();
457 return OK;
458 }
459 else {
460 qWarning("Improper coordinate transform delivered to fwd_bem_set_head_mri_t");
461 return FAIL;
462 }
463}
464
465//=============================================================================================================
466
467MNESurface::UPtr FwdBemModel::make_guesses(MNESurface* guess_surf, float guessrad, const Eigen::Vector3f& guess_r0, float grid, float exclude, float mindist)
468{
469 QString bemname;
470 MNESurface::UPtr sphere_owner;
472 int k;
473 float dist;
474
475 if (!guess_surf) {
476 qInfo("Making a spherical guess space with radius %7.1f mm...",1000*guessrad);
477
478 QFile bemFile(QString(QCoreApplication::applicationDirPath() + "/../resources/general/surf2bem/icos.fif"));
479 if ( !QCoreApplication::startingUp() )
480 bemFile.setFileName(QCoreApplication::applicationDirPath() + QString("/../resources/general/surf2bem/icos.fif"));
481 else if (!bemFile.exists())
482 bemFile.setFileName("../resources/general/surf2bem/icos.fif");
483
484 if( !bemFile.exists () ){
485 qDebug() << bemFile.fileName() << "does not exists.";
486 return res;
487 }
488
489 bemname = bemFile.fileName();
490
491 sphere_owner = MNESurface::read_bem_surface(bemname,9003,false);
492 if (!sphere_owner)
493 return res;
494
495 for (k = 0; k < sphere_owner->np; k++) {
496 dist = sphere_owner->point(k).norm();
497 sphere_owner->rr.row(k) = (guessrad * sphere_owner->rr.row(k) / dist) + guess_r0.transpose();
498 }
499 if (sphere_owner->add_geometry_info(true) == FAIL)
500 return res;
501 guess_surf = sphere_owner.get();
502 }
503 else {
504 qInfo("Guess surface (%d = %s) is in %s coordinates",
505 guess_surf->id,FwdBemModel::fwd_bem_explain_surface(guess_surf->id).toUtf8().constData(),
506 mne_coord_frame_name_40(guess_surf->coord_frame).toUtf8().constData());
507 }
508 qInfo("Filtering (grid = %6.f mm)...",1000*grid);
509 res.reset(reinterpret_cast<MNESurface*>(MNESourceSpace::make_volume_source_space(*guess_surf,grid,exclude,mindist)));
510
511 return res;
512}
513
514//=============================================================================================================
515
516double FwdBemModel::calc_beta(const Eigen::Vector3d& rk, const Eigen::Vector3d& rk1)
517
518{
519 Eigen::Vector3d rkk1 = rk1 - rk;
520 double size = rkk1.norm();
521
522 return log((rk.norm() * size + rk.dot(rkk1)) /
523 (rk1.norm() * size + rk1.dot(rkk1))) / size;
524}
525
526//=============================================================================================================
527
528void FwdBemModel::lin_pot_coeff(const Eigen::Vector3f& from, MNETriangle& to, Eigen::Vector3d& omega) /* The final result */
529/*
530 * The linear potential matrix element computations
531 */
532{
533 Eigen::Vector3d y1, y2, y3; /* Corners with origin at from */
534 double l1,l2,l3; /* Lengths of y1, y2, and y3 */
535 double solid; /* The standard solid angle */
536 Eigen::Vector3d vec_omega; /* The cross-product integral */
537 double triple; /* (y1 x y2) . y3 */
538 double ss;
539 double beta[3],bbeta[3];
540 int j,k;
541 double n2,area2;
542 static const double solid_eps = 4.0*M_PI/1.0E6;
543 /*
544 * Corners with origin at from (float → double)
545 */
546 y1 = (to.r1 - from).cast<double>();
547 y2 = (to.r2 - from).cast<double>();
548 y3 = (to.r3 - from).cast<double>();
549 /*
550 * Circular indexing for vertex access
551 */
552 const Eigen::Vector3d* y_arr[5] = { &y3, &y1, &y2, &y3, &y1 };
553 const Eigen::Vector3d** yy = y_arr + 1; /* yy can have index -1! */
554 /*
555 * The standard solid angle computation
556 */
557 Eigen::Vector3d cross = y1.cross(y2);
558 triple = cross.dot(y3);
559
560 l1 = y1.norm();
561 l2 = y2.norm();
562 l3 = y3.norm();
563 ss = (l1*l2*l3+y1.dot(y2)*l3+y1.dot(y3)*l2+y2.dot(y3)*l1);
564 solid = 2.0*atan2(triple,ss);
565 if (std::fabs(solid) < solid_eps) {
566 omega.setZero();
567 }
568 else {
569 /*
570 * Calculate the magic vector vec_omega
571 */
572 for (j = 0; j < 3; j++)
573 beta[j] = calc_beta(*yy[j],*yy[j+1]);
574 bbeta[0] = beta[2] - beta[0];
575 bbeta[1] = beta[0] - beta[1];
576 bbeta[2] = beta[1] - beta[2];
577
578 vec_omega.setZero();
579 for (j = 0; j < 3; j++)
580 vec_omega += bbeta[j] * (*yy[j]);
581 /*
582 * Put it all together...
583 */
584 area2 = 2.0*to.area;
585 n2 = 1.0/(area2*area2);
586 Eigen::Vector3d nn_d = to.nn.cast<double>();
587 for (k = 0; k < 3; k++) {
588 Eigen::Vector3d z = yy[k+1]->cross(*yy[k-1]);
589 Eigen::Vector3d diff = *yy[k-1] - *yy[k+1];
590 omega[k] = n2*(-area2*z.dot(nn_d)*solid +
591 triple*diff.dot(vec_omega));
592 }
593 }
594#ifdef CHECK
595 /*
596 * Check it out!
597 *
598 * omega1 + omega2 + omega3 = solid
599 */
600 double rel1 = (solid + omega[0]+omega[1]+omega[2])/solid;
601 /*
602 * The other way of evaluating...
603 */
604 Eigen::Vector3d check = Eigen::Vector3d::Zero();
605 Eigen::Vector3d nn_check = to.nn.cast<double>();
606 for (k = 0; k < 3; k++) {
607 Eigen::Vector3d z = nn_check.cross(*yy[k]);
608 check += omega[k]*z;
609 }
610 check *= -area2/triple;
611 fprintf (stderr,"(%g,%g,%g) =? (%g,%g,%g)\n",
612 check[0],check[1],check[2],
613 vec_omega[0],vec_omega[1],vec_omega[2]);
614 check -= vec_omega;
615 double rel2 = sqrt(check.dot(check)/vec_omega.dot(vec_omega));
616 fprintf (stderr,"err1 = %g, err2 = %g\n",100*rel1,100*rel2);
617#endif
618 return;
619}
620
621//=============================================================================================================
622
623void FwdBemModel::correct_auto_elements(MNESurface& surf, Eigen::MatrixXf& mat)
624/*
625 * Improve auto-element approximation...
626 */
627{
628 float sum,miss;
629 int nnode = surf.np;
630 int ntri = surf.ntri;
631 int nmemb;
632 int j,k;
633 float pi2 = 2.0*M_PI;
634 MNETriangle* tri;
635
636#ifdef SIMPLE
637 for (j = 0; j < nnode; j++) {
638 sum = 0.0;
639 for (k = 0; k < nnode; k++)
640 sum = sum + mat(j,k);
641 fprintf (stderr,"row %d sum = %g missing = %g\n",j+1,sum/pi2,
642 1.0-sum/pi2);
643 mat(j,j) = pi2 - sum;
644 }
645#else
646 for (j = 0; j < nnode; j++) {
647 /*
648 * How much is missing?
649 */
650 sum = 0.0;
651 for (k = 0; k < nnode; k++)
652 sum = sum + mat(j,k);
653 miss = pi2-sum;
654 nmemb = surf.nneighbor_tri[j];
655 /*
656 * The node itself receives one half
657 */
658 mat(j,j) = miss/2.0;
659 /*
660 * The rest is divided evenly among the member nodes...
661 */
662 miss = miss/(4.0*nmemb);
663 for (k = 0,tri = surf.tris.data(); k < ntri; k++,tri++) {
664 if (tri->vert[0] == j) {
665 mat(j,tri->vert[1]) = mat(j,tri->vert[1]) + miss;
666 mat(j,tri->vert[2]) = mat(j,tri->vert[2]) + miss;
667 }
668 else if (tri->vert[1] == j) {
669 mat(j,tri->vert[0]) = mat(j,tri->vert[0]) + miss;
670 mat(j,tri->vert[2]) = mat(j,tri->vert[2]) + miss;
671 }
672 else if (tri->vert[2] == j) {
673 mat(j,tri->vert[0]) = mat(j,tri->vert[0]) + miss;
674 mat(j,tri->vert[1]) = mat(j,tri->vert[1]) + miss;
675 }
676 }
677 /*
678 * Just check it it out...
679 *
680 for (k = 0, sum = 0; k < nnode; k++)
681 sum = sum + mat(j,k);
682 fprintf (stderr,"row %d sum = %g\n",j+1,sum/pi2);
683 */
684 }
685#endif
686 return;
687}
688
689//=============================================================================================================
690
691Eigen::MatrixXf FwdBemModel::fwd_bem_lin_pot_coeff(const std::vector<MNESurface*>& surfs)
692/*
693 * Calculate the coefficients for linear collocation approach
694 */
695{
696 int np1,np2,ntri,np_tot,np_max;
697 MNETriangle* tri;
698 Eigen::Vector3d omega;
699 int j,k,p,q,c;
700 int joff,koff;
701 MNESurface* surf1;
702 MNESurface* surf2;
703
704 for (p = 0, np_tot = np_max = 0; p < surfs.size(); p++) {
705 np_tot += surfs[p]->np;
706 if (surfs[p]->np > np_max)
707 np_max = surfs[p]->np;
708 }
709
710 Eigen::MatrixXf mat = Eigen::MatrixXf::Zero(np_tot, np_tot);
711 Eigen::VectorXd row(np_max);
712 for (p = 0, joff = 0; p < surfs.size(); p++, joff = joff + np1) {
713 surf1 = surfs[p];
714 np1 = surf1->np;
715 for (q = 0, koff = 0; q < surfs.size(); q++, koff = koff + np2) {
716 surf2 = surfs[q];
717 np2 = surf2->np;
718 ntri = surf2->ntri;
719
720 qInfo("\t\t%s (%d) -> %s (%d) ... ",
721 fwd_bem_explain_surface(surf1->id).toUtf8().constData(),np1,
722 fwd_bem_explain_surface(surf2->id).toUtf8().constData(),np2);
723
724 for (j = 0; j < np1; j++) {
725 for (k = 0; k < np2; k++)
726 row[k] = 0.0;
727 for (k = 0, tri = surf2->tris.data(); k < ntri; k++,tri++) {
728 /*
729 * No contribution from a triangle that
730 * this vertex belongs to
731 */
732 if (p == q && (tri->vert[0] == j || tri->vert[1] == j || tri->vert[2] == j))
733 continue;
734 /*
735 * Otherwise do the hard job
736 */
737 lin_pot_coeff(surf1->point(j),*tri,omega);
738 for (c = 0; c < 3; c++)
739 row[tri->vert[c]] = row[tri->vert[c]] - omega[c];
740 }
741 for (k = 0; k < np2; k++)
742 mat(j+joff,k+koff) = row[k];
743 }
744 if (p == q) {
745 Eigen::MatrixXf sub_mat = mat.block(joff, koff, np1, np1);
746 correct_auto_elements(*surf1, sub_mat);
747 mat.block(joff, koff, np1, np1) = sub_mat;
748 }
749 qInfo("[done]");
750 }
751 }
752 return mat;
753}
754
755//=============================================================================================================
756
758/*
759 * Compute the linear collocation potential solution
760 */
761{
762 Eigen::MatrixXf coeff;
763 float ip_mult;
764 int k;
765
767
768 // Extract raw surface pointers for coefficient functions
769 std::vector<MNESurface*> rawSurfs;
770 rawSurfs.reserve(nsurf);
771 for (auto& s : surfs) rawSurfs.push_back(s.get());
772
773 qInfo("\nComputing the linear collocation solution...");
774 qInfo("\tMatrix coefficients...");
775 coeff = fwd_bem_lin_pot_coeff(rawSurfs);
776 if (coeff.size() == 0) {
778 return FAIL;
779 }
780
781 for (k = 0, nsol = 0; k < nsurf; k++)
782 nsol += surfs[k]->np;
783
784 qInfo("\tInverting the coefficient matrix...");
786 if (solution.size() == 0) {
788 return FAIL;
789 }
790
791 /*
792 * IP approach?
793 */
794 if ((nsurf == 3) &&
795 (ip_mult = sigma[nsurf-2]/sigma[nsurf-1]) <= ip_approach_limit) {
796 Eigen::MatrixXf ip_solution;
797
798 qInfo("IP approach required...");
799
800 qInfo("\tMatrix coefficients (homog)...");
801 std::vector<MNESurface*> last_surfs = { surfs.back().get() };
802 coeff = fwd_bem_lin_pot_coeff(last_surfs);
803 if (coeff.size() == 0) {
805 return FAIL;
806 }
807
808 qInfo("\tInverting the coefficient matrix (homog)...");
809 ip_solution = fwd_bem_homog_solution(coeff, surfs[nsurf-1]->np);
810 if (ip_solution.size() == 0) {
812 return FAIL;
813 }
814
815 qInfo("\tModify the original solution to incorporate IP approach...");
816
817 fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, nsurf, this->np);
818 }
820 qInfo("Solution ready.");
821 return OK;
822}
823
824//=============================================================================================================
825
826Eigen::MatrixXf FwdBemModel::fwd_bem_multi_solution(Eigen::MatrixXf& solids, const Eigen::MatrixXf *gamma, int nsurf, const Eigen::VectorXi& ntri)
827/*
828 * Invert I - solids/(2*M_PI)
829 * Take deflation into account
830 * The matrix is destroyed after inversion
831 * This is the general multilayer case
832 */
833{
834 int j,k,p,q;
835 float defl;
836 float pi2 = 1.0/(2*M_PI);
837 float mult;
838 int joff,koff,jup,kup,ntot;
839
840 for (j = 0,ntot = 0; j < nsurf; j++)
841 ntot += ntri[j];
842 defl = 1.0/ntot;
843 /*
844 * Modify the matrix
845 */
846 for (p = 0, joff = 0; p < nsurf; p++) {
847 jup = ntri[p] + joff;
848 for (q = 0, koff = 0; q < nsurf; q++) {
849 kup = ntri[q] + koff;
850 mult = (gamma == nullptr) ? pi2 : pi2 * (*gamma)(p, q);
851 for (j = joff; j < jup; j++)
852 for (k = koff; k < kup; k++)
853 solids(j,k) = defl - solids(j,k)*mult;
854 koff = kup;
855 }
856 joff = jup;
857 }
858 for (k = 0; k < ntot; k++)
859 solids(k,k) = solids(k,k) + 1.0;
860
861 Eigen::MatrixXf result = solids.inverse();
862 return result;
863}
864
865//=============================================================================================================
866
867Eigen::MatrixXf FwdBemModel::fwd_bem_homog_solution(Eigen::MatrixXf& solids, int ntri)
868/*
869 * Invert I - solids/(2*M_PI)
870 * Take deflation into account
871 * The matrix is destroyed after inversion
872 * This is the homogeneous model case
873 */
874{
875 return fwd_bem_multi_solution (solids,nullptr,1,Eigen::VectorXi::Constant(1,ntri));
876}
877
878//=============================================================================================================
879
880void FwdBemModel::fwd_bem_ip_modify_solution(Eigen::MatrixXf &solution, Eigen::MatrixXf& ip_solution, float ip_mult, int nsurf, const Eigen::VectorXi &ntri)
881/*
882 * Modify the solution according to the IP approach
883 */
884{
885 int s;
886 int j,k,joff,koff,ntot,nlast;
887 float mult;
888
889 for (s = 0, koff = 0; s < nsurf-1; s++)
890 koff = koff + ntri[s];
891 nlast = ntri[nsurf-1];
892 ntot = koff + nlast;
893
894 Eigen::VectorXf row(nlast);
895 mult = (1.0 + ip_mult)/ip_mult;
896
897 qInfo("\t\tCombining...");
898 qInfo("t ");
899 ip_solution.transposeInPlace();
900
901 for (s = 0, joff = 0; s < nsurf; s++) {
902 qInfo("%d3 ",s+1);
903 /*
904 * For each row in this surface block, compute dot products
905 * with the transposed ip_solution and subtract 2x the result
906 */
907 for (j = 0; j < ntri[s]; j++) {
908 for (k = 0; k < nlast; k++) {
909 row[k] = solution.row(j + joff).segment(koff, nlast).dot(ip_solution.row(k).head(nlast));
910 }
911 solution.row(j + joff).segment(koff, nlast) -= 2.0f * row.transpose();
912 }
913 joff = joff + ntri[s];
914 }
915
916 qInfo("t ");
917 ip_solution.transposeInPlace();
918
919 qInfo("33 ");
920 /*
921 * The lower right corner is a special case
922 */
923 for (j = 0; j < nlast; j++)
924 for (k = 0; k < nlast; k++)
925 solution(j + koff, k + koff) += mult * ip_solution(j,k);
926 /*
927 * Final scaling
928 */
929 qInfo("done.\n\t\tScaling...");
930 solution *= ip_mult;
931 qInfo("done.");
932 return;
933}
934
935//=============================================================================================================
936
937int FwdBemModel::fwd_bem_check_solids(const Eigen::MatrixXf& angles, int ntri1, int ntri2, float desired)
938/*
939 * Check the angle computations
940 */
941{
942 float sum;
943 int j,k;
944 int res = 0;
945
946 Eigen::VectorXf sums(ntri1);
947 for (j = 0; j < ntri1; j++) {
948 sum = 0;
949 for (k = 0; k < ntri2; k++)
950 sum = sum + angles(j,k);
951 sums[j] = sum/(2*M_PI);
952 }
953 for (j = 0; j < ntri1; j++)
954 /*
955 * Three cases:
956 * same surface: sum = 2*pi
957 * to outer: sum = 4*pi
958 * to inner: sum = 0*pi;
959 */
960 if (std::fabs(sums[j]-desired) > 1e-4) {
961 qWarning("solid angle matrix: rowsum[%d] = 2PI*%g",
962 j+1,sums[j]);
963 res = -1;
964 break;
965 }
966 return res;
967}
968
969//=============================================================================================================
970
971Eigen::MatrixXf FwdBemModel::fwd_bem_solid_angles(const std::vector<MNESurface*>& surfs)
972/*
973 * Compute the solid angle matrix
974 */
975{
976 MNESurface* surf1;
977 MNESurface* surf2;
978 MNETriangle* tri;
979 int ntri1,ntri2,ntri_tot;
980 int j,k,p,q;
981 int joff,koff;
982 float result;
983 float desired;
984
985 for (p = 0,ntri_tot = 0; p < surfs.size(); p++)
986 ntri_tot += surfs[p]->ntri;
987
988 Eigen::MatrixXf solids = Eigen::MatrixXf::Zero(ntri_tot, ntri_tot);
989 for (p = 0, joff = 0; p < surfs.size(); p++, joff = joff + ntri1) {
990 surf1 = surfs[p];
991 ntri1 = surf1->ntri;
992 for (q = 0, koff = 0; q < surfs.size(); q++, koff = koff + ntri2) {
993 surf2 = surfs[q];
994 ntri2 = surf2->ntri;
995 qInfo("\t\t%s (%d) -> %s (%d) ... ",fwd_bem_explain_surface(surf1->id).toUtf8().constData(),ntri1,fwd_bem_explain_surface(surf2->id).toUtf8().constData(),ntri2);
996 for (j = 0; j < ntri1; j++)
997 for (k = 0, tri = surf2->tris.data(); k < ntri2; k++, tri++) {
998 if (p == q && j == k)
999 result = 0.0;
1000 else
1001 result = MNESurfaceOrVolume::solid_angle (surf1->tris[j].cent,*tri);
1002 solids(j+joff,k+koff) = result;
1003 }
1004 qInfo("[done]");
1005 if (p == q)
1006 desired = 1;
1007 else if (p < q)
1008 desired = 0;
1009 else
1010 desired = 2;
1011 Eigen::MatrixXf sub_block = solids.block(joff, koff, ntri1, ntri2);
1012 if (fwd_bem_check_solids(sub_block,ntri1,ntri2,desired) == FAIL) {
1013 return Eigen::MatrixXf();
1014 }
1015 }
1016 }
1017 return solids;
1018}
1019
1020//=============================================================================================================
1021
1023/*
1024 * Compute the solution for the constant collocation approach
1025 */
1026{
1027 Eigen::MatrixXf solids;
1028 int k;
1029 float ip_mult;
1030
1032
1033 // Extract raw surface pointers for coefficient functions
1034 std::vector<MNESurface*> rawSurfs;
1035 rawSurfs.reserve(nsurf);
1036 for (auto& s : surfs) rawSurfs.push_back(s.get());
1037
1038 qInfo("\nComputing the constant collocation solution...");
1039 qInfo("\tSolid angles...");
1040 solids = fwd_bem_solid_angles(rawSurfs);
1041 if (solids.size() == 0) {
1043 return FAIL;
1044 }
1045
1046 for (k = 0, nsol = 0; k < nsurf; k++)
1047 nsol += surfs[k]->ntri;
1048
1049 qInfo("\tInverting the coefficient matrix...");
1051 if (solution.size() == 0) {
1053 return FAIL;
1054 }
1055 /*
1056 * IP approach?
1057 */
1058 if ((nsurf == 3) &&
1059 (ip_mult = sigma[nsurf-2]/sigma[nsurf-1]) <= ip_approach_limit) {
1060 Eigen::MatrixXf ip_solution;
1061
1062 qInfo("IP approach required...");
1063
1064 qInfo("\tSolid angles (homog)...");
1065 std::vector<MNESurface*> last_surfs = { surfs.back().get() };
1066 solids = fwd_bem_solid_angles(last_surfs);
1067 if (solids.size() == 0) {
1069 return FAIL;
1070 }
1071
1072 qInfo("\tInverting the coefficient matrix (homog)...");
1073 ip_solution = fwd_bem_homog_solution(solids, surfs[nsurf-1]->ntri);
1074 if (ip_solution.size() == 0) {
1076 return FAIL;
1077 }
1078
1079 qInfo("\tModify the original solution to incorporate IP approach...");
1080 fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, nsurf, this->ntri);
1081 }
1083 qInfo("Solution ready.");
1084
1085 return OK;
1086}
1087
1088//=============================================================================================================
1089
1091/*
1092 * Compute the solution
1093 */
1094{
1095 /*
1096 * Compute the solution
1097 */
1102
1104 qWarning("Unknown BEM method: %d",bem_method);
1105 return FAIL;
1106}
1107
1108//=============================================================================================================
1109
1110int FwdBemModel::fwd_bem_load_recompute_solution(const QString& name, int bem_method, int force_recompute)
1111/*
1112 * Load or recompute the potential solution matrix
1113 */
1114{
1115 int solres;
1116
1117 if (!force_recompute) {
1119 solres = fwd_bem_load_solution(name,bem_method);
1120 if (solres == LOADED) {
1121 qInfo("\nLoaded %s BEM solution from %s",fwd_bem_explain_method(this->bem_method).toUtf8().constData(),name.toUtf8().constData());
1122 return OK;
1123 }
1124 else if (solres == FAIL)
1125 return FAIL;
1126#ifdef DEBUG
1127 else
1128 qWarning("Desired BEM solution not available in %s (%s)",name,err_get_error());
1129#endif
1130 }
1134}
1135
1136//=============================================================================================================
1137
1138float FwdBemModel::fwd_bem_inf_field(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp, const Eigen::Vector3f& dir)
1139/*
1140 * Infinite-medium magnetic field
1141 * (without \mu_0/4\pi)
1142 */
1143{
1144 Eigen::Vector3f diff = rp - rd;
1145 float diff2 = diff.squaredNorm();
1146 Eigen::Vector3f cr = Q.cross(diff);
1147
1148 return cr.dot(dir) / (diff2 * std::sqrt(diff2));
1149}
1150
1151//=============================================================================================================
1152
1153float FwdBemModel::fwd_bem_inf_pot(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp)
1154/*
1155 * The infinite medium potential
1156 */
1157{
1158 Eigen::Vector3f diff = rp - rd;
1159 float diff2 = diff.squaredNorm();
1160 return Q.dot(diff) / (4.0 * M_PI * diff2 * std::sqrt(diff2));
1161}
1162
1163//=============================================================================================================
1164
1166/*
1167 * Set up for computing the solution at a set of electrodes
1168 */
1169{
1170 FwdCoil* el;
1171 MNESurface* scalp;
1172 int k,p,q,v;
1173 float r[3],w[3],dist;
1174 int best;
1175 MNETriangle* tri;
1176 float x,y,z;
1177 FwdBemSolution* sol = nullptr;
1178
1179 if (solution.size() == 0) {
1180 qWarning("Solution not computed in fwd_bem_specify_els");
1181 return FAIL;
1182 }
1183 if (!els || els->ncoil() == 0)
1184 return OK;
1185 els->user_data.reset();
1186 /*
1187 * Hard work follows
1188 */
1189 els->user_data = std::make_unique<FwdBemSolution>();
1190 sol = els->user_data.get();
1191
1192 sol->ncoil = els->ncoil();
1193 sol->np = nsol;
1194 sol->solution = Eigen::MatrixXf::Zero(sol->ncoil,sol->np);
1195 /*
1196 * Go through all coils
1197 */
1198 for (k = 0; k < els->ncoil(); k++) {
1199 el = els->coils[k].get();
1200 scalp = surfs[0].get();
1201 /*
1202 * Go through all 'integration points'
1203 */
1204 for (p = 0; p < el->np; p++) {
1205 r[0] = el->rmag(p, 0); r[1] = el->rmag(p, 1); r[2] = el->rmag(p, 2);
1206 if (!head_mri_t.isEmpty())
1208 best = scalp->project_to_surface(nullptr,Eigen::Map<const Eigen::Vector3f>(r),dist);
1209 if (best < 0) {
1210 qWarning("One of the electrodes could not be projected onto the scalp surface. How come?");
1211 els->user_data.reset();
1212 return FAIL;
1213 }
1215 /*
1216 * Simply pick the value at the triangle
1217 */
1218 for (q = 0; q < nsol; q++)
1219 sol->solution(k, q) += el->w[p] * solution(best, q);
1220 }
1221 else if (bem_method == FWD_BEM_LINEAR_COLL) {
1222 /*
1223 * Calculate a linear interpolation between the vertex values
1224 */
1225 tri = &scalp->tris[best];
1226 scalp->triangle_coords(Eigen::Map<const Eigen::Vector3f>(r),best,x,y,z);
1227
1228 w[0] = el->w[p]*(1.0 - x - y);
1229 w[1] = el->w[p]*x;
1230 w[2] = el->w[p]*y;
1231 for (v = 0; v < 3; v++) {
1232 for (q = 0; q < nsol; q++)
1233 sol->solution(k, q) += w[v] * solution(tri->vert[v], q);
1234 }
1235 }
1236 else {
1237 qWarning("Unknown BEM approximation method : %d",bem_method);
1238 els->user_data.reset();
1239 return FAIL;
1240 }
1241 }
1242 }
1243 return OK;
1244}
1245
1246//=============================================================================================================
1247
1248void FwdBemModel::fwd_bem_pot_grad_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet* els, int all_surfs, Eigen::Ref<Eigen::VectorXf> xgrad, Eigen::Ref<Eigen::VectorXf> ygrad, Eigen::Ref<Eigen::VectorXf> zgrad)
1249/*
1250 * Compute the potentials due to a current dipole
1251 */
1252{
1253 MNETriangle* tri;
1254 int ntri;
1255 int s,k,p,nsol,pp;
1256 float mult;
1257 Eigen::Vector3f ee;
1258 Eigen::Vector3f mri_rd = rd;
1259 Eigen::Vector3f mri_Q = Q;
1260
1261 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
1262
1263 if (v0.size() == 0)
1264 v0.resize(this->nsol);
1265 float* v0p = v0.data();
1266
1267 if (!head_mri_t.isEmpty()) {
1270 }
1271 for (pp = X; pp <= Z; pp++) {
1272 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
1273
1274 ee = Eigen::Vector3f::Unit(pp);
1275 if (!head_mri_t.isEmpty())
1277
1278 for (s = 0, p = 0; s < nsurf; s++) {
1279 ntri = surfs[s]->ntri;
1280 tri = surfs[s]->tris.data();
1281 mult = source_mult[s];
1282 for (k = 0; k < ntri; k++, tri++)
1283 v0p[p++] = mult*fwd_bem_inf_pot_der(mri_rd,mri_Q,tri->cent,ee);
1284 }
1285 if (els) {
1286 FwdBemSolution* sol = els->user_data.get();
1287 nsol = sol->ncoil;
1288 for (k = 0; k < nsol; k++)
1289 grad[k] = sol->solution.row(k).dot(v0);
1290 }
1291 else {
1292 nsol = all_surfs ? this->nsol : surfs[0]->ntri;
1293 for (k = 0; k < nsol; k++)
1294 grad[k] = solution.row(k).dot(v0);
1295 }
1296 }
1297 return;
1298}
1299
1300//=============================================================================================================
1301
1302void FwdBemModel::fwd_bem_lin_pot_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet *els, int all_surfs, Eigen::Ref<Eigen::VectorXf> pot)
1303/*
1304 * Compute the potentials due to a current dipole
1305 * using the linear potential approximation
1306 */
1307{
1308 float *rr_row;
1309 int np;
1310 int s,k,p,nsol;
1311 float mult;
1312 Eigen::Vector3f mri_rd = rd;
1313 Eigen::Vector3f mri_Q = Q;
1314
1315 if (v0.size() == 0)
1316 v0.resize(this->nsol);
1317 float* v0p = v0.data();
1318
1319 if (!head_mri_t.isEmpty()) {
1322 }
1323 for (s = 0, p = 0; s < nsurf; s++) {
1324 np = surfs[s]->np;
1325 mult = source_mult[s];
1326 for (k = 0; k < np; k++)
1327 v0p[p++] = mult*fwd_bem_inf_pot(mri_rd,mri_Q,surfs[s]->point(k));
1328 }
1329 if (els) {
1330 FwdBemSolution* sol = els->user_data.get();
1331 nsol = sol->ncoil;
1332 for (k = 0; k < nsol; k++)
1333 pot[k] = sol->solution.row(k).dot(v0);
1334 }
1335 else {
1336 nsol = all_surfs ? this->nsol : surfs[0]->np;
1337 for (k = 0; k < nsol; k++)
1338 pot[k] = solution.row(k).dot(v0);
1339 }
1340 return;
1341}
1342
1343//=============================================================================================================
1344
1345void FwdBemModel::fwd_bem_lin_pot_grad_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet *els, int all_surfs, Eigen::Ref<Eigen::VectorXf> xgrad, Eigen::Ref<Eigen::VectorXf> ygrad, Eigen::Ref<Eigen::VectorXf> zgrad)
1346/*
1347 * Compute the derivaties of potentials due to a current dipole with respect to the dipole position
1348 * using the linear potential approximation
1349 */
1350{
1351 int np;
1352 int s,k,p,nsol,pp;
1353 float mult;
1354 Eigen::Vector3f mri_rd = rd;
1355 Eigen::Vector3f mri_Q = Q;
1356 Eigen::Vector3f ee;
1357
1358 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
1359
1360 if (v0.size() == 0)
1361 v0.resize(this->nsol);
1362 float* v0p = v0.data();
1363
1364 if (!head_mri_t.isEmpty()) {
1367 }
1368 for (pp = X; pp <= Z; pp++) {
1369 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
1370
1371 ee = Eigen::Vector3f::Unit(pp);
1372 if (!head_mri_t.isEmpty())
1374
1375 for (s = 0, p = 0; s < nsurf; s++) {
1376 np = surfs[s]->np;
1377 mult = source_mult[s];
1378 for (k = 0; k < np; k++)
1379 v0p[p++] = mult*fwd_bem_inf_pot_der(mri_rd,mri_Q,surfs[s]->point(k),ee);
1380 }
1381 if (els) {
1382 FwdBemSolution* sol = els->user_data.get();
1383 nsol = sol->ncoil;
1384 for (k = 0; k < nsol; k++)
1385 grad[k] = sol->solution.row(k).dot(v0);
1386 }
1387 else {
1388 nsol = all_surfs ? this->nsol : surfs[0]->np;
1389 for (k = 0; k < nsol; k++)
1390 grad[k] = solution.row(k).dot(v0);
1391 }
1392 }
1393 return;
1394}
1395
1396//=============================================================================================================
1397
1398void FwdBemModel::fwd_bem_pot_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet *els, int all_surfs, Eigen::Ref<Eigen::VectorXf> pot)
1399/*
1400 * Compute the potentials due to a current dipole
1401 */
1402{
1403 MNETriangle* tri;
1404 int ntri;
1405 int s,k,p,nsol;
1406 float mult;
1407 Eigen::Vector3f mri_rd = rd;
1408 Eigen::Vector3f mri_Q = Q;
1409
1410 if (v0.size() == 0)
1411 v0.resize(this->nsol);
1412 float* v0p = v0.data();
1413
1414 if (!head_mri_t.isEmpty()) {
1417 }
1418 for (s = 0, p = 0; s < nsurf; s++) {
1419 ntri = surfs[s]->ntri;
1420 tri = surfs[s]->tris.data();
1421 mult = source_mult[s];
1422 for (k = 0; k < ntri; k++, tri++)
1423 v0p[p++] = mult*fwd_bem_inf_pot(mri_rd,mri_Q,tri->cent);
1424 }
1425 if (els) {
1426 FwdBemSolution* sol = els->user_data.get();
1427 nsol = sol->ncoil;
1428 for (k = 0; k < nsol; k++)
1429 pot[k] = sol->solution.row(k).dot(v0);
1430 }
1431 else {
1432 nsol = all_surfs ? this->nsol : surfs[0]->ntri;
1433 for (k = 0; k < nsol; k++)
1434 pot[k] = solution.row(k).dot(v0);
1435 }
1436 return;
1437}
1438
1439//=============================================================================================================
1440
1441int FwdBemModel::fwd_bem_pot_els(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &els, Eigen::Ref<Eigen::VectorXf> pot, void *client) /* The model */
1442/*
1443 * This version calculates the potential on all surfaces
1444 */
1445{
1446 auto* m = static_cast<FwdBemModel*>(client);
1447 FwdBemSolution* sol = els.user_data.get();
1448
1449 if (!m) {
1450 qWarning("No BEM model specified to fwd_bem_pot_els");
1451 return FAIL;
1452 }
1453 if (m->solution.size() == 0) {
1454 qWarning("No solution available for fwd_bem_pot_els");
1455 return FAIL;
1456 }
1457 if (!sol || sol->ncoil != els.ncoil()) {
1458 qWarning("No appropriate electrode-specific data available in fwd_bem_pot_coils");
1459 return FAIL;
1460 }
1461 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
1462 m->fwd_bem_pot_calc(rd,Q,&els,false,pot);
1463 }
1464 else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
1465 m->fwd_bem_lin_pot_calc(rd,Q,&els,false,pot);
1466 }
1467 else {
1468 qWarning("Unknown BEM method : %d",m->bem_method);
1469 return FAIL;
1470 }
1471 return OK;
1472}
1473
1474//=============================================================================================================
1475
1476int FwdBemModel::fwd_bem_pot_grad_els(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &els, Eigen::Ref<Eigen::VectorXf> pot, Eigen::Ref<Eigen::VectorXf> xgrad, Eigen::Ref<Eigen::VectorXf> ygrad, Eigen::Ref<Eigen::VectorXf> zgrad, void *client) /* The model */
1477/*
1478 * This version calculates the potential on all surfaces
1479 */
1480{
1481 auto* m = static_cast<FwdBemModel*>(client);
1482 FwdBemSolution* sol = els.user_data.get();
1483
1484 if (!m) {
1485 qCritical("No BEM model specified to fwd_bem_pot_els");
1486 return FAIL;
1487 }
1488 if (m->solution.size() == 0) {
1489 qCritical("No solution available for fwd_bem_pot_els");
1490 return FAIL;
1491 }
1492 if (!sol || sol->ncoil != els.ncoil()) {
1493 qCritical("No appropriate electrode-specific data available in fwd_bem_pot_coils");
1494 return FAIL;
1495 }
1496 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
1497 int n = els.ncoil();
1498 m->fwd_bem_pot_calc(rd,Q,&els,false,pot);
1499 m->fwd_bem_pot_grad_calc(rd,Q,&els,false,xgrad,ygrad,zgrad);
1500 }
1501 else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
1502 int n = els.ncoil();
1503 m->fwd_bem_lin_pot_calc(rd,Q,&els,false,pot);
1504 m->fwd_bem_lin_pot_grad_calc(rd,Q,&els,false,xgrad,ygrad,zgrad);
1505 }
1506 else {
1507 qCritical("Unknown BEM method : %d",m->bem_method);
1508 return FAIL;
1509 }
1510 return OK;
1511}
1512
1513//=============================================================================================================
1514
1515inline double arsinh(double x) { return std::asinh(x); }
1516
1517void FwdBemModel::calc_f(const Eigen::Vector3d& xx, const Eigen::Vector3d& yy, Eigen::Vector3d& f0, Eigen::Vector3d& fx, Eigen::Vector3d& fy)
1518{
1519 double det = -xx[1]*yy[0] + xx[2]*yy[0] +
1520 xx[0]*yy[1] - xx[2]*yy[1] - xx[0]*yy[2] + xx[1]*yy[2];
1521
1522 f0[0] = -xx[2]*yy[1] + xx[1]*yy[2];
1523 f0[1] = xx[2]*yy[0] - xx[0]*yy[2];
1524 f0[2] = -xx[1]*yy[0] + xx[0]*yy[1];
1525
1526 fx[0] = yy[1] - yy[2];
1527 fx[1] = -yy[0] + yy[2];
1528 fx[2] = yy[0] - yy[1];
1529
1530 fy[0] = -xx[1] + xx[2];
1531 fy[1] = xx[0] - xx[2];
1532 fy[2] = -xx[0] + xx[1];
1533
1534 f0 /= det;
1535 fx /= det;
1536 fy /= det;
1537}
1538
1539//=============================================================================================================
1540
1541void FwdBemModel::calc_magic(double u, double z, double A, double B, Eigen::Vector3d& beta, double& D)
1542{
1543 double B2 = 1.0 + B*B;
1544 double ABu = A + B*u;
1545 D = sqrt(u*u + z*z + ABu*ABu);
1546 beta[0] = ABu/sqrt(u*u + z*z);
1547 beta[1] = (A*B + B2*u)/sqrt(A*A + B2*z*z);
1548 beta[2] = (B*z*z - A*u)/(z*D);
1549}
1550
1551//=============================================================================================================
1552
1553void FwdBemModel::field_integrals(const Eigen::Vector3f& from, MNETriangle& to, double& I1p, Eigen::Vector2d& T, Eigen::Vector2d& S1, Eigen::Vector2d& S2, Eigen::Vector3d& f0, Eigen::Vector3d& fx, Eigen::Vector3d& fy)
1554{
1555 double xx[4],yy[4];
1556 double A,B,z,dx;
1557 Eigen::Vector3d beta;
1558 double I1,Tx,Ty,Txx,Tyy,Sxx,mult;
1559 double S1x,S1y,S2x,S2y;
1560 double D1,B2;
1561 int k;
1562 /*
1563 * Preliminaries...
1564 *
1565 * 1. Move origin to viewpoint...
1566 *
1567 */
1568 Eigen::Vector3d y1 = (to.r1 - from).cast<double>();
1569 Eigen::Vector3d y2 = (to.r2 - from).cast<double>();
1570 Eigen::Vector3d y3 = (to.r3 - from).cast<double>();
1571 /*
1572 * 2. Calculate local xy coordinates...
1573 */
1574 Eigen::Vector3d ex_d = to.ex.cast<double>();
1575 Eigen::Vector3d ey_d = to.ey.cast<double>();
1576 xx[0] = y1.dot(ex_d);
1577 xx[1] = y2.dot(ex_d);
1578 xx[2] = y3.dot(ex_d);
1579 xx[3] = xx[0];
1580
1581 yy[0] = y1.dot(ey_d);
1582 yy[1] = y2.dot(ey_d);
1583 yy[2] = y3.dot(ey_d);
1584 yy[3] = yy[0];
1585
1586 calc_f(Eigen::Map<const Eigen::Vector3d>(xx), Eigen::Map<const Eigen::Vector3d>(yy), f0, fx, fy);
1587 /*
1588 * 3. Distance of the plane from origin...
1589 */
1590 z = y1.dot(to.nn.cast<double>());
1591 /*
1592 * Put together the line integral...
1593 * We use the convention where the local y-axis
1594 * is parallel to the last side and, therefore, dx = 0
1595 * on that side. We can thus omit the last side from this
1596 * computation in some cases.
1597 */
1598 I1 = 0.0;
1599 Tx = 0.0;
1600 Ty = 0.0;
1601 S1x = 0.0;
1602 S1y = 0.0;
1603 S2x = 0.0;
1604 S2y = 0.0;
1605 for (k = 0; k < 2; k++) {
1606 dx = xx[k+1] - xx[k];
1607 A = (yy[k]*xx[k+1] - yy[k+1]*xx[k])/dx;
1608 B = (yy[k+1]-yy[k])/dx;
1609 B2 = (1.0 + B*B);
1610 /*
1611 * Upper limit
1612 */
1613 calc_magic(xx[k+1],z,A,B,beta,D1);
1614 I1 = I1 - xx[k+1]*arsinh(beta[0]) - (A/sqrt(1.0+B*B))*arsinh(beta[1])
1615 - z*atan(beta[2]);
1616 Txx = arsinh(beta[1])/sqrt(B2);
1617 Tx = Tx + Txx;
1618 Ty = Ty + B*Txx;
1619 Sxx = (D1 - A*B*Txx)/B2;
1620 S1x = S1x + Sxx;
1621 S1y = S1y + B*Sxx;
1622 Sxx = (B*D1 + A*Txx)/B2;
1623 S2x = S2x + Sxx;
1624 /*
1625 * Lower limit
1626 */
1627 calc_magic(xx[k],z,A,B,beta,D1);
1628 I1 = I1 + xx[k]*arsinh(beta[0]) + (A/sqrt(1.0+B*B))*arsinh(beta[1])
1629 + z*atan(beta[2]);
1630 Txx = arsinh(beta[1])/sqrt(B2);
1631 Tx = Tx - Txx;
1632 Ty = Ty - B*Txx;
1633 Sxx = (D1 - A*B*Txx)/B2;
1634 S1x = S1x - Sxx;
1635 S1y = S1y - B*Sxx;
1636 Sxx = (B*D1 + A*Txx)/B2;
1637 S2x = S2x - Sxx;
1638 }
1639 /*
1640 * Handle last side (dx = 0) in a special way;
1641 */
1642 mult = 1.0/sqrt(xx[k]*xx[k]+z*z);
1643 /*
1644 * Upper...
1645 */
1646 Tyy = arsinh(mult*yy[k+1]);
1647 Ty = Ty + Tyy;
1648 S1y = S1y + xx[k]*Tyy;
1649 /*
1650 * Lower...
1651 */
1652 Tyy = arsinh(mult*yy[k]);
1653 Ty = Ty - Tyy;
1654 S1y = S1y - xx[k]*Tyy;
1655 /*
1656 * Set return values
1657 */
1658 I1p = I1;
1659 T[0] = Tx;
1660 T[1] = Ty;
1661 S1[0] = S1x;
1662 S1[1] = S1y;
1663 S2[0] = S2x;
1664 S2[1] = -S1x;
1665}
1666
1667//=============================================================================================================
1668
1669double FwdBemModel::one_field_coeff(const Eigen::Vector3f& dest, const Eigen::Vector3f& normal, MNETriangle& tri)
1670{
1671 double beta[3];
1672 double bbeta[3];
1673 int j;
1674
1675 Eigen::Vector3d y1 = (tri.r1 - dest).cast<double>();
1676 Eigen::Vector3d y2 = (tri.r2 - dest).cast<double>();
1677 Eigen::Vector3d y3 = (tri.r3 - dest).cast<double>();
1678
1679 const Eigen::Vector3d* yy[4] = { &y1, &y2, &y3, &y1 };
1680 for (j = 0; j < 3; j++)
1681 beta[j] = calc_beta(*yy[j], *yy[j+1]);
1682 bbeta[0] = beta[2] - beta[0];
1683 bbeta[1] = beta[0] - beta[1];
1684 bbeta[2] = beta[1] - beta[2];
1685
1686 Eigen::Vector3d coeff = Eigen::Vector3d::Zero();
1687 for (j = 0; j < 3; j++)
1688 coeff += bbeta[j] * (*yy[j]);
1689 return coeff.dot(normal.cast<double>());
1690}
1691
1692//=============================================================================================================
1693
1694Eigen::MatrixXf FwdBemModel::fwd_bem_field_coeff(FwdCoilSet *coils) /* Gradiometer coil positions */
1695/*
1696 * Compute the weighting factors to obtain the magnetic field
1697 */
1698{
1699 MNESurface* surf;
1700 MNETriangle* tri;
1701 FwdCoil* coil;
1702 FwdCoilSet::UPtr tcoils;
1703 int ntri;
1704 int j,k,p,s,off;
1705 double res;
1706 double mult;
1707
1708 if (solution.size() == 0) {
1709 qWarning("Solution matrix missing in fwd_bem_field_coeff");
1710 return Eigen::MatrixXf();
1711 }
1713 qWarning("BEM method should be constant collocation for fwd_bem_field_coeff");
1714 return Eigen::MatrixXf();
1715 }
1716 if (coils->coord_frame != FIFFV_COORD_MRI) {
1717 if (coils->coord_frame == FIFFV_COORD_HEAD) {
1718 if (head_mri_t.isEmpty()) {
1719 qWarning("head -> mri coordinate transform missing in fwd_bem_field_coeff");
1720 return Eigen::MatrixXf();
1721 }
1722 else {
1723 /*
1724 * Make a transformed duplicate
1725 */
1726 if ((tcoils = coils->dup_coil_set(head_mri_t)) == nullptr)
1727 return Eigen::MatrixXf();
1728 coils = tcoils.get();
1729 }
1730 }
1731 else {
1732 qWarning("Incompatible coil coordinate frame %d for fwd_bem_field_coeff",coils->coord_frame);
1733 return Eigen::MatrixXf();
1734 }
1735 }
1736 ntri = nsol;
1737 Eigen::MatrixXf coeff = Eigen::MatrixXf::Zero(coils->ncoil(), ntri);
1738
1739 for (s = 0, off = 0; s < nsurf; s++) {
1740 surf = surfs[s].get();
1741 ntri = surf->ntri;
1742 tri = surf->tris.data();
1743 mult = field_mult[s];
1744
1745 for (k = 0; k < ntri; k++,tri++) {
1746 for (j = 0; j < coils->ncoil(); j++) {
1747 coil = coils->coils[j].get();
1748 res = 0.0;
1749 for (p = 0; p < coil->np; p++)
1750 res = res + coil->w[p]*one_field_coeff(coil->pos(p),coil->dir(p),*tri);
1751 coeff(j,k+off) = mult*res;
1752 }
1753 }
1754 off = off + ntri;
1755 }
1756 return coeff;
1757}
1758
1759//=============================================================================================================
1760
1761double FwdBemModel::calc_gamma(const Eigen::Vector3d& rk, const Eigen::Vector3d& rk1)
1762{
1763 Eigen::Vector3d rkk1 = rk1 - rk;
1764 double size = rkk1.norm();
1765
1766 return log((rk1.norm() * size + rk1.dot(rkk1)) /
1767 (rk.norm() * size + rk.dot(rkk1))) / size;
1768}
1769
1770//=============================================================================================================
1771
1772void FwdBemModel::fwd_bem_one_lin_field_coeff_ferg(const Eigen::Vector3f& dest, const Eigen::Vector3f& dir, MNETriangle& tri, Eigen::Vector3d& res)
1773{
1774 double triple,l1,l2,l3,solid,clen;
1775 double common,sum,beta,gamma;
1776 int k;
1777
1778 Eigen::Vector3d rjk[3];
1779 rjk[0] = (tri.r3 - tri.r2).cast<double>();
1780 rjk[1] = (tri.r1 - tri.r3).cast<double>();
1781 rjk[2] = (tri.r2 - tri.r1).cast<double>();
1782
1783 Eigen::Vector3d y1 = (tri.r1 - dest).cast<double>();
1784 Eigen::Vector3d y2 = (tri.r2 - dest).cast<double>();
1785 Eigen::Vector3d y3 = (tri.r3 - dest).cast<double>();
1786
1787 const Eigen::Vector3d* yy[4] = { &y1, &y2, &y3, &y1 };
1788
1789 Eigen::Vector3d nn_d = tri.nn.cast<double>();
1790 clen = y1.dot(nn_d);
1791 Eigen::Vector3d c_vec = clen * nn_d;
1792 Eigen::Vector3d A_vec = dest.cast<double>() + c_vec;
1793
1794 Eigen::Vector3d c1 = tri.r1.cast<double>() - A_vec;
1795 Eigen::Vector3d c2 = tri.r2.cast<double>() - A_vec;
1796 Eigen::Vector3d c3 = tri.r3.cast<double>() - A_vec;
1797
1798 const Eigen::Vector3d* cc[4] = { &c1, &c2, &c3, &c1 };
1799 /*
1800 * beta and gamma...
1801 */
1802 for (sum = 0.0, k = 0; k < 3; k++) {
1803 Eigen::Vector3d cross = cc[k]->cross(*cc[k+1]);
1804 beta = cross.dot(nn_d);
1805 gamma = calc_gamma(*yy[k], *yy[k+1]);
1806 sum = sum + beta*gamma;
1807 }
1808 /*
1809 * Solid angle...
1810 */
1811 Eigen::Vector3d cross = y1.cross(y2);
1812 triple = cross.dot(y3);
1813
1814 l1 = y1.norm();
1815 l2 = y2.norm();
1816 l3 = y3.norm();
1817 solid = 2.0*atan2(triple,
1818 (l1*l2*l3+
1819 y1.dot(y2)*l3+
1820 y1.dot(y3)*l2+
1821 y2.dot(y3)*l1));
1822 /*
1823 * Now we are ready to assemble it all together
1824 */
1825 Eigen::Vector3d dir_d = dir.cast<double>();
1826 common = (sum-clen*solid)/(2.0*tri.area);
1827 for (k = 0; k < 3; k++)
1828 res[k] = -rjk[k].dot(dir_d)*common;
1829 return;
1830}
1831
1832//=============================================================================================================
1833
1834void FwdBemModel::fwd_bem_one_lin_field_coeff_uran(const Eigen::Vector3f& dest, const Eigen::Vector3f& dir_in, MNETriangle& tri, Eigen::Vector3d& res)
1835{
1836 double I1;
1837 Eigen::Vector2d T, S1, S2;
1838 Eigen::Vector3d f0, fx, fy;
1839 double res_x,res_y;
1840 double x_fac,y_fac;
1841 int k;
1842 /*
1843 * Compute the component integrals
1844 */
1845 field_integrals(dest,tri,I1,T,S1,S2,f0,fx,fy);
1846 /*
1847 * Compute the coefficient for each node...
1848 */
1849 Eigen::Vector3f dir = dir_in.normalized();
1850
1851 x_fac = -dir.dot(tri.ex);
1852 y_fac = -dir.dot(tri.ey);
1853 for (k = 0; k < 3; k++) {
1854 res_x = f0[k]*T[0] + fx[k]*S1[0] + fy[k]*S2[0] + fy[k]*I1;
1855 res_y = f0[k]*T[1] + fx[k]*S1[1] + fy[k]*S2[1] - fx[k]*I1;
1856 res[k] = x_fac*res_x + y_fac*res_y;
1857 }
1858}
1859
1860//=============================================================================================================
1861
1862void FwdBemModel::fwd_bem_one_lin_field_coeff_simple(const Eigen::Vector3f& dest, const Eigen::Vector3f& normal, MNETriangle& source, Eigen::Vector3d& res)
1863{
1864 int k;
1865 const Eigen::Vector3f* rr[3] = { &source.r1, &source.r2, &source.r3 };
1866
1867 for (k = 0; k < 3; k++) {
1868 Eigen::Vector3f diff = dest - *rr[k];
1869 float dl = diff.squaredNorm();
1870 Eigen::Vector3f vec_result = diff.cross(source.nn);
1871 res[k] = source.area*vec_result.dot(normal)/(3.0*dl*sqrt(dl));
1872 }
1873 return;
1874}
1875
1876//=============================================================================================================
1877
1878Eigen::MatrixXf FwdBemModel::fwd_bem_lin_field_coeff(FwdCoilSet *coils, int method) /* Which integration formula to use */
1879/*
1880 * Compute the weighting factors to obtain the magnetic field
1881 * in the linear potential approximation
1882 */
1883{
1884 MNESurface* surf;
1885 MNETriangle* tri;
1886 FwdCoil* coil;
1887 FwdCoilSet::UPtr tcoils;
1888 int ntri;
1889 int j,k,p,pp,off,s;
1890 Eigen::Vector3d res, one;
1891 float mult;
1892 linFieldIntFunc func;
1893
1894 if (solution.size() == 0) {
1895 qWarning("Solution matrix missing in fwd_bem_lin_field_coeff");
1896 return Eigen::MatrixXf();
1897 }
1899 qWarning("BEM method should be linear collocation for fwd_bem_lin_field_coeff");
1900 return Eigen::MatrixXf();
1901 }
1902 if (coils->coord_frame != FIFFV_COORD_MRI) {
1903 if (coils->coord_frame == FIFFV_COORD_HEAD) {
1904 if (head_mri_t.isEmpty()) {
1905 qWarning("head -> mri coordinate transform missing in fwd_bem_lin_field_coeff");
1906 return Eigen::MatrixXf();
1907 }
1908 else {
1909 /*
1910 * Make a transformed duplicate
1911 */
1912 if ((tcoils = coils->dup_coil_set(head_mri_t)) == nullptr)
1913 return Eigen::MatrixXf();
1914 coils = tcoils.get();
1915 }
1916 }
1917 else {
1918 qWarning("Incompatible coil coordinate frame %d for fwd_bem_field_coeff",coils->coord_frame);
1919 return Eigen::MatrixXf();
1920 }
1921 }
1922 if (method == FWD_BEM_LIN_FIELD_FERGUSON)
1924 else if (method == FWD_BEM_LIN_FIELD_URANKAR)
1926 else
1928
1929 Eigen::MatrixXf coeff = Eigen::MatrixXf::Zero(coils->ncoil(), nsol);
1930 /*
1931 * Process each of the surfaces
1932 */
1933 for (s = 0, off = 0; s < nsurf; s++) {
1934 surf = surfs[s].get();
1935 ntri = surf->ntri;
1936 tri = surf->tris.data();
1937 mult = field_mult[s];
1938
1939 for (k = 0; k < ntri; k++,tri++) {
1940 for (j = 0; j < coils->ncoil(); j++) {
1941 coil = coils->coils[j].get();
1942 res.setZero();
1943 /*
1944 * Accumulate the coefficients for each triangle node...
1945 */
1946 for (p = 0; p < coil->np; p++) {
1947 func(coil->pos(p),coil->dir(p),*tri,one);
1948 res += coil->w[p] * one;
1949 }
1950 /*
1951 * Add these to the corresponding coefficient matrix
1952 * elements...
1953 */
1954 for (pp = 0; pp < 3; pp++)
1955 coeff(j,tri->vert[pp]+off) = coeff(j,tri->vert[pp]+off) + mult*res[pp];
1956 }
1957 }
1958 off = off + surf->np;
1959 }
1960 /*
1961 * Discard the duplicate
1962 */
1963 return coeff;
1964}
1965
1966//=============================================================================================================
1967
1969/*
1970 * Set up for computing the solution at a set of coils
1971 */
1972{
1973 Eigen::MatrixXf sol;
1974 FwdBemSolution* csol = nullptr;
1975
1976 if (solution.size() == 0) {
1977 qWarning("Solution not computed in fwd_bem_specify_coils");
1978 return FAIL;
1979 }
1980 if(coils)
1981 coils->user_data.reset();
1982 if (!coils || coils->ncoil() == 0)
1983 return OK;
1985 sol = fwd_bem_field_coeff(coils);
1986 else if (bem_method == FWD_BEM_LINEAR_COLL)
1988 else {
1989 qWarning("Unknown BEM method in fwd_bem_specify_coils : %d",bem_method);
1990 return FAIL;
1991 }
1992 if (sol.size() == 0)
1993 return FAIL;
1994 coils->user_data = std::make_unique<FwdBemSolution>();
1995 csol = coils->user_data.get();
1996
1997 csol->ncoil = coils->ncoil();
1998 csol->np = nsol;
1999 csol->solution = sol * solution;
2000
2001 return OK;
2002}
2003
2004//=============================================================================================================
2005
2006void FwdBemModel::fwd_bem_lin_field_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> B)
2007/*
2008 * Calculate the magnetic field in a set of coils
2009 */
2010{
2011 int s,k,p,np;
2012 FwdCoil* coil;
2013 float mult;
2014 Eigen::Vector3f my_rd = rd;
2015 Eigen::Vector3f my_Q = Q;
2016 FwdBemSolution* sol = coils.user_data.get();
2017 /*
2018 * Infinite-medium potentials
2019 */
2020 if (v0.size() == 0)
2021 v0.resize(nsol);
2022 float* v0p = v0.data();
2023 /*
2024 * The dipole location and orientation must be transformed
2025 */
2026 if (!head_mri_t.isEmpty()) {
2029 }
2030 /*
2031 * Compute the inifinite-medium potentials at the vertices
2032 */
2033 for (s = 0, p = 0; s < nsurf; s++) {
2034 np = surfs[s]->np;
2035 mult = source_mult[s];
2036 for (k = 0; k < np; k++)
2037 v0p[p++] = mult*fwd_bem_inf_pot(my_rd,my_Q,surfs[s]->point(k));
2038 }
2039 /*
2040 * Primary current contribution
2041 * (can be calculated in the coil/dipole coordinates)
2042 */
2043 for (k = 0; k < coils.ncoil(); k++) {
2044 coil = coils.coils[k].get();
2045 B[k] = 0.0;
2046 for (p = 0; p < coil->np; p++)
2047 B[k] = B[k] + coil->w[p]*fwd_bem_inf_field(
2048 rd,
2049 Q,
2050 coil->pos(p),
2051 coil->dir(p));
2052 }
2053 /*
2054 * Volume current contribution
2055 */
2056 for (k = 0; k < coils.ncoil(); k++)
2057 B[k] = B[k] + sol->solution.row(k).dot(v0);
2058 /*
2059 * Scale correctly
2060 */
2061 for (k = 0; k < coils.ncoil(); k++)
2062 B[k] = MAG_FACTOR*B[k];
2063 return;
2064}
2065
2066//=============================================================================================================
2067
2068void FwdBemModel::fwd_bem_field_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> B)
2069/*
2070 * Calculate the magnetic field in a set of coils
2071 */
2072{
2073 int s,k,p,ntri;
2074 FwdCoil* coil;
2075 MNETriangle* tri;
2076 float mult;
2077 Eigen::Vector3f my_rd = rd;
2078 Eigen::Vector3f my_Q = Q;
2079 FwdBemSolution* sol = coils.user_data.get();
2080 /*
2081 * Infinite-medium potentials
2082 */
2083 if (v0.size() == 0)
2084 v0.resize(nsol);
2085 float* v0p = v0.data();
2086 /*
2087 * The dipole location and orientation must be transformed
2088 */
2089 if (!head_mri_t.isEmpty()) {
2092 }
2093 /*
2094 * Compute the inifinite-medium potentials at the centers of the triangles
2095 */
2096 for (s = 0, p = 0; s < nsurf; s++) {
2097 ntri = surfs[s]->ntri;
2098 tri = surfs[s]->tris.data();
2099 mult = source_mult[s];
2100 for (k = 0; k < ntri; k++, tri++)
2101 v0p[p++] = mult*fwd_bem_inf_pot(my_rd,my_Q,tri->cent);
2102 }
2103 /*
2104 * Primary current contribution
2105 * (can be calculated in the coil/dipole coordinates)
2106 */
2107 for (k = 0; k < coils.ncoil(); k++) {
2108 coil = coils.coils[k].get();
2109 B[k] = 0.0;
2110 for (p = 0; p < coil->np; p++)
2111 B[k] = B[k] + coil->w[p]*fwd_bem_inf_field(
2112 rd,
2113 Q,
2114 coil->pos(p),
2115 coil->dir(p));
2116 }
2117 /*
2118 * Volume current contribution
2119 */
2120 for (k = 0; k < coils.ncoil(); k++)
2121 B[k] = B[k] + sol->solution.row(k).dot(v0);
2122 /*
2123 * Scale correctly
2124 */
2125 for (k = 0; k < coils.ncoil(); k++)
2126 B[k] = MAG_FACTOR*B[k];
2127 return;
2128}
2129
2130//=============================================================================================================
2131
2132void FwdBemModel::fwd_bem_field_grad_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> xgrad, Eigen::Ref<Eigen::VectorXf> ygrad, Eigen::Ref<Eigen::VectorXf> zgrad)
2133/*
2134 * Calculate the magnetic field in a set of coils
2135 */
2136{
2137 FwdBemSolution* sol = coils.user_data.get();
2138 int s,k,p,ntri,pp;
2139 FwdCoil* coil;
2140 MNETriangle* tri;
2141 float mult;
2142 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
2143 Eigen::Vector3f ee, mri_ee;
2144 Eigen::Vector3f mri_rd = rd;
2145 Eigen::Vector3f mri_Q = Q;
2146 /*
2147 * Infinite-medium potentials
2148 */
2149 if (v0.size() == 0)
2150 v0.resize(nsol);
2151 float* v0p = v0.data();
2152 /*
2153 * The dipole location and orientation must be transformed
2154 */
2155 if (!head_mri_t.isEmpty()) {
2158 }
2159 for (pp = X; pp <= Z; pp++) {
2160 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
2161 /*
2162 * Select the correct gradient component
2163 */
2164 ee = Eigen::Vector3f::Unit(pp);
2165 mri_ee = ee;
2166 if (!head_mri_t.isEmpty())
2168 /*
2169 * Compute the inifinite-medium potential derivatives at the centers of the triangles
2170 */
2171 for (s = 0, p = 0; s < nsurf; s++) {
2172 ntri = surfs[s]->ntri;
2173 tri = surfs[s]->tris.data();
2174 mult = source_mult[s];
2175 for (k = 0; k < ntri; k++, tri++) {
2176 v0p[p++] = mult*fwd_bem_inf_pot_der(mri_rd,mri_Q,tri->cent,mri_ee);
2177 }
2178 }
2179 /*
2180 * Primary current contribution
2181 * (can be calculated in the coil/dipole coordinates)
2182 */
2183 for (k = 0; k < coils.ncoil(); k++) {
2184 coil = coils.coils[k].get();
2185 grad[k] = 0.0;
2186 for (p = 0; p < coil->np; p++)
2187 grad[k] = grad[k] + coil->w[p]*fwd_bem_inf_field_der(
2188 rd,
2189 Q,
2190 coil->pos(p),
2191 coil->dir(p),
2192 ee);
2193 }
2194 /*
2195 * Volume current contribution
2196 */
2197 for (k = 0; k < coils.ncoil(); k++)
2198 grad[k] = grad[k] + sol->solution.row(k).dot(v0);
2199 /*
2200 * Scale correctly
2201 */
2202 for (k = 0; k < coils.ncoil(); k++)
2203 grad[k] = MAG_FACTOR*grad[k];
2204 }
2205 return;
2206}
2207
2208//=============================================================================================================
2209
2210float FwdBemModel::fwd_bem_inf_field_der(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp, const Eigen::Vector3f& dir, const Eigen::Vector3f& comp)
2211/*
2212 * Derivative of the infinite-medium magnetic field with respect to
2213 * one of the dipole position coordinates (without \mu_0/4\pi)
2214 */
2215{
2216 Eigen::Vector3f diff = rp - rd;
2217 float diff2 = diff.squaredNorm();
2218 float diff3 = std::sqrt(diff2) * diff2;
2219 float diff5 = diff3 * diff2;
2220 Eigen::Vector3f cr = Q.cross(diff);
2221 Eigen::Vector3f crn = dir.cross(Q);
2222
2223 return 3 * cr.dot(dir) * comp.dot(diff) / diff5 - comp.dot(crn) / diff3;
2224}
2225
2226//=============================================================================================================
2227
2228float FwdBemModel::fwd_bem_inf_pot_der(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp, const Eigen::Vector3f& comp)
2229/*
2230 * Derivative of the infinite-medium potential with respect to one of
2231 * the dipole position coordinates
2232 */
2233{
2234 Eigen::Vector3f diff = rp - rd;
2235 float diff2 = diff.squaredNorm();
2236 float diff3 = std::sqrt(diff2) * diff2;
2237 float diff5 = diff3 * diff2;
2238
2239 float res = 3 * Q.dot(diff) * comp.dot(diff) / diff5 - comp.dot(Q) / diff3;
2240 return res / (4.0 * M_PI);
2241}
2242
2243//=============================================================================================================
2244
2245void FwdBemModel::fwd_bem_lin_field_grad_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> xgrad, Eigen::Ref<Eigen::VectorXf> ygrad, Eigen::Ref<Eigen::VectorXf> zgrad)
2246/*
2247 * Calculate the gradient with respect to dipole position coordinates in a set of coils
2248 */
2249{
2250 FwdBemSolution* sol = coils.user_data.get();
2251
2252 int s,k,p,np,pp;
2253 FwdCoil *coil;
2254 float mult;
2255 Eigen::Vector3f ee, mri_ee;
2256 Eigen::Vector3f mri_rd = rd;
2257 Eigen::Vector3f mri_Q = Q;
2258 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
2259 /*
2260 * Space for infinite-medium potentials
2261 */
2262 if (v0.size() == 0)
2263 v0.resize(nsol);
2264 float* v0p = v0.data();
2265 /*
2266 * The dipole location and orientation must be transformed
2267 */
2268 if (!head_mri_t.isEmpty()) {
2271 }
2272 for (pp = X; pp <= Z; pp++) {
2273 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
2274 /*
2275 * Select the correct gradient component
2276 */
2277 ee = Eigen::Vector3f::Unit(pp);
2278 mri_ee = ee;
2279 if (!head_mri_t.isEmpty())
2281 /*
2282 * Compute the inifinite-medium potentials at the vertices
2283 */
2284 for (s = 0, p = 0; s < nsurf; s++) {
2285 np = surfs[s]->np;
2286 mult = source_mult[s];
2287
2288 for (k = 0; k < np; k++)
2289 v0p[p++] = mult*fwd_bem_inf_pot_der(mri_rd,mri_Q,surfs[s]->point(k),mri_ee);
2290 }
2291 /*
2292 * Primary current contribution
2293 * (can be calculated in the coil/dipole coordinates)
2294 */
2295 for (k = 0; k < coils.ncoil(); k++) {
2296 coil = coils.coils[k].get();
2297 grad[k] = 0.0;
2298 for (p = 0; p < coil->np; p++)
2299 grad[k] = grad[k] + coil->w[p]*fwd_bem_inf_field_der(
2300 rd,
2301 Q,
2302 coil->pos(p),
2303 coil->dir(p),
2304 ee);
2305 }
2306 /*
2307 * Volume current contribution
2308 */
2309 for (k = 0; k < coils.ncoil(); k++)
2310 grad[k] = grad[k] + sol->solution.row(k).dot(v0);
2311 /*
2312 * Scale correctly
2313 */
2314 for (k = 0; k < coils.ncoil(); k++)
2315 grad[k] = MAG_FACTOR*grad[k];
2316 }
2317 return;
2318}
2319
2320//=============================================================================================================
2321
2322int FwdBemModel::fwd_bem_field(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &coils, Eigen::Ref<Eigen::VectorXf> B, void *client) /* The model */
2323/*
2324 * This version calculates the magnetic field in a set of coils
2325 * Call fwd_bem_specify_coils first to establish the coil-specific
2326 * solution matrix
2327 */
2328{
2329 auto* m = static_cast<FwdBemModel*>(client);
2330 FwdBemSolution* sol = coils.user_data.get();
2331
2332 if (!m) {
2333 qWarning("No BEM model specified to fwd_bem_field");
2334 return FAIL;
2335 }
2336 if (!sol || sol->solution.size() == 0 || sol->ncoil != coils.ncoil()) {
2337 qWarning("No appropriate coil-specific data available in fwd_bem_field");
2338 return FAIL;
2339 }
2340 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
2341 m->fwd_bem_field_calc(rd,Q,coils,B);
2342 }
2343 else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
2344 m->fwd_bem_lin_field_calc(rd,Q,coils,B);
2345 }
2346 else {
2347 qWarning("Unknown BEM method : %d",m->bem_method);
2348 return FAIL;
2349 }
2350 return OK;
2351}
2352
2353//=============================================================================================================
2354
2355int FwdBemModel::fwd_bem_field_grad(const Eigen::Vector3f& rd,
2356 const Eigen::Vector3f& Q,
2357 FwdCoilSet &coils,
2358 Eigen::Ref<Eigen::VectorXf> Bval,
2359 Eigen::Ref<Eigen::VectorXf> xgrad,
2360 Eigen::Ref<Eigen::VectorXf> ygrad,
2361 Eigen::Ref<Eigen::VectorXf> zgrad,
2362 void *client) /* Client data to be passed to some foward modelling routines */
2363{
2364 auto* m = static_cast<FwdBemModel*>(client);
2365 FwdBemSolution* sol = coils.user_data.get();
2366
2367 if (!m) {
2368 qCritical("No BEM model specified to fwd_bem_field");
2369 return FAIL;
2370 }
2371
2372 if (!sol || sol->solution.size() == 0 || sol->ncoil != coils.ncoil()) {
2373 qCritical("No appropriate coil-specific data available in fwd_bem_field");
2374 return FAIL;
2375 }
2376
2377 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
2378 int n = coils.ncoil();
2379 m->fwd_bem_field_calc(rd,Q,coils,Bval);
2380
2381 m->fwd_bem_field_grad_calc(rd,Q,coils,xgrad,ygrad,zgrad);
2382 } else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
2383 int n = coils.ncoil();
2384 m->fwd_bem_lin_field_calc(rd,Q,coils,Bval);
2385
2386 m->fwd_bem_lin_field_grad_calc(rd,Q,coils,xgrad,ygrad,zgrad);
2387 } else {
2388 qCritical("Unknown BEM method : %d",m->bem_method);
2389 return FAIL;
2390 }
2391
2392 return OK;
2393}
2394
2395//=============================================================================================================
2396
2398/*
2399 * Compute the MEG or EEG forward solution for one source space
2400 * and possibly for only one source component
2401 */
2402{
2403 MNESourceSpace* s = a->s;
2404 int j,p,q;
2405
2406 int ncoil = a->coils_els->ncoil();
2407 Eigen::MatrixXf tmp_vec_res(3, ncoil); /* Only needed for vec_field_pot (3 rows → 3 columns) */
2408
2409 auto fail = [&]() { a->stat = FAIL; };
2410
2411 p = a->off;
2412 q = 3*a->off;
2413 if (a->fixed_ori) { /* The normal source component only */
2414 if (a->field_pot_grad && a->res_grad) { /* Gradient requested? */
2415 for (j = 0; j < s->np; j++) {
2416 if (s->inuse[j]) {
2417 if (a->field_pot_grad(s->point(j),
2418 s->normal(j),
2419 *a->coils_els,
2420 a->res->col(p),
2421 a->res_grad->col(q),
2422 a->res_grad->col(q+1),
2423 a->res_grad->col(q+2),
2424 a->client) != OK) {
2425 fail(); return;
2426 }
2427 q = q + 3;
2428 p++;
2429 }
2430 }
2431 } else {
2432 for (j = 0; j < s->np; j++)
2433 if (s->inuse[j]) {
2434 if (a->field_pot(s->point(j),
2435 s->normal(j),
2436 *a->coils_els,
2437 a->res->col(p),
2438 a->client) != OK) {
2439 fail(); return;
2440 }
2441 p++;
2442 }
2443 }
2444 }
2445 else { /* All source components */
2446 if (a->field_pot_grad && a->res_grad) { /* Gradient requested? */
2447 for (j = 0; j < s->np; j++) {
2448 if (s->inuse[j]) {
2449 if (a->comp < 0) { /* Compute all components */
2450 if (a->field_pot_grad(s->point(j),
2451 Qx, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q+1), a->res_grad->col(q+2),
2452 a->client) != OK) {
2453 fail(); return;
2454 }
2455 q = q + 3; p++;
2456 if (a->field_pot_grad(s->point(j),
2457 Qy, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q+1), a->res_grad->col(q+2),
2458 a->client) != OK) {
2459 fail(); return;
2460 }
2461 q = q + 3; p++;
2462 if (a->field_pot_grad(s->point(j),
2463 Qz, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q+1), a->res_grad->col(q+2),
2464 a->client) != OK) {
2465 fail(); return;
2466 }
2467 q = q + 3; p++;
2468 }
2469 else if (a->comp == 0) { /* Compute x component */
2470 if (a->field_pot_grad(s->point(j),
2471 Qx, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q+1), a->res_grad->col(q+2),
2472 a->client) != OK) {
2473 fail(); return;
2474 }
2475 q = q + 3; p++;
2476 q = q + 3; p++;
2477 q = q + 3; p++;
2478 }
2479 else if (a->comp == 1) { /* Compute y component */
2480 q = q + 3; p++;
2481 if (a->field_pot_grad(s->point(j),
2482 Qy, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q+1), a->res_grad->col(q+2),
2483 a->client) != OK) {
2484 fail(); return;
2485 }
2486 q = q + 3; p++;
2487 q = q + 3; p++;
2488 }
2489 else if (a->comp == 2) { /* Compute z component */
2490 q = q + 3; p++;
2491 q = q + 3; p++;
2492 if (a->field_pot_grad(s->point(j),
2493 Qz, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q+1), a->res_grad->col(q+2),
2494 a->client) != OK) {
2495 fail(); return;
2496 }
2497 q = q + 3; p++;
2498 }
2499 }
2500 }
2501 }
2502 else {
2503 for (j = 0; j < s->np; j++) {
2504 if (s->inuse[j]) {
2505 if (a->vec_field_pot) {
2506 if (a->vec_field_pot(s->point(j),*a->coils_els,tmp_vec_res,a->client) != OK) {
2507 fail(); return;
2508 }
2509 a->res->col(p++) = tmp_vec_res.row(0).transpose();
2510 a->res->col(p++) = tmp_vec_res.row(1).transpose();
2511 a->res->col(p++) = tmp_vec_res.row(2).transpose();
2512 }
2513 else {
2514 if (a->comp < 0) { /* Compute all components here */
2515 if (a->field_pot(s->point(j),Qx,*a->coils_els,a->res->col(p++),a->client) != OK) {
2516 fail(); return;
2517 }
2518 if (a->field_pot(s->point(j),Qy,*a->coils_els,a->res->col(p++),a->client) != OK) {
2519 fail(); return;
2520 }
2521 if (a->field_pot(s->point(j),Qz,*a->coils_els,a->res->col(p++),a->client) != OK) {
2522 fail(); return;
2523 }
2524 }
2525 else if (a->comp == 0) { /* Compute x component */
2526 if (a->field_pot(s->point(j),Qx,*a->coils_els,a->res->col(p++),a->client) != OK) {
2527 fail(); return;
2528 }
2529 p++; p++;
2530 }
2531 else if (a->comp == 1) { /* Compute y component */
2532 p++;
2533 if (a->field_pot(s->point(j),Qy,*a->coils_els,a->res->col(p++),a->client) != OK) {
2534 fail(); return;
2535 }
2536 p++;
2537 }
2538 else if (a->comp == 2) { /* Compute z component */
2539 p++; p++;
2540 if (a->field_pot(s->point(j),Qz,*a->coils_els,a->res->col(p++),a->client) != OK) {
2541 fail(); return;
2542 }
2543 }
2544 }
2545 }
2546 }
2547 }
2548 }
2549 a->stat = OK;
2550 return;
2551}
2552
2553//=============================================================================================================
2554
2555int FwdBemModel::compute_forward_meg(std::vector<std::unique_ptr<MNESourceSpace>>& spaces,
2556 FwdCoilSet *coils,
2557 FwdCoilSet *comp_coils,
2558 MNECTFCompDataSet *comp_data,
2559 bool fixed_ori,
2560 const Vector3f &r0,
2561 bool use_threads,
2562 FiffNamedMatrix& resp,
2563 FiffNamedMatrix& resp_grad,
2564 bool bDoGrad)
2565/*
2566 * Compute the MEG forward solution
2567 * Use either the sphere model or BEM in the calculations
2568 */
2569{
2570 Eigen::MatrixXf res_mat; /* The forward solution matrix (ncoil x nsources) */
2571 Eigen::MatrixXf res_grad_mat; /* The gradient (ncoil x 3*nsources) */
2572 MatrixXd matRes;
2573 MatrixXd matResGrad;
2574 int nrow = 0;
2575 FwdCompData *comp = nullptr;
2576 fwdFieldFunc field; /* Computes the field for one dipole orientation */
2577 fwdVecFieldFunc vec_field; /* Computes the field for all dipole orientations */
2578 fwdFieldGradFunc field_grad; /* Computes the field and gradient with respect to dipole position
2579 * for one dipole orientation */
2580 int nmeg = coils->ncoil();/* Number of channels */
2581 int nsource; /* Total number of sources */
2582 int nspace = static_cast<int>(spaces.size());
2583 int k,p,q,off;
2584 QStringList names; /* Channel names */
2585 void *client;
2586 FwdThreadArg::UPtr one_arg;
2587 int nproc = QThread::idealThreadCount();
2588 QStringList emptyList;
2589
2590 auto cleanup_fail = [&]() { one_arg.reset(); delete comp; return FAIL; };
2591
2592 if (true) {
2593 /*
2594 * Use the new compensated field computation
2595 * It works the same way independent of whether or not the compensation is in effect
2596 */
2597#ifdef TEST
2598 qInfo("Using differences.");
2599 comp = FwdCompData::fwd_make_comp_data(comp_data,
2600 coils,comp_coils,
2602 nullptr,
2603 my_bem_field_grad,
2604 this);
2605#else
2606 comp = FwdCompData::fwd_make_comp_data(comp_data,
2607 coils,
2608 comp_coils,
2610 nullptr,
2612 this);
2613#endif
2614 if (!comp)
2615 return cleanup_fail();
2616 /*
2617 * Field computation matrices...
2618 */
2619 qInfo("Composing the field computation matrix...");
2620 if (fwd_bem_specify_coils(coils) == FAIL)
2621 return cleanup_fail();
2622 qInfo("[done]");
2623
2624 if (comp->set && comp->set->current) { /* Test just to specify confusing output */
2625 qInfo("Composing the field computation matrix (compensation coils)...");
2627 return cleanup_fail();
2628 qInfo("[done]");
2629 }
2631 vec_field = nullptr;
2633 client = comp;
2634 }
2635 else {
2636 /*
2637 * Use the new compensated field computation
2638 * It works the same way independent of whether or not the compensation is in effect
2639 */
2640#ifdef TEST
2641 qInfo("Using differences.");
2642 comp = FwdCompData::fwd_make_comp_data(comp_data,coils,comp_coils,
2645 my_sphere_field_grad,
2646 const_cast<Vector3f*>(&r0));
2647#else
2648 comp = FwdCompData::fwd_make_comp_data(comp_data,coils,comp_coils,
2652 const_cast<Vector3f*>(&r0));
2653#endif
2654 if (!comp)
2655 return cleanup_fail();
2659 client = comp;
2660 }
2661 /*
2662 * Count the sources
2663 */
2664 for (k = 0, nsource = 0; k < nspace; k++)
2665 nsource += spaces[k]->nuse;
2666 /*
2667 * Allocate space for the solution
2668 */
2669 {
2670 int ncols = fixed_ori ? nsource : 3*nsource;
2671 res_mat = Eigen::MatrixXf::Zero(nmeg, ncols);
2672 }
2673 if (bDoGrad) {
2674 int ncols = fixed_ori ? 3*nsource : 3*3*nsource;
2675 res_grad_mat = Eigen::MatrixXf::Zero(nmeg, ncols);
2676 }
2677 /*
2678 * Set up the argument for the field computation
2679 */
2680 one_arg = std::make_unique<FwdThreadArg>();
2681 one_arg->res = &res_mat;
2682 one_arg->res_grad = bDoGrad ? &res_grad_mat : nullptr;
2683 one_arg->off = 0;
2684 one_arg->coils_els = coils;
2685 one_arg->client = client;
2686 one_arg->s = nullptr;
2687 one_arg->fixed_ori = fixed_ori;
2688 one_arg->field_pot = field;
2689 one_arg->vec_field_pot = vec_field;
2690 one_arg->field_pot_grad = field_grad;
2691
2692 if (nproc < 2)
2693 use_threads = false;
2694
2695 if (use_threads) {
2696 int nthread = (fixed_ori || vec_field || nproc < 6) ? nspace : 3*nspace;
2697 std::vector<FwdThreadArg::UPtr> args;
2698 int stat;
2699 /*
2700 * We need copies to allocate separate workspace for each thread
2701 */
2702 if (fixed_ori || vec_field || nproc < 6) {
2703 for (k = 0, off = 0; k < nthread; k++) {
2704 auto t_arg = FwdThreadArg::create_meg_multi_thread_duplicate(*one_arg,true);
2705 t_arg->s = spaces[k].get();
2706 t_arg->off = off;
2707 off = fixed_ori ? off + spaces[k]->nuse : off + 3*spaces[k]->nuse;
2708 args.push_back(std::move(t_arg));
2709 }
2710 qInfo("%d processors. I will use one thread for each of the %d source spaces.",
2711 nproc,nspace);
2712 }
2713 else {
2714 for (k = 0, off = 0, q = 0; k < nspace; k++) {
2715 for (p = 0; p < 3; p++,q++) {
2716 auto t_arg = FwdThreadArg::create_meg_multi_thread_duplicate(*one_arg,true);
2717 t_arg->s = spaces[k].get();
2718 t_arg->off = off;
2719 t_arg->comp = p;
2720 args.push_back(std::move(t_arg));
2721 }
2722 off = fixed_ori ? off + spaces[k]->nuse : off + 3*spaces[k]->nuse;
2723 }
2724 qInfo("%d processors. I will use %d threads : %d source spaces x 3 source components.",
2725 nproc,nthread,nspace);
2726 }
2727 qInfo("Computing MEG at %d source locations (%s orientations)...",
2728 nsource,fixed_ori ? "fixed" : "free");
2729 /*
2730 * Ready to start the threads & Wait for them to complete
2731 */
2732 QtConcurrent::blockingMap(args, [](FwdThreadArg::UPtr& a) {
2734 });
2735 /*
2736 * Check the results
2737 */
2738 for (k = 0, stat = OK; k < nthread; k++)
2739 if (args[k]->stat != OK) {
2740 stat = FAIL;
2741 break;
2742 }
2743 if (stat != OK)
2744 return cleanup_fail();
2745 }
2746 else {
2747 qInfo("Computing MEG at %d source locations (%s orientations, no threads)...",
2748 nsource,fixed_ori ? "fixed" : "free");
2749 for (k = 0, off = 0; k < nspace; k++) {
2750 one_arg->s = spaces[k].get();
2751 one_arg->off = off;
2752 meg_eeg_fwd_one_source_space(one_arg.get());
2753 if (one_arg->stat != OK)
2754 return cleanup_fail();
2755 off = fixed_ori ? off + one_arg->s->nuse : off + 3*one_arg->s->nuse;
2756 }
2757 }
2758 qInfo("done.");
2759 {
2760 QStringList orig_names;
2761 for (k = 0; k < nmeg; k++)
2762 orig_names.append(coils->coils[k]->chname);
2763 names = orig_names;
2764 }
2765 one_arg.reset();
2766 delete comp;
2767 comp = nullptr;
2768
2769 // Store solution: res_mat is (nmeg x nsources), transpose to (nsources x nmeg)
2770 nrow = fixed_ori ? nsource : 3*nsource;
2771 resp.nrow = nrow;
2772 resp.ncol = nmeg;
2773 resp.row_names = emptyList;
2774 resp.col_names = names;
2775 resp.data = res_mat.transpose().cast<double>();
2777
2778 if (bDoGrad && res_grad_mat.size() > 0) {
2779 nrow = fixed_ori ? 3*nsource : 3*3*nsource;
2780 resp_grad.nrow = nrow;
2781 resp_grad.ncol = nmeg;
2782 resp_grad.row_names = emptyList;
2783 resp_grad.col_names = names;
2784 resp_grad.data = res_grad_mat.transpose().cast<double>();
2785 resp_grad.transpose_named_matrix();
2786 }
2787 return OK;
2788}
2789
2790//=============================================================================================================
2791
2792int FwdBemModel::compute_forward_eeg(std::vector<std::unique_ptr<MNESourceSpace>>& spaces,
2793 FwdCoilSet *els,
2794 bool fixed_ori,
2795 FwdEegSphereModel *eeg_model,
2796 bool use_threads,
2797 FiffNamedMatrix& resp,
2798 FiffNamedMatrix& resp_grad,
2799 bool bDoGrad)
2800/*
2801 * Compute the EEG forward solution
2802 * Use either the sphere model or BEM in the calculations
2803 */
2804{
2805 Eigen::MatrixXf res_mat; /* The forward solution matrix (neeg x nsources) */
2806 Eigen::MatrixXf res_grad_mat; /* The gradient (neeg x 3*nsources) */
2807 MatrixXd matRes;
2808 MatrixXd matResGrad;
2809 int nrow = 0;
2810 fwdFieldFunc pot; /* Computes the potentials for one dipole orientation */
2811 fwdVecFieldFunc vec_pot; /* Computes the potentials for all dipole orientations */
2812 fwdFieldGradFunc pot_grad; /* Computes the potential and gradient with respect to dipole position
2813 * for one dipole orientation */
2814 int nsource; /* Total number of sources */
2815 int nspace = static_cast<int>(spaces.size());
2816 int neeg = els->ncoil(); /* Number of channels */
2817 int k,p,q,off;
2818 QStringList names; /* Channel names */
2819 void *client;
2820 FwdThreadArg::UPtr one_arg;
2821 int nproc = QThread::idealThreadCount();
2822 QStringList emptyList;
2823 /*
2824 * Count the sources
2825 */
2826 for (k = 0, nsource = 0; k < nspace; k++)
2827 nsource += spaces[k]->nuse;
2828
2829 if (true) {
2830 if (fwd_bem_specify_els(els) == FAIL)
2831 return FAIL;
2832 client = this;
2833 pot = fwd_bem_pot_els;
2834 vec_pot = nullptr;
2835#ifdef TEST
2836 qInfo("Using differences.");
2837 pot_grad = my_bem_pot_grad;
2838#else
2839 pot_grad = fwd_bem_pot_grad_els;
2840#endif
2841 }
2842 else {
2843 if (eeg_model->nfit == 0) {
2844 qInfo("Using the standard series expansion for a multilayer sphere model for EEG");
2846 vec_pot = nullptr;
2847 pot_grad = nullptr;
2848 }
2849 else {
2850 qInfo("Using the equivalent source approach in the homogeneous sphere for EEG");
2854 }
2855 client = eeg_model;
2856 }
2857 /*
2858 * Allocate space for the solution
2859 */
2860 {
2861 int ncols = fixed_ori ? nsource : 3*nsource;
2862 res_mat = Eigen::MatrixXf::Zero(neeg, ncols);
2863 }
2864 if (bDoGrad) {
2865 if (!pot_grad) {
2866 qCritical("EEG gradient calculation function not available");
2867 return FAIL;
2868 }
2869 int ncols = fixed_ori ? 3*nsource : 3*3*nsource;
2870 res_grad_mat = Eigen::MatrixXf::Zero(neeg, ncols);
2871 }
2872 /*
2873 * Set up the argument for the field computation
2874 */
2875 one_arg = std::make_unique<FwdThreadArg>();
2876 one_arg->res = &res_mat;
2877 one_arg->res_grad = bDoGrad ? &res_grad_mat : nullptr;
2878 one_arg->off = 0;
2879 one_arg->coils_els = els;
2880 one_arg->client = client;
2881 one_arg->s = nullptr;
2882 one_arg->fixed_ori = fixed_ori;
2883 one_arg->field_pot = pot;
2884 one_arg->vec_field_pot = vec_pot;
2885 one_arg->field_pot_grad = pot_grad;
2886
2887 if (nproc < 2)
2888 use_threads = false;
2889
2890 if (use_threads) {
2891 int nthread = (fixed_ori || vec_pot || nproc < 6) ? nspace : 3*nspace;
2892 std::vector<FwdThreadArg::UPtr> args;
2893 int stat;
2894 /*
2895 * We need copies to allocate separate workspace for each thread
2896 */
2897 if (fixed_ori || vec_pot || nproc < 6) {
2898 for (k = 0, off = 0; k < nthread; k++) {
2899 auto t_arg = FwdThreadArg::create_eeg_multi_thread_duplicate(*one_arg,true);
2900 t_arg->s = spaces[k].get();
2901 t_arg->off = off;
2902 off = fixed_ori ? off + spaces[k]->nuse : off + 3*spaces[k]->nuse;
2903 args.push_back(std::move(t_arg));
2904 }
2905 qInfo("%d processors. I will use one thread for each of the %d source spaces.",nproc,nspace);
2906 }
2907 else {
2908 for (k = 0, off = 0, q = 0; k < nspace; k++) {
2909 for (p = 0; p < 3; p++,q++) {
2910 auto t_arg = FwdThreadArg::create_eeg_multi_thread_duplicate(*one_arg,true);
2911 t_arg->s = spaces[k].get();
2912 t_arg->off = off;
2913 t_arg->comp = p;
2914 args.push_back(std::move(t_arg));
2915 }
2916 off = fixed_ori ? off + spaces[k]->nuse : off + 3*spaces[k]->nuse;
2917 }
2918 qInfo("%d processors. I will use %d threads : %d source spaces x 3 source components.",nproc,nthread,nspace);
2919 }
2920 qInfo("Computing EEG at %d source locations (%s orientations)...",
2921 nsource,fixed_ori ? "fixed" : "free");
2922 /*
2923 * Ready to start the threads & Wait for them to complete
2924 */
2925 QtConcurrent::blockingMap(args, [](FwdThreadArg::UPtr& a) {
2927 });
2928 /*
2929 * Check the results
2930 */
2931 for (k = 0, stat = OK; k < nthread; k++)
2932 if (args[k]->stat != OK) {
2933 stat = FAIL;
2934 break;
2935 }
2936 if (stat != OK)
2937 return FAIL;
2938 }
2939 else {
2940 qInfo("Computing EEG at %d source locations (%s orientations, no threads)...",
2941 nsource,fixed_ori ? "fixed" : "free");
2942 for (k = 0, off = 0; k < nspace; k++) {
2943 one_arg->s = spaces[k].get();
2944 one_arg->off = off;
2945 meg_eeg_fwd_one_source_space(one_arg.get());
2946 if (one_arg->stat != OK)
2947 return FAIL;
2948 off = fixed_ori ? off + one_arg->s->nuse : off + 3*one_arg->s->nuse;
2949 }
2950 }
2951 qInfo("done.");
2952 {
2953 QStringList orig_names;
2954 for (k = 0; k < neeg; k++)
2955 orig_names.append(els->coils[k]->chname);
2956 names = orig_names;
2957 }
2958 one_arg.reset();
2959
2960 // Store solution: res_mat is (neeg x nsources), transpose to (nsources x neeg)
2961 nrow = fixed_ori ? nsource : 3*nsource;
2962 resp.nrow = nrow;
2963 resp.ncol = neeg;
2964 resp.row_names = emptyList;
2965 resp.col_names = names;
2966 resp.data = res_mat.transpose().cast<double>();
2968
2969 if (bDoGrad && res_grad_mat.size() > 0) {
2970 nrow = fixed_ori ? 3*nsource : 3*3*nsource;
2971 resp_grad.nrow = nrow;
2972 resp_grad.ncol = neeg;
2973 resp_grad.row_names = emptyList;
2974 resp_grad.col_names = names;
2975 resp_grad.data = res_grad_mat.transpose().cast<double>();
2976 resp_grad.transpose_named_matrix();
2977 }
2978 return OK;
2979}
2980
2981//=============================================================================================================
2982
2983int FwdBemModel::fwd_sphere_field(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &coils, Eigen::Ref<Eigen::VectorXf> Bval, void *client) /* Client data will be the sphere model origin */
2984{
2985 /* This version uses Jukka Sarvas' field computation
2986 for details, see
2987
2988 Jukka Sarvas:
2989
2990 Basic mathematical and electromagnetic concepts
2991 of the biomagnetic inverse problem,
2992
2993 Phys. Med. Biol. 1987, Vol. 32, 1, 11-22
2994
2995 The formulas have been manipulated for efficient computation
2996 by Matti Hamalainen, February 1990
2997
2998 */
2999 auto* r0 = static_cast<float*>(client);
3000 float a,a2,r,r2;
3001 float ar,ar0,rr0;
3002 float vr,ve,re,r0e;
3003 float F,g0,gr,result,sum;
3004 int j,k,p;
3005 FwdCoil* this_coil;
3006 int np;
3007
3008 /*
3009 * Shift to the sphere model coordinates
3010 */
3011 Eigen::Vector3f myrd = rd - Eigen::Map<const Eigen::Vector3f>(r0);
3012 /*
3013 * Check for a dipole at the origin
3014 */
3015 for (k = 0 ; k < coils.ncoil() ; k++)
3016 if (FWD_IS_MEG_COIL(coils.coils[k]->coil_class))
3017 Bval[k] = 0.0;
3018 r = myrd.norm();
3019 if (r > EPS) { /* The hard job */
3020
3021 Eigen::Vector3f v = Q.cross(myrd);
3022
3023 for (k = 0; k < coils.ncoil(); k++) {
3024 this_coil = coils.coils[k].get();
3025 if (FWD_IS_MEG_COIL(this_coil->type)) {
3026
3027 np = this_coil->np;
3028
3029 for (j = 0, sum = 0.0; j < np; j++) {
3030
3031 Eigen::Map<const Eigen::Vector3f> this_pos_raw = this_coil->pos(j);
3032 Eigen::Map<const Eigen::Vector3f> this_dir = this_coil->dir(j);
3033
3034 Eigen::Vector3f pos = this_pos_raw - Eigen::Map<const Eigen::Vector3f>(r0);
3035 result = 0.0;
3036
3037 /* Vector from dipole to the field point */
3038
3039 Eigen::Vector3f a_vec = pos - myrd;
3040
3041 /* Compute the dot products needed */
3042
3043 a2 = a_vec.squaredNorm(); a = sqrt(a2);
3044
3045 if (a > 0.0) {
3046 r2 = pos.squaredNorm(); r = sqrt(r2);
3047 if (r > 0.0) {
3048 rr0 = pos.dot(myrd);
3049 ar = (r2-rr0);
3050 if (std::fabs(ar/(a*r)+1.0) > CEPS) { /* There is a problem on the negative 'z' axis if the dipole location
3051 * and the field point are on the same line */
3052 ar0 = ar/a;
3053
3054 ve = v.dot(this_dir); vr = v.dot(pos);
3055 re = pos.dot(this_dir); r0e = myrd.dot(this_dir);
3056
3057 /* The main ingredients */
3058
3059 F = a*(r*a + ar);
3060 gr = a2/r + ar0 + 2.0*(a+r);
3061 g0 = a + 2*r + ar0;
3062
3063 /* Mix them together... */
3064
3065 sum = sum + this_coil->w[j]*(ve*F + vr*(g0*r0e - gr*re))/(F*F);
3066 }
3067 }
3068 }
3069 } /* All points done */
3070 Bval[k] = MAG_FACTOR*sum;
3071 }
3072 }
3073 }
3074 return OK; /* Happy conclusion: this works always */
3075}
3076
3077//=============================================================================================================
3078
3079int FwdBemModel::fwd_sphere_field_vec(const Eigen::Vector3f& rd, FwdCoilSet &coils, Eigen::Ref<Eigen::MatrixXf> Bval, void *client) /* Client data will be the sphere model origin */
3080{
3081 /* This version uses Jukka Sarvas' field computation
3082 for details, see
3083
3084 Jukka Sarvas:
3085
3086 Basic mathematical and electromagnetic concepts
3087 of the biomagnetic inverse problem,
3088
3089 Phys. Med. Biol. 1987, Vol. 32, 1, 11-22
3090
3091 The formulas have been manipulated for efficient computation
3092 by Matti Hamalainen, February 1990
3093
3094 The idea of matrix kernels is from
3095
3096 Mosher, Leahy, and Lewis: EEG and MEG: Forward Solutions for Inverse Methods
3097
3098 which has been simplified here using standard vector notation
3099
3100 */
3101 auto* r0 = static_cast<float*>(client);
3102 float a,a2,r,r2;
3103 float ar,ar0,rr0;
3104 float re,r0e;
3105 float F,g0,gr,g;
3106 int j,k,p;
3107 FwdCoil* this_coil;
3108 int np;
3109 Eigen::Map<const Eigen::Vector3f> r0_vec(r0);
3110
3111 /*
3112 * Shift to the sphere model coordinates
3113 */
3114 Eigen::Vector3f myrd = rd - r0_vec;
3115 /*
3116 * Check for a dipole at the origin
3117 */
3118 r = myrd.norm();
3119 for (k = 0; k < coils.ncoil(); k++) {
3120 this_coil = coils.coils[k].get();
3121 if (FWD_IS_MEG_COIL(this_coil->coil_class)) {
3122 if (r < EPS) {
3123 Bval(0,k) = Bval(1,k) = Bval(2,k) = 0.0;
3124 }
3125 else { /* The hard job */
3126
3127 np = this_coil->np;
3128 Eigen::Vector3f sum = Eigen::Vector3f::Zero();
3129
3130 for (j = 0; j < np; j++) {
3131
3132 Eigen::Map<const Eigen::Vector3f> this_pos_raw = this_coil->pos(j);
3133 Eigen::Map<const Eigen::Vector3f> this_dir = this_coil->dir(j);
3134
3135 Eigen::Vector3f pos = this_pos_raw - r0_vec;
3136
3137 /* Vector from dipole to the field point */
3138
3139 Eigen::Vector3f a_vec = pos - myrd;
3140
3141 /* Compute the dot products needed */
3142
3143 a2 = a_vec.squaredNorm(); a = sqrt(a2);
3144
3145 if (a > 0.0) {
3146 r2 = pos.squaredNorm(); r = sqrt(r2);
3147 if (r > 0.0) {
3148 rr0 = pos.dot(myrd);
3149 ar = (r2-rr0);
3150 if (std::fabs(ar/(a*r)+1.0) > CEPS) { /* There is a problem on the negative 'z' axis if the dipole location
3151 * and the field point are on the same line */
3152
3153 /* The main ingredients */
3154
3155 ar0 = ar/a;
3156 F = a*(r*a + ar);
3157 gr = a2/r + ar0 + 2.0*(a+r);
3158 g0 = a + 2*r + ar0;
3159
3160 re = pos.dot(this_dir); r0e = myrd.dot(this_dir);
3161 Eigen::Vector3f v1 = myrd.cross(this_dir);
3162 Eigen::Vector3f v2 = myrd.cross(pos);
3163
3164 g = (g0*r0e - gr*re)/(F*F);
3165 /*
3166 * Mix them together...
3167 */
3168 sum += this_coil->w[j]*(v1/F + v2*g);
3169 }
3170 }
3171 }
3172 } /* All points done */
3173 for (p = 0; p < 3; p++)
3174 Bval(p,k) = MAG_FACTOR*sum[p];
3175 }
3176 }
3177 }
3178 return OK; /* Happy conclusion: this works always */
3179}
3180
3181//=============================================================================================================
3182
3183int FwdBemModel::fwd_sphere_field_grad(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet &coils, Eigen::Ref<Eigen::VectorXf> Bval, 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 */
3184/*
3185 * Compute the derivatives of the sphere model field with respect to
3186 * dipole coordinates
3187 */
3188{
3189 /* This version uses Jukka Sarvas' field computation
3190 for details, see
3191
3192 Jukka Sarvas:
3193
3194 Basic mathematical and electromagnetic concepts
3195 of the biomagnetic inverse problem,
3196
3197 Phys. Med. Biol. 1987, Vol. 32, 1, 11-22
3198
3199 The formulas have been manipulated for efficient computation
3200 by Matti Hamalainen, February 1990
3201
3202 */
3203
3204 float vr,ve,re,r0e;
3205 float F,g0,gr,result,G,F2;
3206
3207 int j,k;
3208 float huu;
3209 FwdCoil* this_coil;
3210 int np;
3211 auto* r0 = static_cast<float*>(client);
3212 Eigen::Map<const Eigen::Vector3f> r0_vec(r0);
3213
3214 int ncoil = coils.ncoil();
3215 /*
3216 * Shift to the sphere model coordinates
3217 */
3218 Eigen::Vector3f myrd = rd - r0_vec;
3219
3220 /* Check for a dipole at the origin */
3221
3222 float r = myrd.norm();
3223 for (k = 0; k < ncoil ; k++) {
3224 if (FWD_IS_MEG_COIL(coils.coils[k]->coil_class)) {
3225 Bval[k] = 0.0;
3226 xgrad[k] = 0.0;
3227 ygrad[k] = 0.0;
3228 zgrad[k] = 0.0;
3229 }
3230 }
3231 if (r > EPS) { /* The hard job */
3232
3233 Eigen::Vector3f v = Q.cross(myrd);
3234
3235 for (k = 0 ; k < ncoil ; k++) {
3236
3237 this_coil = coils.coils[k].get();
3238
3239 if (FWD_IS_MEG_COIL(this_coil->type)) {
3240
3241 np = this_coil->np;
3242
3243 for (j = 0; j < np; j++) {
3244
3245 Eigen::Map<const Eigen::Vector3f> this_pos_raw = this_coil->pos(j);
3246 /*
3247 * Shift to the sphere model coordinates
3248 */
3249 Eigen::Vector3f pos = this_pos_raw - r0_vec;
3250
3251 Eigen::Map<const Eigen::Vector3f> this_dir = this_coil->dir(j);
3252
3253 /* Vector from dipole to the field point */
3254
3255 Eigen::Vector3f a_vec = pos - myrd;
3256
3257 /* Compute the dot and cross products needed */
3258
3259 float a2 = a_vec.squaredNorm(); float a = sqrt(a2);
3260 float r2 = pos.squaredNorm(); r = sqrt(r2);
3261 float rr0 = pos.dot(myrd);
3262 float ar = (r2 - rr0)/a;
3263
3264 ve = v.dot(this_dir); vr = v.dot(pos);
3265 re = pos.dot(this_dir); r0e = myrd.dot(this_dir);
3266
3267 /* eQ = this_dir x Q */
3268
3269 Eigen::Vector3f eQ = this_dir.cross(Q);
3270
3271 /* rQ = this_pos x Q */
3272
3273 Eigen::Vector3f rQ = pos.cross(Q);
3274
3275 /* The main ingredients */
3276
3277 F = a*(r*a + r2 - rr0);
3278 F2 = F*F;
3279 gr = a2/r + ar + 2.0*(a+r);
3280 g0 = a + 2.0*r + ar;
3281 G = g0*r0e - gr*re;
3282
3283 /* Mix them together... */
3284
3285 result = (ve*F + vr*G)/F2;
3286
3287 /* The computation of the gradient... */
3288
3289 huu = 2.0 + 2.0*a/r;
3290 Eigen::Vector3f ga = -a_vec/a;
3291 Eigen::Vector3f gar = -(ga*ar + pos)/a;
3292 Eigen::Vector3f gg0 = ga + gar;
3293 Eigen::Vector3f ggr = huu*ga + gar;
3294 Eigen::Vector3f gFF = ga/a - (r*a_vec + a*pos)/F;
3295 Eigen::Vector3f gresult = -2.0f*result*gFF + (eQ+gFF*ve)/F +
3296 (rQ*G + vr*(gg0*r0e + g0*this_dir - ggr*re))/F2;
3297
3298 Bval[k] = Bval[k] + this_coil->w[j]*result;
3299 xgrad[k] = xgrad[k] + this_coil->w[j]*gresult[0];
3300 ygrad[k] = ygrad[k] + this_coil->w[j]*gresult[1];
3301 zgrad[k] = zgrad[k] + this_coil->w[j]*gresult[2];
3302 }
3303 Bval[k] = MAG_FACTOR*Bval[k];
3304 xgrad[k] = MAG_FACTOR*xgrad[k];
3305 ygrad[k] = MAG_FACTOR*ygrad[k];
3306 zgrad[k] = MAG_FACTOR*zgrad[k];
3307 }
3308 }
3309 }
3310 return OK; /* Happy conclusion: this works always */
3311}
3312
3313//=============================================================================================================
3314
3315int FwdBemModel::fwd_mag_dipole_field(const Eigen::Vector3f& rm, const Eigen::Vector3f& M, FwdCoilSet &coils, Eigen::Ref<Eigen::VectorXf> Bval, void *client) /* Client data will be the sphere model origin */
3316/*
3317 * This is for a specific dipole component
3318 */
3319{
3320 int j,k,np;
3321 FwdCoil* this_coil;
3322 float sum,dist,dist2,dist5;
3323
3324 Bval.setZero();
3325 for (k = 0; k < coils.ncoil(); k++) {
3326 this_coil = coils.coils[k].get();
3327 if (FWD_IS_MEG_COIL(this_coil->type)) {
3328 np = this_coil->np;
3329 /*
3330 * Go through all points
3331 */
3332 for (j = 0, sum = 0.0; j < np; j++) {
3333 Eigen::Map<const Eigen::Vector3f> dir = this_coil->dir(j);
3334 Eigen::Vector3f diff = this_coil->pos(j) - rm;
3335 dist = diff.norm();
3336 if (dist > EPS) {
3337 dist2 = dist*dist;
3338 dist5 = dist2*dist2*dist;
3339 sum = sum + this_coil->w[j]*(3*M.dot(diff)*diff.dot(dir) - dist2*M.dot(dir))/dist5;
3340 }
3341 } /* All points done */
3342 Bval[k] = MAG_FACTOR*sum;
3343 }
3344 else if (this_coil->type == FWD_COILC_EEG)
3345 Bval[k] = 0.0;
3346 }
3347 return OK;
3348}
3349
3350//=============================================================================================================
3351
3352int FwdBemModel::fwd_mag_dipole_field_vec(const Eigen::Vector3f& rm, FwdCoilSet &coils, Eigen::Ref<Eigen::MatrixXf> Bval, void *client) /* Client data will be the sphere model origin */
3353/*
3354 * This is for all dipole components
3355 * For EEG this produces a zero result
3356 */
3357{
3358 int j,k,p,np;
3359 FwdCoil* this_coil;
3360 float dist,dist2,dist5;
3361
3362 Bval.setZero();
3363 for (k = 0; k < coils.ncoil(); k++) {
3364 this_coil = coils.coils[k].get();
3365 if (FWD_IS_MEG_COIL(this_coil->type)) {
3366 np = this_coil->np;
3367 Eigen::Vector3f sum = Eigen::Vector3f::Zero();
3368 /*
3369 * Go through all points
3370 */
3371 for (j = 0; j < np; j++) {
3372 Eigen::Map<const Eigen::Vector3f> dir = this_coil->dir(j);
3373 Eigen::Vector3f diff = this_coil->pos(j) - rm;
3374 dist = diff.norm();
3375 if (dist > EPS) {
3376 dist2 = dist*dist;
3377 dist5 = dist2*dist2*dist;
3378 for (p = 0; p < 3; p++)
3379 sum[p] = sum[p] + this_coil->w[j]*(3*diff[p]*diff.dot(dir) - dist2*dir[p])/dist5;
3380 }
3381 } /* All points done */
3382 for (p = 0; p < 3; p++)
3383 Bval(p,k) = MAG_FACTOR*sum[p];
3384 }
3385 else if (this_coil->type == FWD_COILC_EEG) {
3386 for (p = 0; p < 3; p++)
3387 Bval(p,k) = 0.0;
3388 }
3389 }
3390 return OK;
3391}
constexpr double EPS
const QString mne_coord_frame_name_40(int frame)
double arsinh(double x)
Per-thread work packet (dipole range, coil set, output column) consumed by the parallel forward-solut...
Software-gradiometer compensation wrapper that subtracts the reference-channel contribution from the ...
Per-sensor projection matrix that turns BEM node potentials into MEG coil readings or EEG electrode v...
std::function< int(const Eigen::Vector3f &rd, FWDLIB::FwdCoilSet &coils, Eigen::Ref< Eigen::MatrixXf > res, void *client)> fwdVecFieldFunc
Definition fwd_types.h:49
std::function< int(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FWDLIB::FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > res, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)> fwdFieldGradFunc
Definition fwd_types.h:51
std::function< int(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FWDLIB::FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > res, void *client)> fwdFieldFunc
Definition fwd_types.h:47
Multi-shell spherical head model with Berg-Scherg equivalent-source approximation for fast EEG forwar...
Boundary Element Method (BEM) volume-conductor model — layered triangulated surfaces,...
constexpr int FAIL
constexpr int Y
constexpr int Z
constexpr int OK
constexpr int X
Lightweight triangulated surface (vertices, triangles, normals) used by surface-based routines.
Single-hemisphere source space (cortical surface or volume grid) loaded from FIFF.
Triangle descriptor with cached centroid, area and normal vectors.
#define M_PI
#define FIFFV_MNE_COORD_MNI_TAL
#define FIFFV_MNE_COORD_FS_TAL_LTZ
#define FIFFV_COORD_MRI_SLICE
#define FIFFV_MNE_COORD_CTF_DEVICE
#define FIFFV_COORD_DEVICE
#define FIFFV_COORD_MRI_DISPLAY
#define FIFFV_MNE_COORD_MRI_VOXEL
#define FIFFV_MNE_COORD_CTF_HEAD
#define FIFFV_COORD_HPI
#define FIFFV_NO_MOVE
#define FIFFV_COORD_HEAD
#define FIFFV_COORD_MRI
#define FIFFV_COORD_ISOTRAK
#define FIFFV_COORD_UNKNOWN
#define FIFFV_MNE_COORD_FS_TAL_GTZ
#define FIFFV_MOVE
#define FIFFV_MNE_COORD_RAS
Matrix paired with row and column name lists, the on-disk form of FIFFB_PROJ_ITEM / FIFFB_MNE_NAMED_M...
#define FIFF_BEM_APPROX
Definition fiff_file.h:735
#define FIFFV_BEM_APPROX_LINEAR
Definition fiff_file.h:772
#define FIFFT_INT
Definition fiff_file.h:224
#define FIFFV_BEM_SURF_ID_SKULL
Definition fiff_file.h:744
#define FIFFB_BEM
Definition fiff_file.h:396
#define FIFFV_BEM_APPROX_CONST
Definition fiff_file.h:771
#define FIFFV_BEM_SURF_ID_HEAD
Definition fiff_file.h:745
#define FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:742
#define FIFF_BEM_POT_SOLUTION
Definition fiff_file.h:734
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O, in-memory data structures and high-level readers/writers.
Forward modelling — BEM solver, spherical models, sensor/coil definitions and the lead-field assembly...
Definition compute_fwd.h:83
constexpr int FWD_BEM_CONSTANT_COLL
constexpr double FWD_BEM_IP_APPROACH_LIMIT
constexpr int FWD_BEM_LIN_FIELD_URANKAR
constexpr int FWD_COILC_EEG
Definition fwd_coil.h:69
constexpr bool FWD_IS_MEG_COIL(int x)
Definition fwd_coil.h:79
constexpr int FWD_BEM_LINEAR_COLL
constexpr int FWD_BEM_LIN_FIELD_FERGUSON
constexpr int FWD_BEM_UNKNOWN
constexpr int FWD_BEM_LIN_FIELD_SIMPLE
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
FiffCoordTrans inverted() const
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
QSharedPointer< FiffDirNode > SPtr
FIFF named matrix: dense / sparse Eigen matrix plus row-name and column-name string lists.
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:164
const QString name
Lookup record mapping a FIFF coordinate frame integer ID to its human-readable name.
static FwdBemModel::UPtr fwd_bem_load_three_layer_surfaces(const QString &name)
Load a three-layer BEM model (scalp, outer skull, inner skull) from a FIFF file.
static void field_integrals(const Eigen::Vector3f &from, MNELIB::MNETriangle &to, double &I1p, Eigen::Vector2d &T, Eigen::Vector2d &S1, Eigen::Vector2d &S2, Eigen::Vector3d &f0, Eigen::Vector3d &fx, Eigen::Vector3d &fy)
Compute the geometry integrals for the magnetic field from a triangle.
void fwd_bem_field_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > B)
Compute BEM magnetic fields at coils using constant collocation.
static double calc_gamma(const Eigen::Vector3d &rk, const Eigen::Vector3d &rk1)
Compute the gamma angle for the linear field integration (Ferguson).
Eigen::VectorXi np
void fwd_bem_pot_grad_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet *els, int all_surfs, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad)
Compute the gradient of BEM potentials with respect to dipole position (constant collocation).
Eigen::VectorXf source_mult
static double calc_beta(const Eigen::Vector3d &rk, const Eigen::Vector3d &rk1)
Compute the beta angle used in the linear collocation integration.
static int get_int(FIFFLIB::FiffStream::SPtr &stream, const FIFFLIB::FiffDirNode::SPtr &node, int what, int *res)
Read an integer tag from a FIFF node.
Eigen::MatrixXf gamma
static void fwd_bem_one_lin_field_coeff_uran(const Eigen::Vector3f &dest, const Eigen::Vector3f &dir, MNELIB::MNETriangle &tri, Eigen::Vector3d &res)
Compute linear field coefficients using the Urankar method.
static Eigen::MatrixXf fwd_bem_multi_solution(Eigen::MatrixXf &solids, const Eigen::MatrixXf *gamma, int nsurf, const Eigen::VectorXi &ntri)
Compute the multi-surface BEM solution from solid-angle coefficients.
static FwdBemModel::UPtr fwd_bem_load_surfaces(const QString &name, const std::vector< int > &kinds)
Load BEM surfaces of specified kinds from a FIFF file.
static void correct_auto_elements(MNELIB::MNESurface &surf, Eigen::MatrixXf &mat)
Correct the auto (self-coupling) elements of the linear collocation matrix.
static int fwd_sphere_field_grad(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > Bval, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)
Callback: compute the spherical-model magnetic field and its position gradient at coils.
static int fwd_bem_check_solids(const Eigen::MatrixXf &angles, int ntri1, int ntri2, float desired)
Verify that solid-angle sums match the expected value.
int fwd_bem_load_solution(const QString &name, int bem_method)
Load a pre-computed BEM solution from a FIFF file.
static int fwd_mag_dipole_field_vec(const Eigen::Vector3f &rm, FwdCoilSet &coils, Eigen::Ref< Eigen::MatrixXf > Bval, void *client)
Callback: compute the vector magnetic field of a magnetic dipole at coils.
int fwd_bem_compute_solution(int bem_method)
Compute the BEM solution matrix using the specified method.
static constexpr double MAG_FACTOR
int fwd_bem_specify_coils(FwdCoilSet *coils)
Precompute the coil-specific BEM solution for MEG.
static Eigen::MatrixXf fwd_bem_solid_angles(const std::vector< MNELIB::MNESurface * > &surfs)
Compute the solid-angle matrix for all BEM surfaces.
int fwd_bem_specify_els(FwdCoilSet *els)
Precompute the electrode-specific BEM solution.
static void fwd_bem_one_lin_field_coeff_simple(const Eigen::Vector3f &dest, const Eigen::Vector3f &normal, MNELIB::MNETriangle &source, Eigen::Vector3d &res)
Compute linear field coefficients using the simple (direct) method.
std::vector< std::shared_ptr< MNELIB::MNESurface > > surfs
Eigen::VectorXf field_mult
std::unique_ptr< FwdBemModel > UPtr
virtual ~FwdBemModel()
Destroys the BEM model.
static void fwd_bem_one_lin_field_coeff_ferg(const Eigen::Vector3f &dest, const Eigen::Vector3f &dir, MNELIB::MNETriangle &tri, Eigen::Vector3d &res)
Compute linear field coefficients using the Ferguson method.
static QString fwd_bem_make_bem_sol_name(const QString &name)
Build a standard BEM solution file name from a model name.
static Eigen::MatrixXf fwd_bem_homog_solution(Eigen::MatrixXf &solids, int ntri)
Compute the homogeneous (single-layer) BEM solution.
void fwd_bem_lin_field_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > B)
Compute BEM magnetic fields at coils using linear collocation.
static int fwd_mag_dipole_field(const Eigen::Vector3f &rm, const Eigen::Vector3f &M, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > Bval, void *client)
Callback: compute the magnetic field of a magnetic dipole at coils.
MNELIB::MNESurface * fwd_bem_find_surface(int kind)
Find a surface of the given kind in this BEM model.
void fwd_bem_free_solution()
Release the potential solution matrix and associated workspace.
Eigen::MatrixXf fwd_bem_field_coeff(FwdCoilSet *coils)
Assemble the constant-collocation magnetic field coefficient matrix.
static int fwd_sphere_field(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > Bval, void *client)
Callback: compute the spherical-model magnetic field at coils.
static void lin_pot_coeff(const Eigen::Vector3f &from, MNELIB::MNETriangle &to, Eigen::Vector3d &omega)
Compute the linear potential coefficients for one source-destination pair.
Eigen::VectorXi ntri
void fwd_bem_field_grad_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad)
Compute the gradient of BEM magnetic fields with respect to dipole position (constant collocation).
static const QString & fwd_bem_explain_method(int method)
Return a human-readable label for a BEM method.
void fwd_bem_pot_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet *els, int all_surfs, Eigen::Ref< Eigen::VectorXf > pot)
Compute BEM potentials at electrodes using constant collocation.
void fwd_bem_lin_field_grad_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad)
Compute the gradient of BEM magnetic fields with respect to dipole position (linear collocation).
static void calc_f(const Eigen::Vector3d &xx, const Eigen::Vector3d &yy, Eigen::Vector3d &f0, Eigen::Vector3d &fx, Eigen::Vector3d &fy)
Compute the f0, fx, fy integration helper values from corner coordinates.
int fwd_bem_linear_collocation_solution()
Compute the linear-collocation BEM solution for this model.
int fwd_bem_load_recompute_solution(const QString &name, int bem_method, int force_recompute)
Load a BEM solution from file, recomputing if necessary.
static int fwd_bem_field(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > B, void *client)
Callback: compute BEM magnetic fields at coils for a dipole.
static int fwd_bem_field_grad(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > Bval, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)
Callback: compute BEM magnetic fields and position gradients at coils.
int compute_forward_eeg(std::vector< std::unique_ptr< MNELIB::MNESourceSpace > > &spaces, FwdCoilSet *els, bool fixed_ori, FwdEegSphereModel *eeg_model, bool use_threads, FIFFLIB::FiffNamedMatrix &resp, FIFFLIB::FiffNamedMatrix &resp_grad, bool bDoGrad)
Compute the EEG forward solution for one or more source spaces.
static float fwd_bem_inf_pot(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Vector3f &rp)
Compute the infinite-medium electric potential at a single point.
static int fwd_bem_pot_els(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > pot, void *client)
Callback: compute BEM potentials at electrodes for a dipole.
static Eigen::MatrixXf fwd_bem_lin_pot_coeff(const std::vector< MNELIB::MNESurface * > &surfs)
Assemble the full linear-collocation potential coefficient matrix.
int fwd_bem_set_head_mri_t(const FIFFLIB::FiffCoordTrans &t)
Set the Head-to-MRI coordinate transform for this BEM model.
void(* linFieldIntFunc)(const Eigen::Vector3f &dest, const Eigen::Vector3f &dir, MNELIB::MNETriangle &tri, Eigen::Vector3d &res)
Function pointer type for linear field coefficient integration methods.
static float fwd_bem_inf_field_der(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Vector3f &rp, const Eigen::Vector3f &dir, const Eigen::Vector3f &comp)
Compute the derivative of the infinite-medium magnetic field with respect to dipole position.
static FwdBemModel::UPtr fwd_bem_load_homog_surface(const QString &name)
Load a single-layer (homogeneous) BEM model from a FIFF file.
static float fwd_bem_inf_field(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Vector3f &rp, const Eigen::Vector3f &dir)
Compute the infinite-medium magnetic field at a single point.
static float fwd_bem_inf_pot_der(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, const Eigen::Vector3f &rp, const Eigen::Vector3f &comp)
Compute the derivative of the infinite-medium electric potential with respect to dipole position.
static int fwd_sphere_field_vec(const Eigen::Vector3f &rd, FwdCoilSet &coils, Eigen::Ref< Eigen::MatrixXf > Bval, void *client)
Callback: compute the spherical-model vector magnetic field at coils.
void fwd_bem_lin_pot_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet *els, int all_surfs, Eigen::Ref< Eigen::VectorXf > pot)
Compute BEM potentials at electrodes using linear collocation.
FwdBemModel()
Constructs an empty BEM model.
static double one_field_coeff(const Eigen::Vector3f &dest, const Eigen::Vector3f &normal, MNELIB::MNETriangle &tri)
Compute the constant-collocation magnetic field coefficient for one triangle.
static const QString & fwd_bem_explain_surface(int kind)
Return a human-readable label for a BEM surface kind.
static void meg_eeg_fwd_one_source_space(FwdThreadArg *arg)
Thread worker: compute the forward solution for one source space.
int compute_forward_meg(std::vector< std::unique_ptr< MNELIB::MNESourceSpace > > &spaces, FwdCoilSet *coils, FwdCoilSet *comp_coils, MNELIB::MNECTFCompDataSet *comp_data, bool fixed_ori, const Eigen::Vector3f &r0, bool use_threads, FIFFLIB::FiffNamedMatrix &resp, FIFFLIB::FiffNamedMatrix &resp_grad, bool bDoGRad)
Compute the MEG forward solution for one or more source spaces.
static void fwd_bem_ip_modify_solution(Eigen::MatrixXf &solution, Eigen::MatrixXf &ip_solution, float ip_mult, int nsurf, const Eigen::VectorXi &ntri)
Modify the BEM solution with the isolated-problem (IP) approach.
static std::unique_ptr< MNELIB::MNESurface > make_guesses(MNELIB::MNESurface *guess_surf, float guessrad, const Eigen::Vector3f &guess_r0, float grid, float exclude, float mindist)
Generate a set of dipole guess locations inside a boundary surface.
int fwd_bem_constant_collocation_solution()
Compute the constant-collocation BEM solution for this model.
Eigen::MatrixXf solution
void fwd_bem_lin_pot_grad_calc(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet *els, int all_surfs, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad)
Compute the gradient of BEM potentials with respect to dipole position (linear collocation).
static int fwd_bem_pot_grad_els(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > pot, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)
Callback: compute BEM potentials and position gradients at electrodes.
Eigen::MatrixXf fwd_bem_lin_field_coeff(FwdCoilSet *coils, int method)
Assemble the linear-collocation magnetic field coefficient matrix.
Eigen::VectorXf sigma
static void calc_magic(double u, double z, double A, double B, Eigen::Vector3d &beta, double &D)
Compute the "magic" beta and D factors for the Urankar field integration.
Eigen::VectorXf v0
FIFFLIB::FiffCoordTrans head_mri_t
Channel-specific projection that contracts a BEM node-potential vector down to one entry per MEG coil...
Single MEG sensor coil or EEG electrode — stores the coil-local frame and the (r_mag,...
Definition fwd_coil.h:88
Eigen::Map< const Eigen::Vector3f > dir(int j) const
Definition fwd_coil.h:177
Eigen::Map< const Eigen::Vector3f > pos(int j) const
Definition fwd_coil.h:175
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::unique_ptr< FwdBemSolution > user_data
std::unique_ptr< FwdCoilSet > UPtr
std::vector< FwdCoil::UPtr > coils
FwdCoilSet::UPtr dup_coil_set(const FIFFLIB::FiffCoordTrans &t=FIFFLIB::FiffCoordTrans()) const
CTF / 4D software-gradiometer wrapper that re-evaluates the primary field callback on a separate refe...
static int fwd_comp_field_grad(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > res, Eigen::Ref< Eigen::VectorXf > xgrad, Eigen::Ref< Eigen::VectorXf > ygrad, Eigen::Ref< Eigen::VectorXf > zgrad, void *client)
MNELIB::MNECTFCompDataSet * set
static int fwd_comp_field_vec(const Eigen::Vector3f &rd, FwdCoilSet &coils, Eigen::Ref< Eigen::MatrixXf > res, void *client)
FwdCoilSet * comp_coils
static int fwd_comp_field(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > res, void *client)
static FwdCompData * fwd_make_comp_data(MNELIB::MNECTFCompDataSet *set, FwdCoilSet *coils, FwdCoilSet *comp_coils, fwdFieldFunc field, fwdVecFieldFunc vec_field, fwdFieldGradFunc field_grad, void *client)
Multi-shell concentric-sphere head model holding the Berg-Scherg equivalent-source parameters that ac...
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 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_spherepot_coil_vec(const Eigen::Vector3f &rd, FwdCoilSet &els, Eigen::Ref< Eigen::MatrixXf > Vval_vec, void *client)
static int fwd_eeg_spherepot_coil(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &els, Eigen::Ref< Eigen::VectorXf > Vval, void *client)
Per-thread work packet carrying the dipole-index range, coil set, field/grad callback and write-back ...
fwdFieldGradFunc field_pot_grad
std::unique_ptr< FwdThreadArg > UPtr
static FwdThreadArg::UPtr create_eeg_multi_thread_duplicate(FwdThreadArg &one, bool bem_model)
Eigen::MatrixXf * res_grad
Eigen::MatrixXf * res
fwdVecFieldFunc vec_field_pot
static FwdThreadArg::UPtr create_meg_multi_thread_duplicate(FwdThreadArg &one, bool bem_model)
MNELIB::MNESourceSpace * s
Collection of CTF third-order gradient compensation operators.
std::unique_ptr< MNECTFCompData > current
This defines a source space.
static MNESourceSpace * make_volume_source_space(const MNESurface &surf, float grid, float exclude, float mindist)
Lightweight triangulated surface (vertices, triangles, normals).
Definition mne_surface.h:66
std::unique_ptr< MNESurface > UPtr
Definition mne_surface.h:70
void triangle_coords(const Eigen::Vector3f &r, int tri, float &x, float &y, float &z) const
static std::unique_ptr< MNESurface > read_bem_surface(const QString &name, int which, bool add_geometry)
int project_to_surface(const MNEProjData *proj_data, const Eigen::Vector3f &r, float &distp) const
Eigen::Map< const Eigen::Vector3f > normal(int k) const
static double solid_angle(const Eigen::Vector3f &from, const MNELIB::MNETriangle &tri)
std::vector< MNETriangle > tris
Eigen::Map< const Eigen::Vector3f > point(int k) const
Per-triangle geometric data for a cortical or BEM surface.
Eigen::Vector3f nn
Eigen::Vector3f r2
Eigen::Vector3f r1
Eigen::Vector3f ex
Eigen::Vector3f r3
Eigen::Vector3f ey
Eigen::Vector3f cent