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