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