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