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#include <QDebug>
43
44// MSVC builds already define _USE_MATH_DEFINES globally (see src/CMakeLists.txt),
45// so define it here only for the toolchains that do not.
46#ifndef _USE_MATH_DEFINES
47#define _USE_MATH_DEFINES
48#endif
49#include <math.h>
50
51#include <Eigen/Dense>
52
53static const Eigen::Vector3f Qx(1.0f, 0.0f, 0.0f);
54static const Eigen::Vector3f Qy(0.0f, 1.0f, 0.0f);
55static const Eigen::Vector3f Qz(0.0f, 0.0f, 1.0f);
56
57//=============================================================================================================
58// Local constants
59//=============================================================================================================
60
61namespace
62{
63constexpr int X = 0;
64constexpr int Z = 2;
65constexpr int FAIL = -1;
66constexpr int OK = 0;
67constexpr int LOADED = 1; // fwd_bem_load_solution: successfully loaded
68constexpr int NOT_FOUND = 0; // fwd_bem_load_solution: solution not available
69
70// BEM file suffixes (kept for documentation).
71[[maybe_unused]] constexpr auto BEM_SUFFIX = "-bem.fif";
72constexpr auto BEM_SOL_SUFFIX = "-bem-sol.fif";
73constexpr float EPS = 1e-5f; // Points closer to origin than this are considered at the origin
74constexpr float CEPS = 1e-5f;
75}
76
77namespace FWDLIB
78{
79
81{
82 int kind;
83 const QString name;
84};
85
87{
88 int method;
89 const QString name;
90};
91
92} // namespace FWDLIB
93
94static FWDLIB::SurfExpl surf_expl[] = {{FIFFV_BEM_SURF_ID_BRAIN, "inner skull"},
95 {FIFFV_BEM_SURF_ID_SKULL, "outer skull"},
96 {FIFFV_BEM_SURF_ID_HEAD, "scalp"},
97 {-1, "unknown"}};
98
99static FWDLIB::MethodExpl method_expl[] = {{FWDLIB::FWD_BEM_CONSTANT_COLL, "constant collocation"},
100 {FWDLIB::FWD_BEM_LINEAR_COLL, "linear collocation"},
101 {-1, "unknown"}};
102
103static QString strip_from(const QString& s, const QString& suffix)
104{
105 QString res;
106
107 if (s.endsWith(suffix)) {
108 res = s;
109 res.chop(suffix.size());
110 } else
111 res = s;
112
113 return res;
114}
115
116
117namespace FWDLIB
118{
119
124{
125 int frame;
126 const QString name;
127};
128
129}
130
131const QString mne_coord_frame_name_40(int frame)
132
133{
134 static FWDLIB::FrameNameRec frames[] = {
135 {FIFFV_COORD_UNKNOWN, "unknown"},
136 {FIFFV_COORD_DEVICE, "MEG device"},
137 {FIFFV_COORD_ISOTRAK, "isotrak"},
138 {FIFFV_COORD_HPI, "hpi"},
139 {FIFFV_COORD_HEAD, "head"},
140 {FIFFV_COORD_MRI, "MRI (surface RAS)"},
141 {FIFFV_MNE_COORD_MRI_VOXEL, "MRI voxel"},
142 {FIFFV_COORD_MRI_SLICE, "MRI slice"},
143 {FIFFV_COORD_MRI_DISPLAY, "MRI display"},
144 {FIFFV_MNE_COORD_CTF_DEVICE, "CTF MEG device"},
145 {FIFFV_MNE_COORD_CTF_HEAD, "CTF/4D/KIT head"},
146 {FIFFV_MNE_COORD_RAS, "RAS (non-zero origin)"},
147 {FIFFV_MNE_COORD_MNI_TAL, "MNI Talairach"},
148 {FIFFV_MNE_COORD_FS_TAL_GTZ, "Talairach (MNI z > 0)"},
149 {FIFFV_MNE_COORD_FS_TAL_LTZ, "Talairach (MNI z < 0)"},
150 {-1, "unknown"}};
151 int k;
152 for (k = 0; frames[k].frame != -1; k++) {
153 if (frame == frames[k].frame)
154 return frames[k].name;
155 }
156 return frames[k].name;
157}
158
159//=============================================================================================================
160// USED NAMESPACES
161//=============================================================================================================
162
163using namespace Eigen;
164using namespace FIFFLIB;
165using namespace MNELIB;
166using namespace FWDLIB;
167
168//=============================================================================================================
169// DEFINE MEMBER METHODS
170//=============================================================================================================
171
181
182//=============================================================================================================
183
185{
186 // surfs cleaned up automatically via shared_ptr
187 this->fwd_bem_free_solution();
188}
189
190//=============================================================================================================
191
193{
194 this->solution.resize(0, 0);
195 this->sol_name.clear();
196 this->v0.resize(0);
198 this->nsol = 0;
199}
200
201//=============================================================================================================
202
203QString FwdBemModel::fwd_bem_make_bem_sol_name(const QString& name)
204/*
205 * Make a standard BEM solution file name
206 */
207{
208 QString s1, s2;
209
210 s1 = strip_from(name, ".fif");
211 s2 = strip_from(s1, "-sol");
212 s1 = strip_from(s2, "-bem");
213 s2 = QString("%1%2").arg(s1).arg(BEM_SOL_SUFFIX);
214 return s2;
215}
216
217//=============================================================================================================
218
220{
221 int k;
222
223 for (k = 0; surf_expl[k].kind >= 0; k++)
224 if (surf_expl[k].kind == kind)
225 return surf_expl[k].name;
226
227 return surf_expl[k].name;
228}
229
230//=============================================================================================================
231
232const QString& FwdBemModel::fwd_bem_explain_method(int method)
233
234{
235 int k;
236
237 for (k = 0; method_expl[k].method >= 0; k++)
238 if (method_expl[k].method == method)
239 return method_expl[k].name;
240
241 return method_expl[k].name;
242}
243
244//=============================================================================================================
245
246int FwdBemModel::get_int(FiffStream::SPtr& stream, const FiffDirNode::SPtr& node, int what, int* res)
247/*
248 * Wrapper to get int's
249 */
250{
251 FiffTag::UPtr t_pTag;
252 if (node->find_tag(stream, what, t_pTag)) {
253 if (t_pTag->getType() != FIFFT_INT) {
254 qWarning("Expected an integer tag : %d (found data type %d instead)", what, t_pTag->getType());
255 return FAIL;
256 }
257 *res = *t_pTag->toInt();
258 return OK;
259 }
260 return FAIL;
261}
262
263//=============================================================================================================
264
266{
267 for (int k = 0; k < this->nsurf; k++)
268 if (this->surfs[k]->id == kind)
269 return this->surfs[k].get();
270 qWarning("Desired surface (%d = %s) not found.", kind, fwd_bem_explain_surface(kind).toUtf8().constData());
271 return nullptr;
272}
273
274//=============================================================================================================
275
276FwdBemModel::UPtr FwdBemModel::fwd_bem_load_surfaces(const QString& name, const std::vector<int>& kinds)
277/*
278 * Load a set of surfaces
279 */
280{
281 std::vector<std::shared_ptr<MNESurface>> surfs;
282 const int nkind = static_cast<int>(kinds.size());
283 Eigen::VectorXf sigma_tmp(nkind);
284 int j, k;
285
286 if (nkind <= 0) {
287 qCritical("No surfaces specified to fwd_bem_load_surfaces");
288 return nullptr;
289 }
290
291 for (k = 0; k < nkind; k++) {
292 float cond = -1.0f;
293 auto s = MNESurface::read_bem_surface(name, kinds[k], true, cond);
294 if (!s)
295 return nullptr;
296 if (cond < 0.0) {
297 qCritical("No conductivity available for surface %s", fwd_bem_explain_surface(kinds[k]).toUtf8().constData());
298 return nullptr;
299 }
300 if (s->coord_frame != FIFFV_COORD_MRI) {
301 qCritical("FsSurface %s not specified in MRI coordinates.", fwd_bem_explain_surface(kinds[k]).toUtf8().constData());
302 return nullptr;
303 }
304 sigma_tmp[k] = cond;
305 surfs.push_back(std::move(s));
306 }
307 auto m = std::make_unique<FwdBemModel>();
308
309 m->surf_name = name;
310 m->nsurf = nkind;
311 m->surfs = std::move(surfs);
312 m->sigma = sigma_tmp;
313 m->ntri.resize(nkind);
314 m->np.resize(nkind);
315 m->gamma.resize(nkind, nkind);
316 m->source_mult.resize(nkind);
317 m->field_mult.resize(nkind);
318 /*
319 * Build a shifted conductivity array with sigma[-1] = 0 (outside)
320 */
321 Eigen::VectorXf sigma1(nkind + 1);
322 sigma1[0] = 0.0f;
323 sigma1.tail(nkind) = m->sigma;
324 // sigma[j] below refers to sigma1[j+1], sigma[j-1] to sigma1[j]
325 /*
326 * Gamma factors and multipliers
327 */
328 for (j = 0; j < m->nsurf; j++) {
329 m->ntri[j] = m->surfs[j]->ntri;
330 m->np[j] = m->surfs[j]->np;
331 m->source_mult[j] = 2.0f / (sigma1[j + 1] + sigma1[j]);
332 m->field_mult[j] = sigma1[j + 1] - sigma1[j];
333 for (k = 0; k < m->nsurf; k++)
334 m->gamma(j, k) = (sigma1[k + 1] - sigma1[k]) / (sigma1[j + 1] + sigma1[j]);
335 }
336
337 return m;
338}
339
340//=============================================================================================================
341
343/*
344 * Load surfaces for the homogeneous model
345 */
346{
348}
349
350//=============================================================================================================
351
353/*
354 * Load surfaces for three-layer model
355 */
356{
358}
359
360//=============================================================================================================
361
362int FwdBemModel::fwd_bem_load_solution(const QString& name, int bemMethod)
363/*
364 * Load the potential solution matrix and attach it to the model
365 */
366{
367 QFile file(name);
368 FiffStream::SPtr stream(new FiffStream(&file));
369
370 FiffDirNode::SPtr bem_node;
371 int method;
372 FiffTag::UPtr t_pTag;
373 int nSolutions;
374
375 if (!stream->open()) {
376 stream->close();
377 return NOT_FOUND;
378 }
379
380 /*
381 * Find the BEM data
382 */
383 {
384 QList<FiffDirNode::SPtr> nodes = stream->dirtree()->dir_tree_find(FIFFB_BEM);
385
386 if (nodes.size() == 0) {
387 qWarning("No BEM data in %s", name.toUtf8().constData());
388 stream->close();
389 return NOT_FOUND;
390 }
391 bem_node = nodes[0];
392 }
393 /*
394 * Approximation method
395 */
396 if (get_int(stream, bem_node, FIFF_BEM_APPROX, &method) != OK) {
397 stream->close();
398 return NOT_FOUND;
399 }
400 if (method == FIFFV_BEM_APPROX_CONST)
401 method = FWD_BEM_CONSTANT_COLL;
402 else if (method == FIFFV_BEM_APPROX_LINEAR)
403 method = FWD_BEM_LINEAR_COLL;
404 else {
405 qWarning("Cannot handle BEM approximation method : %d", method);
406 stream->close();
407 return FAIL;
408 }
409 if (bemMethod != FWD_BEM_UNKNOWN && method != bemMethod) {
410 qWarning("Approximation method in file : %d desired : %d", method, bemMethod);
411 stream->close();
412 return NOT_FOUND;
413 }
414 {
415 int dim, k;
416
417 if (!bem_node->find_tag(stream, FIFF_BEM_POT_SOLUTION, t_pTag)) {
418 stream->close();
419 return FAIL;
420 }
421 qint32 ndim;
422 QVector<qint32> dims;
423 t_pTag->getMatrixDimensions(ndim, dims);
424
425 if (ndim != 2) {
426 qWarning("Expected a two-dimensional solution matrix instead of a %d dimensional one", ndim);
427 stream->close();
428 return FAIL;
429 }
430 for (k = 0, dim = 0; k < nsurf; k++)
431 dim = dim + ((method == FWD_BEM_LINEAR_COLL) ? surfs[k]->np : surfs[k]->ntri);
432 if (dims[0] != dim || dims[1] != dim) {
433 qWarning("Expected a %d x %d solution matrix instead of a %d x %d one", dim, dim, dims[0], dims[1]);
434 stream->close();
435 return NOT_FOUND;
436 }
437
438 MatrixXf tmp_sol = t_pTag->toFloatMatrix().transpose();
439 nSolutions = dims[1];
440 }
442 sol_name = name;
443 solution = t_pTag->toFloatMatrix().transpose();
444 this->nsol = nSolutions;
445 this->bem_method = method;
446 stream->close();
447
448 return LOADED;
449}
450
451//=============================================================================================================
452
454/*
455 * Set the coordinate transformation
456 */
457{
458 if (t.from == FIFFV_COORD_HEAD && t.to == FIFFV_COORD_MRI) {
459 head_mri_t = t;
460 return OK;
461 } else if (t.from == FIFFV_COORD_MRI && t.to == FIFFV_COORD_HEAD) {
462 head_mri_t = t.inverted();
463 return OK;
464 } else {
465 qWarning("Improper coordinate transform delivered to fwd_bem_set_head_mri_t");
466 return FAIL;
467 }
468}
469
470//=============================================================================================================
471
472std::unique_ptr<MNESourceSpace> FwdBemModel::make_guesses(MNESurface* guess_surf, float guessrad, const Eigen::Vector3f& guess_r0, float grid, float exclude, float mindist)
473{
474 QString bemname;
475 MNESurface::UPtr sphere_owner;
476 std::unique_ptr<MNESourceSpace> res;
477 int k;
478 float dist;
479
480 if (!guess_surf) {
481 qInfo("Making a spherical guess space with radius %7.1f mm...", 1000 * guessrad);
482
483 QFile bemFile(QString(QCoreApplication::applicationDirPath() + "/../resources/general/surf2bem/icos.fif"));
484 if (!QCoreApplication::startingUp())
485 bemFile.setFileName(QCoreApplication::applicationDirPath() + QString("/../resources/general/surf2bem/icos.fif"));
486 else if (!bemFile.exists())
487 bemFile.setFileName("../resources/general/surf2bem/icos.fif");
488
489 if (!bemFile.exists()) {
490 qDebug() << bemFile.fileName() << "does not exists.";
491 return res;
492 }
493
494 bemname = bemFile.fileName();
495
496 sphere_owner = MNESurface::read_bem_surface(bemname, 9003, false);
497 if (!sphere_owner)
498 return res;
499
500 for (k = 0; k < sphere_owner->np; k++) {
501 dist = sphere_owner->point(k).norm();
502 sphere_owner->rr.row(k) = (guessrad * sphere_owner->rr.row(k) / dist) + guess_r0.transpose();
503 }
504 if (sphere_owner->add_geometry_info(true) == FAIL)
505 return res;
506 guess_surf = sphere_owner.get();
507 } else {
508 qInfo("Guess surface (%d = %s) is in %s coordinates",
509 guess_surf->id, FwdBemModel::fwd_bem_explain_surface(guess_surf->id).toUtf8().constData(),
510 mne_coord_frame_name_40(guess_surf->coord_frame).toUtf8().constData());
511 }
512 qInfo("Filtering (grid = %6.f mm)...", 1000 * grid);
513 res = MNESourceSpace::make_volume_source_space(*guess_surf, grid, exclude, mindist);
514
515 return res;
516}
517
518//=============================================================================================================
519
520double FwdBemModel::calc_beta(const Eigen::Vector3d& rk, const Eigen::Vector3d& rk1)
521
522{
523 Eigen::Vector3d rkk1 = rk1 - rk;
524 double size = rkk1.norm();
525
526 return log((rk.norm() * size + rk.dot(rkk1)) /
527 (rk1.norm() * size + rk1.dot(rkk1))) /
528 size;
529}
530
531//=============================================================================================================
532
533void FwdBemModel::lin_pot_coeff(const Eigen::Vector3f& from, MNETriangle& to, Eigen::Vector3d& omega) /* The final result */
534/*
535 * The linear potential matrix element computations
536 */
537{
538 Eigen::Vector3d y1, y2, y3; /* Corners with origin at from */
539 double l1, l2, l3; /* Lengths of y1, y2, and y3 */
540 double solid; /* The standard solid angle */
541 Eigen::Vector3d vec_omega; /* The cross-product integral */
542 double triple; /* (y1 x y2) . y3 */
543 double ss;
544 double beta[3], bbeta[3];
545 int j, k;
546 double n2, area2;
547 static const double solid_eps = 4.0 * M_PI / 1.0E6;
548 /*
549 * Corners with origin at from (float → double)
550 */
551 y1 = (to.r1 - from).cast<double>();
552 y2 = (to.r2 - from).cast<double>();
553 y3 = (to.r3 - from).cast<double>();
554 /*
555 * Circular indexing for vertex access
556 */
557 const Eigen::Vector3d* y_arr[5] = {&y3, &y1, &y2, &y3, &y1};
558 const Eigen::Vector3d** yy = y_arr + 1; /* yy can have index -1! */
559 /*
560 * The standard solid angle computation
561 */
562 Eigen::Vector3d cross = y1.cross(y2);
563 triple = cross.dot(y3);
564
565 l1 = y1.norm();
566 l2 = y2.norm();
567 l3 = y3.norm();
568 ss = (l1 * l2 * l3 + y1.dot(y2) * l3 + y1.dot(y3) * l2 + y2.dot(y3) * l1);
569 solid = 2.0 * atan2(triple, ss);
570 if (std::fabs(solid) < solid_eps) {
571 omega.setZero();
572 } else {
573 /*
574 * Calculate the magic vector vec_omega
575 */
576 for (j = 0; j < 3; j++)
577 beta[j] = calc_beta(*yy[j], *yy[j + 1]);
578 bbeta[0] = beta[2] - beta[0];
579 bbeta[1] = beta[0] - beta[1];
580 bbeta[2] = beta[1] - beta[2];
581
582 vec_omega.setZero();
583 for (j = 0; j < 3; j++)
584 vec_omega += bbeta[j] * (*yy[j]);
585 /*
586 * Put it all together...
587 */
588 area2 = 2.0 * to.area;
589 n2 = 1.0 / (area2 * area2);
590 Eigen::Vector3d nn_d = to.nn.cast<double>();
591 for (k = 0; k < 3; k++) {
592 Eigen::Vector3d z = yy[k + 1]->cross(*yy[k - 1]);
593 Eigen::Vector3d diff = *yy[k - 1] - *yy[k + 1];
594 omega[k] = n2 * (-area2 * z.dot(nn_d) * solid + triple * diff.dot(vec_omega));
595 }
596 }
597#ifdef CHECK
598 /*
599 * Check it out!
600 *
601 * omega1 + omega2 + omega3 = solid
602 */
603 double rel1 = (solid + omega[0] + omega[1] + omega[2]) / solid;
604 /*
605 * The other way of evaluating...
606 */
607 Eigen::Vector3d check = Eigen::Vector3d::Zero();
608 Eigen::Vector3d nn_check = to.nn.cast<double>();
609 for (k = 0; k < 3; k++) {
610 Eigen::Vector3d z = nn_check.cross(*yy[k]);
611 check += omega[k] * z;
612 }
613 check *= -area2 / triple;
614 qInfo("(%g,%g,%g) =? (%g,%g,%g)",
615 check[0], check[1], check[2],
616 vec_omega[0], vec_omega[1], vec_omega[2]);
617 check -= vec_omega;
618 double rel2 = sqrt(check.dot(check) / vec_omega.dot(vec_omega));
619 qInfo("err1 = %g, err2 = %g", 100 * rel1, 100 * rel2);
620#endif
621 return;
622}
623
624//=============================================================================================================
625
626void FwdBemModel::correct_auto_elements(MNESurface& surf, Eigen::MatrixXf& mat)
627/*
628 * Improve auto-element approximation...
629 */
630{
631 float sum, miss;
632 int nnode = surf.np;
633 int ntri = surf.ntri;
634 int nmemb;
635 int j, k;
636 float pi2 = static_cast<float>(2.0 * M_PI);
637 MNETriangle* tri;
638
639#ifdef SIMPLE
640 for (j = 0; j < nnode; j++) {
641 sum = 0.0;
642 for (k = 0; k < nnode; k++)
643 sum = sum + mat(j, k);
644 qInfo("row %d sum = %g missing = %g", j + 1, sum / pi2,
645 1.0 - sum / pi2);
646 mat(j, j) = pi2 - sum;
647 }
648#else
649 for (j = 0; j < nnode; j++) {
650 /*
651 * How much is missing?
652 */
653 sum = 0.0;
654 for (k = 0; k < nnode; k++)
655 sum = sum + mat(j, k);
656 miss = pi2 - sum;
657 nmemb = surf.nneighbor_tri[j];
658 /*
659 * The node itself receives one half
660 */
661 mat(j, j) = miss / 2.0;
662 /*
663 * The rest is divided evenly among the member nodes...
664 */
665 miss = miss / (4.0 * nmemb);
666 for (k = 0, tri = surf.tris.data(); k < ntri; k++, tri++) {
667 if (tri->vert[0] == j) {
668 mat(j, tri->vert[1]) = mat(j, tri->vert[1]) + miss;
669 mat(j, tri->vert[2]) = mat(j, tri->vert[2]) + miss;
670 } else if (tri->vert[1] == j) {
671 mat(j, tri->vert[0]) = mat(j, tri->vert[0]) + miss;
672 mat(j, tri->vert[2]) = mat(j, tri->vert[2]) + miss;
673 } else if (tri->vert[2] == j) {
674 mat(j, tri->vert[0]) = mat(j, tri->vert[0]) + miss;
675 mat(j, tri->vert[1]) = mat(j, tri->vert[1]) + miss;
676 }
677 }
678 /*
679 * Just check it it out...
680 *
681 for (k = 0, sum = 0; k < nnode; k++)
682 sum = sum + mat(j,k);
683 qInfo("row %d sum = %g",j+1,sum/pi2);
684 */
685 }
686#endif
687 return;
688}
689
690//=============================================================================================================
691
692Eigen::MatrixXf FwdBemModel::fwd_bem_lin_pot_coeff(const std::vector<MNESurface*>& surfs)
693/*
694 * Calculate the coefficients for linear collocation approach
695 */
696{
697 int np1, np2, ntri, np_tot, np_max;
698 MNETriangle* tri;
699 Eigen::Vector3d omega;
700 int j, k, p, q, c;
701 int joff, koff;
702 MNESurface* surf1;
703 MNESurface* surf2;
704
705 for (p = 0, np_tot = np_max = 0; p < static_cast<int>(surfs.size()); p++) {
706 np_tot += surfs[p]->np;
707 if (surfs[p]->np > np_max)
708 np_max = surfs[p]->np;
709 }
710
711 Eigen::MatrixXf mat = Eigen::MatrixXf::Zero(np_tot, np_tot);
712 Eigen::VectorXd row(np_max);
713 for (p = 0, joff = 0; p < static_cast<int>(surfs.size()); p++, joff = joff + np1) {
714 surf1 = surfs[p];
715 np1 = surf1->np;
716 for (q = 0, koff = 0; q < static_cast<int>(surfs.size()); q++, koff = koff + np2) {
717 surf2 = surfs[q];
718 np2 = surf2->np;
719 ntri = surf2->ntri;
720
721 qInfo("\t\t%s (%d) -> %s (%d) ... ",
722 fwd_bem_explain_surface(surf1->id).toUtf8().constData(), np1,
723 fwd_bem_explain_surface(surf2->id).toUtf8().constData(), np2);
724
725 for (j = 0; j < np1; j++) {
726 for (k = 0; k < np2; k++)
727 row[k] = 0.0;
728 for (k = 0, tri = surf2->tris.data(); k < ntri; k++, tri++) {
729 /*
730 * No contribution from a triangle that
731 * this vertex belongs to
732 */
733 if (p == q && (tri->vert[0] == j || tri->vert[1] == j || tri->vert[2] == j))
734 continue;
735 /*
736 * Otherwise do the hard job
737 */
738 lin_pot_coeff(surf1->point(j), *tri, omega);
739 for (c = 0; c < 3; c++)
740 row[tri->vert[c]] = row[tri->vert[c]] - omega[c];
741 }
742 for (k = 0; k < np2; k++)
743 mat(j + joff, k + koff) = row[k];
744 }
745 if (p == q) {
746 Eigen::MatrixXf sub_mat = mat.block(joff, koff, np1, np1);
747 correct_auto_elements(*surf1, sub_mat);
748 mat.block(joff, koff, np1, np1) = sub_mat;
749 }
750 qInfo("[done]");
751 }
752 }
753 return mat;
754}
755
756//=============================================================================================================
757
759/*
760 * Compute the linear collocation potential solution
761 */
762{
763 Eigen::MatrixXf coeff;
764 float ip_mult;
765 int k;
766
768
769 // Extract raw surface pointers for coefficient functions
770 std::vector<MNESurface*> rawSurfs;
771 rawSurfs.reserve(nsurf);
772 for (auto& s : surfs)
773 rawSurfs.push_back(s.get());
774
775 qInfo("\nComputing the linear collocation solution...");
776 qInfo("\tMatrix coefficients...");
777 coeff = fwd_bem_lin_pot_coeff(rawSurfs);
778 if (coeff.size() == 0) {
780 return FAIL;
781 }
782
783 for (k = 0, nsol = 0; k < nsurf; k++)
784 nsol += surfs[k]->np;
785
786 qInfo("\tInverting the coefficient matrix...");
788 if (solution.size() == 0) {
790 return FAIL;
791 }
792
793 /*
794 * IP approach?
795 */
796 if ((nsurf == 3) &&
797 (ip_mult = sigma[nsurf - 2] / sigma[nsurf - 1]) <= ip_approach_limit) {
798 Eigen::MatrixXf ip_solution;
799
800 qInfo("IP approach required...");
801
802 qInfo("\tMatrix coefficients (homog)...");
803 std::vector<MNESurface*> last_surfs = {surfs.back().get()};
804 coeff = fwd_bem_lin_pot_coeff(last_surfs);
805 if (coeff.size() == 0) {
807 return FAIL;
808 }
809
810 qInfo("\tInverting the coefficient matrix (homog)...");
811 ip_solution = fwd_bem_homog_solution(coeff, surfs[nsurf - 1]->np);
812 if (ip_solution.size() == 0) {
814 return FAIL;
815 }
816
817 qInfo("\tModify the original solution to incorporate IP approach...");
818
819 fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, nsurf, this->np);
820 }
822 qInfo("Solution ready.");
823 return OK;
824}
825
826//=============================================================================================================
827
828Eigen::MatrixXf FwdBemModel::fwd_bem_multi_solution(Eigen::MatrixXf& solids, const Eigen::MatrixXf* gamma, int nsurf, const Eigen::VectorXi& ntri)
829/*
830 * Invert I - solids/(2*M_PI)
831 * Take deflation into account
832 * The matrix is destroyed after inversion
833 * This is the general multilayer case
834 */
835{
836 int j, k, p, q;
837 float defl;
838 float pi2 = static_cast<float>(1.0 / (2 * M_PI));
839 float mult;
840 int joff, koff, jup, kup, ntot;
841
842 for (j = 0, ntot = 0; j < nsurf; j++)
843 ntot += ntri[j];
844 defl = 1.0 / ntot;
845 /*
846 * Modify the matrix
847 */
848 for (p = 0, joff = 0; p < nsurf; p++) {
849 jup = ntri[p] + joff;
850 for (q = 0, koff = 0; q < nsurf; q++) {
851 kup = ntri[q] + koff;
852 mult = (gamma == nullptr) ? pi2 : pi2 * (*gamma)(p, q);
853 for (j = joff; j < jup; j++)
854 for (k = koff; k < kup; k++)
855 solids(j, k) = defl - solids(j, k) * mult;
856 koff = kup;
857 }
858 joff = jup;
859 }
860 for (k = 0; k < ntot; k++)
861 solids(k, k) = solids(k, k) + 1.0;
862
863 Eigen::MatrixXf result = solids.inverse();
864 return result;
865}
866
867//=============================================================================================================
868
869Eigen::MatrixXf FwdBemModel::fwd_bem_homog_solution(Eigen::MatrixXf& solids, int ntri)
870/*
871 * Invert I - solids/(2*M_PI)
872 * Take deflation into account
873 * The matrix is destroyed after inversion
874 * This is the homogeneous model case
875 */
876{
877 return fwd_bem_multi_solution(solids, nullptr, 1, Eigen::VectorXi::Constant(1, ntri));
878}
879
880//=============================================================================================================
881
882void FwdBemModel::fwd_bem_ip_modify_solution(Eigen::MatrixXf& solution, Eigen::MatrixXf& ip_solution, float ip_mult, int nsurf, const Eigen::VectorXi& ntri)
883/*
884 * Modify the solution according to the IP approach
885 */
886{
887 int s;
888 int j, k, joff, koff, nlast;
889 float mult;
890
891 for (s = 0, koff = 0; s < nsurf - 1; s++)
892 koff = koff + ntri[s];
893 nlast = ntri[nsurf - 1];
894
895 Eigen::VectorXf row(nlast);
896 mult = (1.0 + ip_mult) / ip_mult;
897
898 qInfo("\t\tCombining...");
899 qInfo("t ");
900 ip_solution.transposeInPlace();
901
902 for (s = 0, joff = 0; s < nsurf; s++) {
903 qInfo("%d3 ", s + 1);
904 /*
905 * For each row in this surface block, compute dot products
906 * with the transposed ip_solution and subtract 2x the result
907 */
908 for (j = 0; j < ntri[s]; j++) {
909 for (k = 0; k < nlast; k++) {
910 row[k] = solution.row(j + joff).segment(koff, nlast).dot(ip_solution.row(k).head(nlast));
911 }
912 solution.row(j + joff).segment(koff, nlast) -= 2.0f * row.transpose();
913 }
914 joff = joff + ntri[s];
915 }
916
917 qInfo("t ");
918 ip_solution.transposeInPlace();
919
920 qInfo("33 ");
921 /*
922 * The lower right corner is a special case
923 */
924 for (j = 0; j < nlast; j++)
925 for (k = 0; k < nlast; k++)
926 solution(j + koff, k + koff) += mult * ip_solution(j, k);
927 /*
928 * Final scaling
929 */
930 qInfo("done.\n\t\tScaling...");
931 solution *= ip_mult;
932 qInfo("done.");
933 return;
934}
935
936//=============================================================================================================
937
938int FwdBemModel::fwd_bem_check_solids(const Eigen::MatrixXf& angles, int ntri1, int ntri2, float desired)
939/*
940 * Check the angle computations
941 */
942{
943 float sum;
944 int j, k;
945 int res = 0;
946
947 Eigen::VectorXf sums(ntri1);
948 for (j = 0; j < ntri1; j++) {
949 sum = 0;
950 for (k = 0; k < ntri2; k++)
951 sum = sum + angles(j, k);
952 sums[j] = sum / (2 * M_PI);
953 }
954 for (j = 0; j < ntri1; j++)
955 /*
956 * Three cases:
957 * same surface: sum = 2*pi
958 * to outer: sum = 4*pi
959 * to inner: sum = 0*pi;
960 */
961 if (std::fabs(sums[j] - desired) > 1e-4) {
962 qWarning("solid angle matrix: rowsum[%d] = 2PI*%g",
963 j + 1, sums[j]);
964 res = -1;
965 break;
966 }
967 return res;
968}
969
970//=============================================================================================================
971
972Eigen::MatrixXf FwdBemModel::fwd_bem_solid_angles(const std::vector<MNESurface*>& surfs)
973/*
974 * Compute the solid angle matrix
975 */
976{
977 MNESurface* surf1;
978 MNESurface* surf2;
979 MNETriangle* tri;
980 int ntri1, ntri2, ntri_tot;
981 int j, k, p, q;
982 int joff, koff;
983 float result;
984 float desired;
985
986 for (p = 0, ntri_tot = 0; p < static_cast<int>(surfs.size()); p++)
987 ntri_tot += surfs[p]->ntri;
988
989 Eigen::MatrixXf solids = Eigen::MatrixXf::Zero(ntri_tot, ntri_tot);
990 for (p = 0, joff = 0; p < static_cast<int>(surfs.size()); p++, joff = joff + ntri1) {
991 surf1 = surfs[p];
992 ntri1 = surf1->ntri;
993 for (q = 0, koff = 0; q < static_cast<int>(surfs.size()); q++, koff = koff + ntri2) {
994 surf2 = surfs[q];
995 ntri2 = surf2->ntri;
996 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);
997 for (j = 0; j < ntri1; j++)
998 for (k = 0, tri = surf2->tris.data(); k < ntri2; k++, tri++) {
999 if (p == q && j == k)
1000 result = 0.0;
1001 else
1002 result = MNESurfaceOrVolume::solid_angle(surf1->tris[j].cent, *tri);
1003 solids(j + joff, k + koff) = result;
1004 }
1005 qInfo("[done]");
1006 if (p == q)
1007 desired = 1;
1008 else if (p < q)
1009 desired = 0;
1010 else
1011 desired = 2;
1012 Eigen::MatrixXf sub_block = solids.block(joff, koff, ntri1, ntri2);
1013 if (fwd_bem_check_solids(sub_block, ntri1, ntri2, desired) == FAIL) {
1014 return Eigen::MatrixXf();
1015 }
1016 }
1017 }
1018 return solids;
1019}
1020
1021//=============================================================================================================
1022
1024/*
1025 * Compute the solution for the constant collocation approach
1026 */
1027{
1028 Eigen::MatrixXf solids;
1029 int k;
1030 float ip_mult;
1031
1033
1034 // Extract raw surface pointers for coefficient functions
1035 std::vector<MNESurface*> rawSurfs;
1036 rawSurfs.reserve(nsurf);
1037 for (auto& s : surfs)
1038 rawSurfs.push_back(s.get());
1039
1040 qInfo("\nComputing the constant collocation solution...");
1041 qInfo("\tSolid angles...");
1042 solids = fwd_bem_solid_angles(rawSurfs);
1043 if (solids.size() == 0) {
1045 return FAIL;
1046 }
1047
1048 for (k = 0, nsol = 0; k < nsurf; k++)
1049 nsol += surfs[k]->ntri;
1050
1051 qInfo("\tInverting the coefficient matrix...");
1053 if (solution.size() == 0) {
1055 return FAIL;
1056 }
1057 /*
1058 * IP approach?
1059 */
1060 if ((nsurf == 3) &&
1061 (ip_mult = sigma[nsurf - 2] / sigma[nsurf - 1]) <= ip_approach_limit) {
1062 Eigen::MatrixXf ip_solution;
1063
1064 qInfo("IP approach required...");
1065
1066 qInfo("\tSolid angles (homog)...");
1067 std::vector<MNESurface*> last_surfs = {surfs.back().get()};
1068 solids = fwd_bem_solid_angles(last_surfs);
1069 if (solids.size() == 0) {
1071 return FAIL;
1072 }
1073
1074 qInfo("\tInverting the coefficient matrix (homog)...");
1075 ip_solution = fwd_bem_homog_solution(solids, surfs[nsurf - 1]->ntri);
1076 if (ip_solution.size() == 0) {
1078 return FAIL;
1079 }
1080
1081 qInfo("\tModify the original solution to incorporate IP approach...");
1082 fwd_bem_ip_modify_solution(solution, ip_solution, ip_mult, nsurf, this->ntri);
1083 }
1085 qInfo("Solution ready.");
1086
1087 return OK;
1088}
1089
1090//=============================================================================================================
1091
1093/*
1094 * Compute the solution
1095 */
1096{
1097 /*
1098 * Compute the solution
1099 */
1100 if (bemMethod == FWD_BEM_LINEAR_COLL)
1102 else if (bemMethod == FWD_BEM_CONSTANT_COLL)
1104
1106 qWarning("Unknown BEM method: %d", bemMethod);
1107 return FAIL;
1108}
1109
1110//=============================================================================================================
1111
1112int FwdBemModel::fwd_bem_load_recompute_solution(const QString& name, int bemMethod, int force_recompute)
1113/*
1114 * Load or recompute the potential solution matrix
1115 */
1116{
1117 int solres;
1118
1119 if (!force_recompute) {
1121 solres = fwd_bem_load_solution(name, bemMethod);
1122 if (solres == LOADED) {
1123 qInfo("\nLoaded %s BEM solution from %s", fwd_bem_explain_method(this->bem_method).toUtf8().constData(), name.toUtf8().constData());
1124 return OK;
1125 } else if (solres == FAIL)
1126 return FAIL;
1127#ifdef DEBUG
1128 else
1129 qWarning("Desired BEM solution not available in %s (%s)", name, err_get_error());
1130#endif
1131 }
1132 if (bemMethod == FWD_BEM_UNKNOWN)
1133 bemMethod = FWD_BEM_LINEAR_COLL;
1134 return fwd_bem_compute_solution(bemMethod);
1135}
1136
1137//=============================================================================================================
1138
1139int FwdBemModel::fwd_bem_save_model(const QString& name) const
1140{
1141 if (nsurf == 0) {
1142 qWarning("[FwdBemModel::fwd_bem_save_model] No model to save");
1143 return FAIL;
1144 }
1146 qWarning("[FwdBemModel::fwd_bem_save_model] Unknown BEM method : %d", bem_method);
1147 return FAIL;
1148 }
1149 QFile file(name);
1151 if (!stream) {
1152 return FAIL;
1153 }
1154 stream->start_block(FIFFB_BEM);
1155 int coordFrame = surfs[0]->coord_frame;
1156 stream->write_int(FIFF_BEM_COORD_FRAME, &coordFrame);
1157 for (int k = 0; k < nsurf; ++k) {
1158 surfs[k]->sigma = sigma[k];
1159 stream->start_block(FIFFB_BEM_SURF);
1160 surfs[k]->writeToStream(stream.data());
1161 stream->end_block(FIFFB_BEM_SURF);
1162 }
1163 if (solution.size() > 0) {
1165 stream->write_int(FIFF_BEM_APPROX, &approx);
1166 stream->write_float_matrix(FIFF_BEM_POT_SOLUTION, solution);
1167 }
1168 stream->end_block(FIFFB_BEM);
1169 stream->end_file();
1170 return OK;
1171}
1172
1173//=============================================================================================================
1174
1175float FwdBemModel::fwd_bem_inf_field(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp, const Eigen::Vector3f& dir)
1176/*
1177 * Infinite-medium magnetic field
1178 * (without \mu_0/4\pi)
1179 */
1180{
1181 Eigen::Vector3f diff = rp - rd;
1182 float diff2 = diff.squaredNorm();
1183 Eigen::Vector3f cr = Q.cross(diff);
1184
1185 return cr.dot(dir) / (diff2 * std::sqrt(diff2));
1186}
1187
1188//=============================================================================================================
1189
1190float FwdBemModel::fwd_bem_inf_pot(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp)
1191/*
1192 * The infinite medium potential
1193 */
1194{
1195 Eigen::Vector3f diff = rp - rd;
1196 float diff2 = diff.squaredNorm();
1197 return Q.dot(diff) / (4.0 * M_PI * diff2 * std::sqrt(diff2));
1198}
1199
1200//=============================================================================================================
1201
1203/*
1204 * Set up for computing the solution at a set of electrodes
1205 */
1206{
1207 FwdCoil* el;
1208 MNESurface* scalp;
1209 int k, p, q, v;
1210 float r[3], w[3], dist;
1211 int best;
1212 MNETriangle* tri;
1213 float x, y, z;
1214 FwdBemSolution* sol = nullptr;
1215
1216 if (solution.size() == 0) {
1217 qWarning("Solution not computed in fwd_bem_specify_els");
1218 return FAIL;
1219 }
1220 if (!els || els->ncoil() == 0)
1221 return OK;
1222 els->user_data.reset();
1223 /*
1224 * Hard work follows
1225 */
1226 els->user_data = std::make_unique<FwdBemSolution>();
1227 sol = els->user_data.get();
1228
1229 sol->ncoil = els->ncoil();
1230 sol->np = nsol;
1231 sol->solution = Eigen::MatrixXf::Zero(sol->ncoil, sol->np);
1232 /*
1233 * Go through all coils
1234 */
1235 for (k = 0; k < els->ncoil(); k++) {
1236 el = els->coils[k].get();
1237 scalp = surfs[0].get();
1238 /*
1239 * Go through all 'integration points'
1240 */
1241 for (p = 0; p < el->np; p++) {
1242 r[0] = el->rmag(p, 0);
1243 r[1] = el->rmag(p, 1);
1244 r[2] = el->rmag(p, 2);
1245 if (!head_mri_t.isEmpty())
1247 best = scalp->project_to_surface(nullptr, Eigen::Map<const Eigen::Vector3f>(r), dist);
1248 if (best < 0) {
1249 qWarning("One of the electrodes could not be projected onto the scalp surface. How come?");
1250 els->user_data.reset();
1251 return FAIL;
1252 }
1254 /*
1255 * Simply pick the value at the triangle
1256 */
1257 for (q = 0; q < nsol; q++)
1258 sol->solution(k, q) += el->w[p] * solution(best, q);
1259 } else if (bem_method == FWD_BEM_LINEAR_COLL) {
1260 /*
1261 * Calculate a linear interpolation between the vertex values
1262 */
1263 tri = &scalp->tris[best];
1264 scalp->triangle_coords(Eigen::Map<const Eigen::Vector3f>(r), best, x, y, z);
1265
1266 w[0] = el->w[p] * (1.0 - x - y);
1267 w[1] = el->w[p] * x;
1268 w[2] = el->w[p] * y;
1269 for (v = 0; v < 3; v++) {
1270 for (q = 0; q < nsol; q++)
1271 sol->solution(k, q) += w[v] * solution(tri->vert[v], q);
1272 }
1273 } else {
1274 qWarning("Unknown BEM approximation method : %d", bem_method);
1275 els->user_data.reset();
1276 return FAIL;
1277 }
1278 }
1279 }
1280 return OK;
1281}
1282
1283//=============================================================================================================
1284
1285void 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)
1286/*
1287 * Compute the potentials due to a current dipole
1288 */
1289{
1290 MNETriangle* tri;
1291 int nTriangles;
1292 int s, k, p, nSolutions, pp;
1293 float mult;
1294 Eigen::Vector3f ee;
1295 Eigen::Vector3f mri_rd = rd;
1296 Eigen::Vector3f mri_Q = Q;
1297
1298 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
1299
1300 if (v0.size() == 0)
1301 v0.resize(this->nsol);
1302 float* v0p = v0.data();
1303
1304 if (!head_mri_t.isEmpty()) {
1307 }
1308 for (pp = X; pp <= Z; pp++) {
1309 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
1310
1311 ee = Eigen::Vector3f::Unit(pp);
1312 if (!head_mri_t.isEmpty())
1314
1315 for (s = 0, p = 0; s < nsurf; s++) {
1316 nTriangles = surfs[s]->ntri;
1317 tri = surfs[s]->tris.data();
1318 mult = source_mult[s];
1319 for (k = 0; k < nTriangles; k++, tri++)
1320 v0p[p++] = mult * fwd_bem_inf_pot_der(mri_rd, mri_Q, tri->cent, ee);
1321 }
1322 if (els) {
1323 FwdBemSolution* sol = els->user_data.get();
1324 nSolutions = sol->ncoil;
1325 for (k = 0; k < nSolutions; k++)
1326 grad[k] = sol->solution.row(k).dot(v0);
1327 } else {
1328 nSolutions = all_surfs ? this->nsol : surfs[0]->ntri;
1329 for (k = 0; k < nSolutions; k++)
1330 grad[k] = solution.row(k).dot(v0);
1331 }
1332 }
1333 return;
1334}
1335
1336//=============================================================================================================
1337
1338void FwdBemModel::fwd_bem_lin_pot_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet* els, int all_surfs, Eigen::Ref<Eigen::VectorXf> pot)
1339/*
1340 * Compute the potentials due to a current dipole
1341 * using the linear potential approximation
1342 */
1343{
1344 int nPoints;
1345 int s, k, p, nSolutions;
1346 float mult;
1347 Eigen::Vector3f mri_rd = rd;
1348 Eigen::Vector3f mri_Q = Q;
1349
1350 if (v0.size() == 0)
1351 v0.resize(this->nsol);
1352 float* v0p = v0.data();
1353
1354 if (!head_mri_t.isEmpty()) {
1357 }
1358 for (s = 0, p = 0; s < nsurf; s++) {
1359 nPoints = surfs[s]->np;
1360 mult = source_mult[s];
1361 for (k = 0; k < nPoints; k++)
1362 v0p[p++] = mult * fwd_bem_inf_pot(mri_rd, mri_Q, surfs[s]->point(k));
1363 }
1364 if (els) {
1365 FwdBemSolution* sol = els->user_data.get();
1366 nSolutions = sol->ncoil;
1367 for (k = 0; k < nSolutions; k++)
1368 pot[k] = sol->solution.row(k).dot(v0);
1369 } else {
1370 nSolutions = all_surfs ? this->nsol : surfs[0]->np;
1371 for (k = 0; k < nSolutions; k++)
1372 pot[k] = solution.row(k).dot(v0);
1373 }
1374 return;
1375}
1376
1377//=============================================================================================================
1378
1379void 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)
1380/*
1381 * Compute the derivaties of potentials due to a current dipole with respect to the dipole position
1382 * using the linear potential approximation
1383 */
1384{
1385 int nPoints;
1386 int s, k, p, nSolutions, pp;
1387 float mult;
1388 Eigen::Vector3f mri_rd = rd;
1389 Eigen::Vector3f mri_Q = Q;
1390 Eigen::Vector3f ee;
1391
1392 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
1393
1394 if (v0.size() == 0)
1395 v0.resize(this->nsol);
1396 float* v0p = v0.data();
1397
1398 if (!head_mri_t.isEmpty()) {
1401 }
1402 for (pp = X; pp <= Z; pp++) {
1403 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
1404
1405 ee = Eigen::Vector3f::Unit(pp);
1406 if (!head_mri_t.isEmpty())
1408
1409 for (s = 0, p = 0; s < nsurf; s++) {
1410 nPoints = surfs[s]->np;
1411 mult = source_mult[s];
1412 for (k = 0; k < nPoints; k++)
1413 v0p[p++] = mult * fwd_bem_inf_pot_der(mri_rd, mri_Q, surfs[s]->point(k), ee);
1414 }
1415 if (els) {
1416 FwdBemSolution* sol = els->user_data.get();
1417 nSolutions = sol->ncoil;
1418 for (k = 0; k < nSolutions; k++)
1419 grad[k] = sol->solution.row(k).dot(v0);
1420 } else {
1421 nSolutions = all_surfs ? this->nsol : surfs[0]->np;
1422 for (k = 0; k < nSolutions; k++)
1423 grad[k] = solution.row(k).dot(v0);
1424 }
1425 }
1426 return;
1427}
1428
1429//=============================================================================================================
1430
1431void FwdBemModel::fwd_bem_pot_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet* els, int all_surfs, Eigen::Ref<Eigen::VectorXf> pot)
1432/*
1433 * Compute the potentials due to a current dipole
1434 */
1435{
1436 MNETriangle* tri;
1437 int nTriangles;
1438 int s, k, p, nSolutions;
1439 float mult;
1440 Eigen::Vector3f mri_rd = rd;
1441 Eigen::Vector3f mri_Q = Q;
1442
1443 if (v0.size() == 0)
1444 v0.resize(this->nsol);
1445 float* v0p = v0.data();
1446
1447 if (!head_mri_t.isEmpty()) {
1450 }
1451 for (s = 0, p = 0; s < nsurf; s++) {
1452 nTriangles = surfs[s]->ntri;
1453 tri = surfs[s]->tris.data();
1454 mult = source_mult[s];
1455 for (k = 0; k < nTriangles; k++, tri++)
1456 v0p[p++] = mult * fwd_bem_inf_pot(mri_rd, mri_Q, tri->cent);
1457 }
1458 if (els) {
1459 FwdBemSolution* sol = els->user_data.get();
1460 nSolutions = sol->ncoil;
1461 for (k = 0; k < nSolutions; k++)
1462 pot[k] = sol->solution.row(k).dot(v0);
1463 } else {
1464 nSolutions = all_surfs ? this->nsol : surfs[0]->ntri;
1465 for (k = 0; k < nSolutions; k++)
1466 pot[k] = solution.row(k).dot(v0);
1467 }
1468 return;
1469}
1470
1471//=============================================================================================================
1472
1473int FwdBemModel::fwd_bem_pot_els(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& els, Eigen::Ref<Eigen::VectorXf> pot, void* client) /* The model */
1474/*
1475 * This version calculates the potential on all surfaces
1476 */
1477{
1478 auto* m = static_cast<FwdBemModel*>(client);
1479 FwdBemSolution* sol = els.user_data.get();
1480
1481 if (!m) {
1482 qWarning("No BEM model specified to fwd_bem_pot_els");
1483 return FAIL;
1484 }
1485 if (m->solution.size() == 0) {
1486 qWarning("No solution available for fwd_bem_pot_els");
1487 return FAIL;
1488 }
1489 if (!sol || sol->ncoil != els.ncoil()) {
1490 qWarning("No appropriate electrode-specific data available in fwd_bem_pot_coils");
1491 return FAIL;
1492 }
1493 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
1494 m->fwd_bem_pot_calc(rd, Q, &els, false, pot);
1495 } else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
1496 m->fwd_bem_lin_pot_calc(rd, Q, &els, false, pot);
1497 } else {
1498 qWarning("Unknown BEM method : %d", m->bem_method);
1499 return FAIL;
1500 }
1501 return OK;
1502}
1503
1504//=============================================================================================================
1505
1506int 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 */
1507/*
1508 * This version calculates the potential on all surfaces
1509 */
1510{
1511 auto* m = static_cast<FwdBemModel*>(client);
1512 FwdBemSolution* sol = els.user_data.get();
1513
1514 if (!m) {
1515 qCritical("No BEM model specified to fwd_bem_pot_els");
1516 return FAIL;
1517 }
1518 if (m->solution.size() == 0) {
1519 qCritical("No solution available for fwd_bem_pot_els");
1520 return FAIL;
1521 }
1522 if (!sol || sol->ncoil != els.ncoil()) {
1523 qCritical("No appropriate electrode-specific data available in fwd_bem_pot_coils");
1524 return FAIL;
1525 }
1526 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
1527 m->fwd_bem_pot_calc(rd, Q, &els, false, pot);
1528 m->fwd_bem_pot_grad_calc(rd, Q, &els, false, xgrad, ygrad, zgrad);
1529 } else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
1530 m->fwd_bem_lin_pot_calc(rd, Q, &els, false, pot);
1531 m->fwd_bem_lin_pot_grad_calc(rd, Q, &els, false, xgrad, ygrad, zgrad);
1532 } else {
1533 qCritical("Unknown BEM method : %d", m->bem_method);
1534 return FAIL;
1535 }
1536 return OK;
1537}
1538
1539//=============================================================================================================
1540
1541inline double arsinh(double x)
1542{
1543 return std::asinh(x);
1544}
1545
1546void FwdBemModel::calc_f(const Eigen::Vector3d& xx, const Eigen::Vector3d& yy, Eigen::Vector3d& f0, Eigen::Vector3d& fx, Eigen::Vector3d& fy)
1547{
1548 double det = -xx[1] * yy[0] + xx[2] * yy[0] +
1549 xx[0] * yy[1] - xx[2] * yy[1] - xx[0] * yy[2] + xx[1] * yy[2];
1550
1551 f0[0] = -xx[2] * yy[1] + xx[1] * yy[2];
1552 f0[1] = xx[2] * yy[0] - xx[0] * yy[2];
1553 f0[2] = -xx[1] * yy[0] + xx[0] * yy[1];
1554
1555 fx[0] = yy[1] - yy[2];
1556 fx[1] = -yy[0] + yy[2];
1557 fx[2] = yy[0] - yy[1];
1558
1559 fy[0] = -xx[1] + xx[2];
1560 fy[1] = xx[0] - xx[2];
1561 fy[2] = -xx[0] + xx[1];
1562
1563 f0 /= det;
1564 fx /= det;
1565 fy /= det;
1566}
1567
1568//=============================================================================================================
1569
1570void FwdBemModel::calc_magic(double u, double z, double A, double B, Eigen::Vector3d& beta, double& D)
1571{
1572 double B2 = 1.0 + B * B;
1573 double ABu = A + B * u;
1574 D = sqrt(u * u + z * z + ABu * ABu);
1575 beta[0] = ABu / sqrt(u * u + z * z);
1576 beta[1] = (A * B + B2 * u) / sqrt(A * A + B2 * z * z);
1577 beta[2] = (B * z * z - A * u) / (z * D);
1578}
1579
1580//=============================================================================================================
1581
1582void 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)
1583{
1584 double xx[4], yy[4];
1585 double A, B, z, dx;
1586 Eigen::Vector3d beta;
1587 double I1, Tx, Ty, Txx, Tyy, Sxx, mult;
1588 double S1x, S1y, S2x;
1589 double D1, B2;
1590 int k;
1591 /*
1592 * Preliminaries...
1593 *
1594 * 1. Move origin to viewpoint...
1595 *
1596 */
1597 Eigen::Vector3d y1 = (to.r1 - from).cast<double>();
1598 Eigen::Vector3d y2 = (to.r2 - from).cast<double>();
1599 Eigen::Vector3d y3 = (to.r3 - from).cast<double>();
1600 /*
1601 * 2. Calculate local xy coordinates...
1602 */
1603 Eigen::Vector3d ex_d = to.ex.cast<double>();
1604 Eigen::Vector3d ey_d = to.ey.cast<double>();
1605 xx[0] = y1.dot(ex_d);
1606 xx[1] = y2.dot(ex_d);
1607 xx[2] = y3.dot(ex_d);
1608 xx[3] = xx[0];
1609
1610 yy[0] = y1.dot(ey_d);
1611 yy[1] = y2.dot(ey_d);
1612 yy[2] = y3.dot(ey_d);
1613 yy[3] = yy[0];
1614
1615 calc_f(Eigen::Map<const Eigen::Vector3d>(xx), Eigen::Map<const Eigen::Vector3d>(yy), f0, fx, fy);
1616 /*
1617 * 3. Distance of the plane from origin...
1618 */
1619 z = y1.dot(to.nn.cast<double>());
1620 /*
1621 * Put together the line integral...
1622 * We use the convention where the local y-axis
1623 * is parallel to the last side and, therefore, dx = 0
1624 * on that side. We can thus omit the last side from this
1625 * computation in some cases.
1626 */
1627 I1 = 0.0;
1628 Tx = 0.0;
1629 Ty = 0.0;
1630 S1x = 0.0;
1631 S1y = 0.0;
1632 S2x = 0.0;
1633 for (k = 0; k < 2; k++) {
1634 dx = xx[k + 1] - xx[k];
1635 A = (yy[k] * xx[k + 1] - yy[k + 1] * xx[k]) / dx;
1636 B = (yy[k + 1] - yy[k]) / dx;
1637 B2 = (1.0 + B * B);
1638 /*
1639 * Upper limit
1640 */
1641 calc_magic(xx[k + 1], z, A, B, beta, D1);
1642 I1 = I1 - xx[k + 1] * arsinh(beta[0]) - (A / sqrt(1.0 + B * B)) * arsinh(beta[1]) - z * atan(beta[2]);
1643 Txx = arsinh(beta[1]) / sqrt(B2);
1644 Tx = Tx + Txx;
1645 Ty = Ty + B * Txx;
1646 Sxx = (D1 - A * B * Txx) / B2;
1647 S1x = S1x + Sxx;
1648 S1y = S1y + B * Sxx;
1649 Sxx = (B * D1 + A * Txx) / B2;
1650 S2x = S2x + Sxx;
1651 /*
1652 * Lower limit
1653 */
1654 calc_magic(xx[k], z, A, B, beta, D1);
1655 I1 = I1 + xx[k] * arsinh(beta[0]) + (A / sqrt(1.0 + B * B)) * arsinh(beta[1]) + z * atan(beta[2]);
1656 Txx = arsinh(beta[1]) / sqrt(B2);
1657 Tx = Tx - Txx;
1658 Ty = Ty - B * Txx;
1659 Sxx = (D1 - A * B * Txx) / B2;
1660 S1x = S1x - Sxx;
1661 S1y = S1y - B * Sxx;
1662 Sxx = (B * D1 + A * Txx) / B2;
1663 S2x = S2x - Sxx;
1664 }
1665 /*
1666 * Handle last side (dx = 0) in a special way;
1667 */
1668 mult = 1.0 / sqrt(xx[k] * xx[k] + z * z);
1669 /*
1670 * Upper...
1671 */
1672 Tyy = arsinh(mult * yy[k + 1]);
1673 Ty = Ty + Tyy;
1674 S1y = S1y + xx[k] * Tyy;
1675 /*
1676 * Lower...
1677 */
1678 Tyy = arsinh(mult * yy[k]);
1679 Ty = Ty - Tyy;
1680 S1y = S1y - xx[k] * Tyy;
1681 /*
1682 * Set return values
1683 */
1684 I1p = I1;
1685 T[0] = Tx;
1686 T[1] = Ty;
1687 S1[0] = S1x;
1688 S1[1] = S1y;
1689 S2[0] = S2x;
1690 S2[1] = -S1x;
1691}
1692
1693//=============================================================================================================
1694
1695double FwdBemModel::one_field_coeff(const Eigen::Vector3f& dest, const Eigen::Vector3f& normal, MNETriangle& tri)
1696{
1697 double beta[3];
1698 double bbeta[3];
1699 int j;
1700
1701 Eigen::Vector3d y1 = (tri.r1 - dest).cast<double>();
1702 Eigen::Vector3d y2 = (tri.r2 - dest).cast<double>();
1703 Eigen::Vector3d y3 = (tri.r3 - dest).cast<double>();
1704
1705 const Eigen::Vector3d* yy[4] = {&y1, &y2, &y3, &y1};
1706 for (j = 0; j < 3; j++)
1707 beta[j] = calc_beta(*yy[j], *yy[j + 1]);
1708 bbeta[0] = beta[2] - beta[0];
1709 bbeta[1] = beta[0] - beta[1];
1710 bbeta[2] = beta[1] - beta[2];
1711
1712 Eigen::Vector3d coeff = Eigen::Vector3d::Zero();
1713 for (j = 0; j < 3; j++)
1714 coeff += bbeta[j] * (*yy[j]);
1715 return coeff.dot(normal.cast<double>());
1716}
1717
1718//=============================================================================================================
1719
1720Eigen::MatrixXf FwdBemModel::fwd_bem_field_coeff(FwdCoilSet* coils) /* Gradiometer coil positions */
1721/*
1722 * Compute the weighting factors to obtain the magnetic field
1723 */
1724{
1725 MNESurface* surf;
1726 MNETriangle* tri;
1727 FwdCoil* coil;
1728 FwdCoilSet::UPtr tcoils;
1729 int nTriangles;
1730 int j, k, p, s, off;
1731 double res;
1732 double mult;
1733
1734 if (solution.size() == 0) {
1735 qWarning("Solution matrix missing in fwd_bem_field_coeff");
1736 return Eigen::MatrixXf();
1737 }
1739 qWarning("BEM method should be constant collocation for fwd_bem_field_coeff");
1740 return Eigen::MatrixXf();
1741 }
1742 if (coils->coord_frame != FIFFV_COORD_MRI) {
1743 if (coils->coord_frame == FIFFV_COORD_HEAD) {
1744 if (head_mri_t.isEmpty()) {
1745 qWarning("head -> mri coordinate transform missing in fwd_bem_field_coeff");
1746 return Eigen::MatrixXf();
1747 } else {
1748 /*
1749 * Make a transformed duplicate
1750 */
1751 if ((tcoils = coils->dup_coil_set(head_mri_t)) == nullptr)
1752 return Eigen::MatrixXf();
1753 coils = tcoils.get();
1754 }
1755 } else {
1756 qWarning("Incompatible coil coordinate frame %d for fwd_bem_field_coeff", coils->coord_frame);
1757 return Eigen::MatrixXf();
1758 }
1759 }
1760 nTriangles = nsol;
1761 Eigen::MatrixXf coeff = Eigen::MatrixXf::Zero(coils->ncoil(), nTriangles);
1762
1763 for (s = 0, off = 0; s < nsurf; s++) {
1764 surf = surfs[s].get();
1765 nTriangles = surf->ntri;
1766 tri = surf->tris.data();
1767 mult = field_mult[s];
1768
1769 for (k = 0; k < nTriangles; k++, tri++) {
1770 for (j = 0; j < coils->ncoil(); j++) {
1771 coil = coils->coils[j].get();
1772 res = 0.0;
1773 for (p = 0; p < coil->np; p++)
1774 res = res + coil->w[p] * one_field_coeff(coil->pos(p), coil->dir(p), *tri);
1775 coeff(j, k + off) = mult * res;
1776 }
1777 }
1778 off = off + nTriangles;
1779 }
1780 return coeff;
1781}
1782
1783//=============================================================================================================
1784
1785double FwdBemModel::calc_gamma(const Eigen::Vector3d& rk, const Eigen::Vector3d& rk1)
1786{
1787 Eigen::Vector3d rkk1 = rk1 - rk;
1788 double size = rkk1.norm();
1789
1790 return log((rk1.norm() * size + rk1.dot(rkk1)) /
1791 (rk.norm() * size + rk.dot(rkk1))) /
1792 size;
1793}
1794
1795//=============================================================================================================
1796
1797void FwdBemModel::fwd_bem_one_lin_field_coeff_ferg(const Eigen::Vector3f& dest, const Eigen::Vector3f& dir, MNETriangle& tri, Eigen::Vector3d& res)
1798{
1799 double triple, l1, l2, l3, solid, clen;
1800 double common, sum, beta, gamma;
1801 int k;
1802
1803 Eigen::Vector3d rjk[3];
1804 rjk[0] = (tri.r3 - tri.r2).cast<double>();
1805 rjk[1] = (tri.r1 - tri.r3).cast<double>();
1806 rjk[2] = (tri.r2 - tri.r1).cast<double>();
1807
1808 Eigen::Vector3d y1 = (tri.r1 - dest).cast<double>();
1809 Eigen::Vector3d y2 = (tri.r2 - dest).cast<double>();
1810 Eigen::Vector3d y3 = (tri.r3 - dest).cast<double>();
1811
1812 const Eigen::Vector3d* yy[4] = {&y1, &y2, &y3, &y1};
1813
1814 Eigen::Vector3d nn_d = tri.nn.cast<double>();
1815 clen = y1.dot(nn_d);
1816 Eigen::Vector3d c_vec = clen * nn_d;
1817 Eigen::Vector3d A_vec = dest.cast<double>() + c_vec;
1818
1819 Eigen::Vector3d c1 = tri.r1.cast<double>() - A_vec;
1820 Eigen::Vector3d c2 = tri.r2.cast<double>() - A_vec;
1821 Eigen::Vector3d c3 = tri.r3.cast<double>() - A_vec;
1822
1823 const Eigen::Vector3d* cc[4] = {&c1, &c2, &c3, &c1};
1824 /*
1825 * beta and gamma...
1826 */
1827 for (sum = 0.0, k = 0; k < 3; k++) {
1828 Eigen::Vector3d cross = cc[k]->cross(*cc[k + 1]);
1829 beta = cross.dot(nn_d);
1830 gamma = calc_gamma(*yy[k], *yy[k + 1]);
1831 sum = sum + beta * gamma;
1832 }
1833 /*
1834 * Solid angle...
1835 */
1836 Eigen::Vector3d cross = y1.cross(y2);
1837 triple = cross.dot(y3);
1838
1839 l1 = y1.norm();
1840 l2 = y2.norm();
1841 l3 = y3.norm();
1842 solid = 2.0 * atan2(triple, (l1 * l2 * l3 + y1.dot(y2) * l3 + y1.dot(y3) * l2 + y2.dot(y3) * l1));
1843 /*
1844 * Now we are ready to assemble it all together
1845 */
1846 Eigen::Vector3d dir_d = dir.cast<double>();
1847 common = (sum - clen * solid) / (2.0 * tri.area);
1848 for (k = 0; k < 3; k++)
1849 res[k] = -rjk[k].dot(dir_d) * common;
1850 return;
1851}
1852
1853//=============================================================================================================
1854
1855void FwdBemModel::fwd_bem_one_lin_field_coeff_uran(const Eigen::Vector3f& dest, const Eigen::Vector3f& dir_in, MNETriangle& tri, Eigen::Vector3d& res)
1856{
1857 double I1;
1858 Eigen::Vector2d T, S1, S2;
1859 Eigen::Vector3d f0, fx, fy;
1860 double res_x, res_y;
1861 double x_fac, y_fac;
1862 int k;
1863 /*
1864 * Compute the component integrals
1865 */
1866 field_integrals(dest, tri, I1, T, S1, S2, f0, fx, fy);
1867 /*
1868 * Compute the coefficient for each node...
1869 */
1870 Eigen::Vector3f dir = dir_in.normalized();
1871
1872 x_fac = -dir.dot(tri.ex);
1873 y_fac = -dir.dot(tri.ey);
1874 for (k = 0; k < 3; k++) {
1875 res_x = f0[k] * T[0] + fx[k] * S1[0] + fy[k] * S2[0] + fy[k] * I1;
1876 res_y = f0[k] * T[1] + fx[k] * S1[1] + fy[k] * S2[1] - fx[k] * I1;
1877 res[k] = x_fac * res_x + y_fac * res_y;
1878 }
1879}
1880
1881//=============================================================================================================
1882
1883void FwdBemModel::fwd_bem_one_lin_field_coeff_simple(const Eigen::Vector3f& dest, const Eigen::Vector3f& normal, MNETriangle& source, Eigen::Vector3d& res)
1884{
1885 int k;
1886 const Eigen::Vector3f* rr[3] = {&source.r1, &source.r2, &source.r3};
1887
1888 for (k = 0; k < 3; k++) {
1889 Eigen::Vector3f diff = dest - *rr[k];
1890 float dl = diff.squaredNorm();
1891 Eigen::Vector3f vec_result = diff.cross(source.nn);
1892 res[k] = source.area * vec_result.dot(normal) / (3.0 * dl * sqrt(dl));
1893 }
1894 return;
1895}
1896
1897//=============================================================================================================
1898
1899Eigen::MatrixXf FwdBemModel::fwd_bem_lin_field_coeff(FwdCoilSet* coils, int method) /* Which integration formula to use */
1900/*
1901 * Compute the weighting factors to obtain the magnetic field
1902 * in the linear potential approximation
1903 */
1904{
1905 MNESurface* surf;
1906 MNETriangle* tri;
1907 FwdCoil* coil;
1908 FwdCoilSet::UPtr tcoils;
1909 int nTriangles;
1910 int j, k, p, pp, off, s;
1911 Eigen::Vector3d res, one;
1912 float mult;
1913 linFieldIntFunc func;
1914
1915 if (solution.size() == 0) {
1916 qWarning("Solution matrix missing in fwd_bem_lin_field_coeff");
1917 return Eigen::MatrixXf();
1918 }
1920 qWarning("BEM method should be linear collocation for fwd_bem_lin_field_coeff");
1921 return Eigen::MatrixXf();
1922 }
1923 if (coils->coord_frame != FIFFV_COORD_MRI) {
1924 if (coils->coord_frame == FIFFV_COORD_HEAD) {
1925 if (head_mri_t.isEmpty()) {
1926 qWarning("head -> mri coordinate transform missing in fwd_bem_lin_field_coeff");
1927 return Eigen::MatrixXf();
1928 } else {
1929 /*
1930 * Make a transformed duplicate
1931 */
1932 if ((tcoils = coils->dup_coil_set(head_mri_t)) == nullptr)
1933 return Eigen::MatrixXf();
1934 coils = tcoils.get();
1935 }
1936 } else {
1937 qWarning("Incompatible coil coordinate frame %d for fwd_bem_field_coeff", coils->coord_frame);
1938 return Eigen::MatrixXf();
1939 }
1940 }
1941 if (method == FWD_BEM_LIN_FIELD_FERGUSON)
1943 else if (method == FWD_BEM_LIN_FIELD_URANKAR)
1945 else
1947
1948 Eigen::MatrixXf coeff = Eigen::MatrixXf::Zero(coils->ncoil(), nsol);
1949 /*
1950 * Process each of the surfaces
1951 */
1952 for (s = 0, off = 0; s < nsurf; s++) {
1953 surf = surfs[s].get();
1954 nTriangles = surf->ntri;
1955 tri = surf->tris.data();
1956 mult = field_mult[s];
1957
1958 for (k = 0; k < nTriangles; k++, tri++) {
1959 for (j = 0; j < coils->ncoil(); j++) {
1960 coil = coils->coils[j].get();
1961 res.setZero();
1962 /*
1963 * Accumulate the coefficients for each triangle node...
1964 */
1965 for (p = 0; p < coil->np; p++) {
1966 func(coil->pos(p), coil->dir(p), *tri, one);
1967 res += coil->w[p] * one;
1968 }
1969 /*
1970 * Add these to the corresponding coefficient matrix
1971 * elements...
1972 */
1973 for (pp = 0; pp < 3; pp++)
1974 coeff(j, tri->vert[pp] + off) = coeff(j, tri->vert[pp] + off) + mult * res[pp];
1975 }
1976 }
1977 off = off + surf->np;
1978 }
1979 /*
1980 * Discard the duplicate
1981 */
1982 return coeff;
1983}
1984
1985//=============================================================================================================
1986
1988/*
1989 * Set up for computing the solution at a set of coils
1990 */
1991{
1992 Eigen::MatrixXf sol;
1993 FwdBemSolution* csol = nullptr;
1994
1995 if (solution.size() == 0) {
1996 qWarning("Solution not computed in fwd_bem_specify_coils");
1997 return FAIL;
1998 }
1999 if (coils)
2000 coils->user_data.reset();
2001 if (!coils || coils->ncoil() == 0)
2002 return OK;
2004 sol = fwd_bem_field_coeff(coils);
2005 else if (bem_method == FWD_BEM_LINEAR_COLL)
2007 else {
2008 qWarning("Unknown BEM method in fwd_bem_specify_coils : %d", bem_method);
2009 return FAIL;
2010 }
2011 if (sol.size() == 0)
2012 return FAIL;
2013 coils->user_data = std::make_unique<FwdBemSolution>();
2014 csol = coils->user_data.get();
2015
2016 csol->ncoil = coils->ncoil();
2017 csol->np = nsol;
2018 csol->solution = sol * solution;
2019
2020 return OK;
2021}
2022
2023//=============================================================================================================
2024
2025void FwdBemModel::fwd_bem_lin_field_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> B)
2026/*
2027 * Calculate the magnetic field in a set of coils
2028 */
2029{
2030 int s, k, p, nPoints;
2031 FwdCoil* coil;
2032 float mult;
2033 Eigen::Vector3f my_rd = rd;
2034 Eigen::Vector3f my_Q = Q;
2035 FwdBemSolution* sol = coils.user_data.get();
2036 /*
2037 * Infinite-medium potentials
2038 */
2039 if (v0.size() == 0)
2040 v0.resize(nsol);
2041 float* v0p = v0.data();
2042 /*
2043 * The dipole location and orientation must be transformed
2044 */
2045 if (!head_mri_t.isEmpty()) {
2048 }
2049 /*
2050 * Compute the inifinite-medium potentials at the vertices
2051 */
2052 for (s = 0, p = 0; s < nsurf; s++) {
2053 nPoints = surfs[s]->np;
2054 mult = source_mult[s];
2055 for (k = 0; k < nPoints; k++)
2056 v0p[p++] = mult * fwd_bem_inf_pot(my_rd, my_Q, surfs[s]->point(k));
2057 }
2058 /*
2059 * Primary current contribution
2060 * (can be calculated in the coil/dipole coordinates)
2061 */
2062 for (k = 0; k < coils.ncoil(); k++) {
2063 coil = coils.coils[k].get();
2064 B[k] = 0.0;
2065 for (p = 0; p < coil->np; p++)
2066 B[k] = B[k] + coil->w[p] * fwd_bem_inf_field(rd, Q, coil->pos(p), coil->dir(p));
2067 }
2068 /*
2069 * Volume current contribution
2070 */
2071 for (k = 0; k < coils.ncoil(); k++)
2072 B[k] = B[k] + sol->solution.row(k).dot(v0);
2073 /*
2074 * Scale correctly
2075 */
2076 for (k = 0; k < coils.ncoil(); k++)
2077 B[k] = MAG_FACTOR * B[k];
2078 return;
2079}
2080
2081//=============================================================================================================
2082
2083void FwdBemModel::fwd_bem_field_calc(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> B)
2084/*
2085 * Calculate the magnetic field in a set of coils
2086 */
2087{
2088 int s, k, p, nTriangles;
2089 FwdCoil* coil;
2090 MNETriangle* tri;
2091 float mult;
2092 Eigen::Vector3f my_rd = rd;
2093 Eigen::Vector3f my_Q = Q;
2094 FwdBemSolution* sol = coils.user_data.get();
2095 /*
2096 * Infinite-medium potentials
2097 */
2098 if (v0.size() == 0)
2099 v0.resize(nsol);
2100 float* v0p = v0.data();
2101 /*
2102 * The dipole location and orientation must be transformed
2103 */
2104 if (!head_mri_t.isEmpty()) {
2107 }
2108 /*
2109 * Compute the inifinite-medium potentials at the centers of the triangles
2110 */
2111 for (s = 0, p = 0; s < nsurf; s++) {
2112 nTriangles = surfs[s]->ntri;
2113 tri = surfs[s]->tris.data();
2114 mult = source_mult[s];
2115 for (k = 0; k < nTriangles; k++, tri++)
2116 v0p[p++] = mult * fwd_bem_inf_pot(my_rd, my_Q, tri->cent);
2117 }
2118 /*
2119 * Primary current contribution
2120 * (can be calculated in the coil/dipole coordinates)
2121 */
2122 for (k = 0; k < coils.ncoil(); k++) {
2123 coil = coils.coils[k].get();
2124 B[k] = 0.0;
2125 for (p = 0; p < coil->np; p++)
2126 B[k] = B[k] + coil->w[p] * fwd_bem_inf_field(rd, Q, coil->pos(p), coil->dir(p));
2127 }
2128 /*
2129 * Volume current contribution
2130 */
2131 for (k = 0; k < coils.ncoil(); k++)
2132 B[k] = B[k] + sol->solution.row(k).dot(v0);
2133 /*
2134 * Scale correctly
2135 */
2136 for (k = 0; k < coils.ncoil(); k++)
2137 B[k] = MAG_FACTOR * B[k];
2138 return;
2139}
2140
2141//=============================================================================================================
2142
2143void 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)
2144/*
2145 * Calculate the magnetic field in a set of coils
2146 */
2147{
2148 FwdBemSolution* sol = coils.user_data.get();
2149 int s, k, p, nTriangles, pp;
2150 FwdCoil* coil;
2151 MNETriangle* tri;
2152 float mult;
2153 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
2154 Eigen::Vector3f ee, mri_ee;
2155 Eigen::Vector3f mri_rd = rd;
2156 Eigen::Vector3f mri_Q = Q;
2157 /*
2158 * Infinite-medium potentials
2159 */
2160 if (v0.size() == 0)
2161 v0.resize(nsol);
2162 float* v0p = v0.data();
2163 /*
2164 * The dipole location and orientation must be transformed
2165 */
2166 if (!head_mri_t.isEmpty()) {
2169 }
2170 for (pp = X; pp <= Z; pp++) {
2171 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
2172 /*
2173 * Select the correct gradient component
2174 */
2175 ee = Eigen::Vector3f::Unit(pp);
2176 mri_ee = ee;
2177 if (!head_mri_t.isEmpty())
2179 /*
2180 * Compute the inifinite-medium potential derivatives at the centers of the triangles
2181 */
2182 for (s = 0, p = 0; s < nsurf; s++) {
2183 nTriangles = surfs[s]->ntri;
2184 tri = surfs[s]->tris.data();
2185 mult = source_mult[s];
2186 for (k = 0; k < nTriangles; k++, tri++) {
2187 v0p[p++] = mult * fwd_bem_inf_pot_der(mri_rd, mri_Q, tri->cent, mri_ee);
2188 }
2189 }
2190 /*
2191 * Primary current contribution
2192 * (can be calculated in the coil/dipole coordinates)
2193 */
2194 for (k = 0; k < coils.ncoil(); k++) {
2195 coil = coils.coils[k].get();
2196 grad[k] = 0.0;
2197 for (p = 0; p < coil->np; p++)
2198 grad[k] = grad[k] + coil->w[p] * fwd_bem_inf_field_der(rd, Q, coil->pos(p), coil->dir(p), ee);
2199 }
2200 /*
2201 * Volume current contribution
2202 */
2203 for (k = 0; k < coils.ncoil(); k++)
2204 grad[k] = grad[k] + sol->solution.row(k).dot(v0);
2205 /*
2206 * Scale correctly
2207 */
2208 for (k = 0; k < coils.ncoil(); k++)
2209 grad[k] = MAG_FACTOR * grad[k];
2210 }
2211 return;
2212}
2213
2214//=============================================================================================================
2215
2216float 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)
2217/*
2218 * Derivative of the infinite-medium magnetic field with respect to
2219 * one of the dipole position coordinates (without \mu_0/4\pi)
2220 */
2221{
2222 Eigen::Vector3f diff = rp - rd;
2223 float diff2 = diff.squaredNorm();
2224 float diff3 = std::sqrt(diff2) * diff2;
2225 float diff5 = diff3 * diff2;
2226 Eigen::Vector3f cr = Q.cross(diff);
2227 Eigen::Vector3f crn = dir.cross(Q);
2228
2229 return 3 * cr.dot(dir) * comp.dot(diff) / diff5 - comp.dot(crn) / diff3;
2230}
2231
2232//=============================================================================================================
2233
2234float FwdBemModel::fwd_bem_inf_pot_der(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, const Eigen::Vector3f& rp, const Eigen::Vector3f& comp)
2235/*
2236 * Derivative of the infinite-medium potential with respect to one of
2237 * the dipole position coordinates
2238 */
2239{
2240 Eigen::Vector3f diff = rp - rd;
2241 float diff2 = diff.squaredNorm();
2242 float diff3 = std::sqrt(diff2) * diff2;
2243 float diff5 = diff3 * diff2;
2244
2245 float res = 3 * Q.dot(diff) * comp.dot(diff) / diff5 - comp.dot(Q) / diff3;
2246 return res / (4.0 * M_PI);
2247}
2248
2249//=============================================================================================================
2250
2251void 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)
2252/*
2253 * Calculate the gradient with respect to dipole position coordinates in a set of coils
2254 */
2255{
2256 FwdBemSolution* sol = coils.user_data.get();
2257
2258 int s, k, p, nPoints, pp;
2259 FwdCoil* coil;
2260 float mult;
2261 Eigen::Vector3f ee, mri_ee;
2262 Eigen::Vector3f mri_rd = rd;
2263 Eigen::Vector3f mri_Q = Q;
2264 Eigen::Ref<Eigen::VectorXf>* grads[] = {&xgrad, &ygrad, &zgrad};
2265 /*
2266 * Space for infinite-medium potentials
2267 */
2268 if (v0.size() == 0)
2269 v0.resize(nsol);
2270 float* v0p = v0.data();
2271 /*
2272 * The dipole location and orientation must be transformed
2273 */
2274 if (!head_mri_t.isEmpty()) {
2277 }
2278 for (pp = X; pp <= Z; pp++) {
2279 Eigen::Ref<Eigen::VectorXf>& grad = *grads[pp];
2280 /*
2281 * Select the correct gradient component
2282 */
2283 ee = Eigen::Vector3f::Unit(pp);
2284 mri_ee = ee;
2285 if (!head_mri_t.isEmpty())
2287 /*
2288 * Compute the inifinite-medium potentials at the vertices
2289 */
2290 for (s = 0, p = 0; s < nsurf; s++) {
2291 nPoints = surfs[s]->np;
2292 mult = source_mult[s];
2293
2294 for (k = 0; k < nPoints; k++)
2295 v0p[p++] = mult * fwd_bem_inf_pot_der(mri_rd, mri_Q, surfs[s]->point(k), mri_ee);
2296 }
2297 /*
2298 * Primary current contribution
2299 * (can be calculated in the coil/dipole coordinates)
2300 */
2301 for (k = 0; k < coils.ncoil(); k++) {
2302 coil = coils.coils[k].get();
2303 grad[k] = 0.0;
2304 for (p = 0; p < coil->np; p++)
2305 grad[k] = grad[k] + coil->w[p] * fwd_bem_inf_field_der(rd, Q, coil->pos(p), coil->dir(p), ee);
2306 }
2307 /*
2308 * Volume current contribution
2309 */
2310 for (k = 0; k < coils.ncoil(); k++)
2311 grad[k] = grad[k] + sol->solution.row(k).dot(v0);
2312 /*
2313 * Scale correctly
2314 */
2315 for (k = 0; k < coils.ncoil(); k++)
2316 grad[k] = MAG_FACTOR * grad[k];
2317 }
2318 return;
2319}
2320
2321//=============================================================================================================
2322
2323int FwdBemModel::fwd_bem_field(const Eigen::Vector3f& rd, const Eigen::Vector3f& Q, FwdCoilSet& coils, Eigen::Ref<Eigen::VectorXf> B, void* client) /* The model */
2324/*
2325 * This version calculates the magnetic field in a set of coils
2326 * Call fwd_bem_specify_coils first to establish the coil-specific
2327 * solution matrix
2328 */
2329{
2330 auto* m = static_cast<FwdBemModel*>(client);
2331 FwdBemSolution* sol = coils.user_data.get();
2332
2333 if (!m) {
2334 qWarning("No BEM model specified to fwd_bem_field");
2335 return FAIL;
2336 }
2337 if (!sol || sol->solution.size() == 0 || sol->ncoil != coils.ncoil()) {
2338 qWarning("No appropriate coil-specific data available in fwd_bem_field");
2339 return FAIL;
2340 }
2341 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
2342 m->fwd_bem_field_calc(rd, Q, coils, B);
2343 } else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
2344 m->fwd_bem_lin_field_calc(rd, Q, coils, B);
2345 } else {
2346 qWarning("Unknown BEM method : %d", m->bem_method);
2347 return FAIL;
2348 }
2349 return OK;
2350}
2351
2352//=============================================================================================================
2353
2354int FwdBemModel::fwd_bem_field_grad(const Eigen::Vector3f& rd,
2355 const Eigen::Vector3f& Q,
2356 FwdCoilSet& coils,
2357 Eigen::Ref<Eigen::VectorXf> Bval,
2358 Eigen::Ref<Eigen::VectorXf> xgrad,
2359 Eigen::Ref<Eigen::VectorXf> ygrad,
2360 Eigen::Ref<Eigen::VectorXf> zgrad,
2361 void* client) /* Client data to be passed to some foward modelling routines */
2362{
2363 auto* m = static_cast<FwdBemModel*>(client);
2364 FwdBemSolution* sol = coils.user_data.get();
2365
2366 if (!m) {
2367 qCritical("No BEM model specified to fwd_bem_field");
2368 return FAIL;
2369 }
2370
2371 if (!sol || sol->solution.size() == 0 || sol->ncoil != coils.ncoil()) {
2372 qCritical("No appropriate coil-specific data available in fwd_bem_field");
2373 return FAIL;
2374 }
2375
2376 if (m->bem_method == FWD_BEM_CONSTANT_COLL) {
2377 m->fwd_bem_field_calc(rd, Q, coils, Bval);
2378
2379 m->fwd_bem_field_grad_calc(rd, Q, coils, xgrad, ygrad, zgrad);
2380 } else if (m->bem_method == FWD_BEM_LINEAR_COLL) {
2381 m->fwd_bem_lin_field_calc(rd, Q, coils, Bval);
2382
2383 m->fwd_bem_lin_field_grad_calc(rd, Q, coils, xgrad, ygrad, zgrad);
2384 } else {
2385 qCritical("Unknown BEM method : %d", m->bem_method);
2386 return FAIL;
2387 }
2388
2389 return OK;
2390}
2391
2392//=============================================================================================================
2393
2395/*
2396 * Compute the MEG or EEG forward solution for one source space
2397 * and possibly for only one source component
2398 */
2399{
2400 MNESourceSpace* s = a->s;
2401 int j, p, q;
2402
2403 int ncoil = a->coils_els->ncoil();
2404 Eigen::MatrixXf tmp_vec_res(3, ncoil); /* Only needed for vec_field_pot (3 rows → 3 columns) */
2405
2406 auto fail = [&]() {
2407 a->stat = FAIL;
2408 };
2409
2410 p = a->off;
2411 q = 3 * a->off;
2412 if (a->fixed_ori) { /* The normal source component only */
2413 if (a->field_pot_grad && a->res_grad) { /* Gradient requested? */
2414 for (j = 0; j < s->np; j++) {
2415 if (s->inuse[j]) {
2416 if (a->field_pot_grad(s->point(j),
2417 s->normal(j),
2418 *a->coils_els,
2419 a->res->col(p),
2420 a->res_grad->col(q),
2421 a->res_grad->col(q + 1),
2422 a->res_grad->col(q + 2),
2423 a->client) != OK) {
2424 fail();
2425 return;
2426 }
2427 q = q + 3;
2428 p++;
2429 }
2430 }
2431 } else {
2432 for (j = 0; j < s->np; j++)
2433 if (s->inuse[j]) {
2434 if (a->field_pot(s->point(j),
2435 s->normal(j),
2436 *a->coils_els,
2437 a->res->col(p),
2438 a->client) != OK) {
2439 fail();
2440 return;
2441 }
2442 p++;
2443 }
2444 }
2445 } else { /* All source components */
2446 if (a->field_pot_grad && a->res_grad) { /* Gradient requested? */
2447 for (j = 0; j < s->np; j++) {
2448 if (s->inuse[j]) {
2449 if (a->comp < 0) { /* Compute all components */
2450 if (a->field_pot_grad(s->point(j),
2451 Qx, *a->coils_els, a->res->col(p), a->res_grad->col(q), a->res_grad->col(q + 1), a->res_grad->col(q + 2),
2452 a->client) != OK) {
2453 fail();
2454 return;
2455 }
2456 q = q + 3;
2457 p++;
2458 if (a->field_pot_grad(s->point(j),
2459 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),
2460 a->client) != OK) {
2461 fail();
2462 return;
2463 }
2464 q = q + 3;
2465 p++;
2466 if (a->field_pot_grad(s->point(j),
2467 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),
2468 a->client) != OK) {
2469 fail();
2470 return;
2471 }
2472 q = q + 3;
2473 p++;
2474 } else if (a->comp == 0) { /* Compute x component */
2475 if (a->field_pot_grad(s->point(j),
2476 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),
2477 a->client) != OK) {
2478 fail();
2479 return;
2480 }
2481 q = q + 3;
2482 p++;
2483 q = q + 3;
2484 p++;
2485 q = q + 3;
2486 p++;
2487 } else if (a->comp == 1) { /* Compute y component */
2488 q = q + 3;
2489 p++;
2490 if (a->field_pot_grad(s->point(j),
2491 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),
2492 a->client) != OK) {
2493 fail();
2494 return;
2495 }
2496 q = q + 3;
2497 p++;
2498 q = q + 3;
2499 p++;
2500 } else if (a->comp == 2) { /* Compute z component */
2501 q = q + 3;
2502 p++;
2503 q = q + 3;
2504 p++;
2505 if (a->field_pot_grad(s->point(j),
2506 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),
2507 a->client) != OK) {
2508 fail();
2509 return;
2510 }
2511 q = q + 3;
2512 p++;
2513 }
2514 }
2515 }
2516 } else {
2517 for (j = 0; j < s->np; j++) {
2518 if (s->inuse[j]) {
2519 if (a->vec_field_pot) {
2520 if (a->vec_field_pot(s->point(j), *a->coils_els, tmp_vec_res, a->client) != OK) {
2521 fail();
2522 return;
2523 }
2524 a->res->col(p++) = tmp_vec_res.row(0).transpose();
2525 a->res->col(p++) = tmp_vec_res.row(1).transpose();
2526 a->res->col(p++) = tmp_vec_res.row(2).transpose();
2527 } else {
2528 if (a->comp < 0) { /* Compute all components here */
2529 if (a->field_pot(s->point(j), Qx, *a->coils_els, a->res->col(p++), a->client) != OK) {
2530 fail();
2531 return;
2532 }
2533 if (a->field_pot(s->point(j), Qy, *a->coils_els, a->res->col(p++), a->client) != OK) {
2534 fail();
2535 return;
2536 }
2537 if (a->field_pot(s->point(j), Qz, *a->coils_els, a->res->col(p++), a->client) != OK) {
2538 fail();
2539 return;
2540 }
2541 } else if (a->comp == 0) { /* Compute x component */
2542 if (a->field_pot(s->point(j), Qx, *a->coils_els, a->res->col(p++), a->client) != OK) {
2543 fail();
2544 return;
2545 }
2546 p++;
2547 p++;
2548 } else if (a->comp == 1) { /* Compute y component */
2549 p++;
2550 if (a->field_pot(s->point(j), Qy, *a->coils_els, a->res->col(p++), a->client) != OK) {
2551 fail();
2552 return;
2553 }
2554 p++;
2555 } else if (a->comp == 2) { /* Compute z component */
2556 p++;
2557 p++;
2558 if (a->field_pot(s->point(j), Qz, *a->coils_els, a->res->col(p++), a->client) != OK) {
2559 fail();
2560 return;
2561 }
2562 }
2563 }
2564 }
2565 }
2566 }
2567 }
2568 a->stat = OK;
2569 return;
2570}
2571
2572//=============================================================================================================
2573
2574int FwdBemModel::compute_forward_meg(std::vector<std::unique_ptr<MNESourceSpace>>& spaces,
2575 FwdCoilSet* coils,
2576 FwdCoilSet* comp_coils,
2577 MNECTFCompDataSet* comp_data,
2578 bool fixed_ori,
2579 const Vector3f& r0,
2580 bool use_threads,
2581 FiffNamedMatrix& resp,
2582 FiffNamedMatrix& resp_grad,
2583 bool bDoGrad)
2584/*
2585 * Compute the MEG forward solution
2586 * Use either the sphere model or BEM in the calculations
2587 */
2588{
2589 // A model without surfaces stands for the sphere model (MNE-C passed a null bem_model).
2590 const bool sphere = nsurf == 0;
2591 Eigen::Vector3f sphere_r0 = r0;
2592
2593 Eigen::MatrixXf res_mat; /* The forward solution matrix (ncoil x nsources) */
2594 Eigen::MatrixXf res_grad_mat; /* The gradient (ncoil x 3*nsources) */
2595 MatrixXd matRes;
2596 MatrixXd matResGrad;
2597 int nrow = 0;
2598 std::unique_ptr<FwdCompData> comp;
2599 fwdFieldFunc field; /* Computes the field for one dipole orientation */
2600 fwdVecFieldFunc vec_field; /* Computes the field for all dipole orientations */
2601 fwdFieldGradFunc field_grad; /* Computes the field and gradient with respect to dipole position
2602 * for one dipole orientation */
2603 int nmeg = coils->ncoil(); /* Number of channels */
2604 int nsource; /* Total number of sources */
2605 int nspace = static_cast<int>(spaces.size());
2606 int k, p, q, off;
2607 QStringList names; /* Channel names */
2608 void* client;
2609 FwdThreadArg::UPtr one_arg;
2610 int nproc = QThread::idealThreadCount();
2611 QStringList emptyList;
2612
2613 auto cleanup_fail = [&]() {
2614 one_arg.reset();
2615 comp.reset();
2616 return FAIL;
2617 };
2618
2619 /*
2620 * Use the new compensated field computation
2621 * It works the same way independent of whether or not the compensation is in effect
2622 */
2623 if (sphere) {
2624 comp = FwdCompData::fwd_make_comp_data(comp_data,
2625 coils,
2626 comp_coils,
2630 sphere_r0.data());
2631 if (!comp)
2632 return cleanup_fail();
2634 } else {
2635#ifdef TEST
2636 qInfo("Using differences.");
2637 comp = FwdCompData::fwd_make_comp_data(comp_data,
2638 coils, comp_coils,
2640 nullptr,
2641 my_bem_field_grad,
2642 this);
2643#else
2644 comp = FwdCompData::fwd_make_comp_data(comp_data,
2645 coils,
2646 comp_coils,
2648 nullptr,
2650 this);
2651#endif
2652 if (!comp)
2653 return cleanup_fail();
2654 /*
2655 * Field computation matrices...
2656 */
2657 qInfo("Composing the field computation matrix...");
2658 if (fwd_bem_specify_coils(coils) == FAIL)
2659 return cleanup_fail();
2660 qInfo("[done]");
2661
2662 if (comp->set && comp->set->current) { /* Test just to specify confusing output */
2663 qInfo("Composing the field computation matrix (compensation coils)...");
2664 if (fwd_bem_specify_coils(comp->comp_coils.get()) == FAIL)
2665 return cleanup_fail();
2666 qInfo("[done]");
2667 }
2668 vec_field = nullptr;
2669 }
2672 client = comp.get();
2673 /*
2674 * Count the sources
2675 */
2676 for (k = 0, nsource = 0; k < nspace; k++)
2677 nsource += spaces[k]->nuse;
2678 /*
2679 * Allocate space for the solution
2680 */
2681 {
2682 int ncols = fixed_ori ? nsource : 3 * nsource;
2683 res_mat = Eigen::MatrixXf::Zero(nmeg, ncols);
2684 }
2685 if (bDoGrad) {
2686 int ncols = fixed_ori ? 3 * nsource : 3 * 3 * nsource;
2687 res_grad_mat = Eigen::MatrixXf::Zero(nmeg, ncols);
2688 }
2689 /*
2690 * Set up the argument for the field computation
2691 */
2692 one_arg = std::make_unique<FwdThreadArg>();
2693 one_arg->res = &res_mat;
2694 one_arg->res_grad = bDoGrad ? &res_grad_mat : nullptr;
2695 one_arg->off = 0;
2696 one_arg->coils_els = coils;
2697 one_arg->client = client;
2698 one_arg->s = nullptr;
2699 one_arg->fixed_ori = fixed_ori;
2700 one_arg->field_pot = field;
2701 one_arg->vec_field_pot = vec_field;
2702 one_arg->field_pot_grad = field_grad;
2703
2704 if (nproc < 2)
2705 use_threads = false;
2706
2707 if (use_threads) {
2708 int nthread = (fixed_ori || vec_field) ? nspace : 3 * nspace;
2709 std::vector<FwdThreadArg::UPtr> args;
2710 int stat;
2711 /*
2712 * We need copies to allocate separate workspace for each thread
2713 */
2714 if (fixed_ori || vec_field) {
2715 for (k = 0, off = 0; k < nthread; k++) {
2716 auto t_arg = FwdThreadArg::create_meg_multi_thread_duplicate(*one_arg, !sphere);
2717 t_arg->s = spaces[k].get();
2718 t_arg->off = off;
2719 off = fixed_ori ? off + spaces[k]->nuse : off + 3 * spaces[k]->nuse;
2720 args.push_back(std::move(t_arg));
2721 }
2722 qInfo("%d processors. I will use one thread for each of the %d source spaces.",
2723 nproc, nspace);
2724 } else {
2725 for (k = 0, off = 0, q = 0; k < nspace; k++) {
2726 for (p = 0; p < 3; p++, q++) {
2727 auto t_arg = FwdThreadArg::create_meg_multi_thread_duplicate(*one_arg, !sphere);
2728 t_arg->s = spaces[k].get();
2729 t_arg->off = off;
2730 t_arg->comp = p;
2731 args.push_back(std::move(t_arg));
2732 }
2733 off = fixed_ori ? off + spaces[k]->nuse : off + 3 * spaces[k]->nuse;
2734 }
2735 qInfo("%d processors. I will use %d threads : %d source spaces x 3 source components.",
2736 nproc, nthread, nspace);
2737 }
2738 qInfo("Computing MEG at %d source locations (%s orientations)...",
2739 nsource, fixed_ori ? "fixed" : "free");
2740 /*
2741 * Ready to start the threads & Wait for them to complete
2742 */
2743 QtConcurrent::blockingMap(args, [](FwdThreadArg::UPtr& a) {
2745 });
2746 /*
2747 * Check the results
2748 */
2749 for (k = 0, stat = OK; k < nthread; k++)
2750 if (args[k]->stat != OK) {
2751 stat = FAIL;
2752 break;
2753 }
2754 if (stat != OK)
2755 return cleanup_fail();
2756 } else {
2757 qInfo("Computing MEG at %d source locations (%s orientations, no threads)...",
2758 nsource, fixed_ori ? "fixed" : "free");
2759 for (k = 0, off = 0; k < nspace; k++) {
2760 one_arg->s = spaces[k].get();
2761 one_arg->off = off;
2762 meg_eeg_fwd_one_source_space(one_arg.get());
2763 if (one_arg->stat != OK)
2764 return cleanup_fail();
2765 off = fixed_ori ? off + one_arg->s->nuse : off + 3 * one_arg->s->nuse;
2766 }
2767 }
2768 qInfo("done.");
2769 {
2770 QStringList orig_names;
2771 for (k = 0; k < nmeg; k++)
2772 orig_names.append(coils->coils[k]->chname);
2773 names = orig_names;
2774 }
2775 one_arg.reset();
2776 comp.reset();
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->coil_class)) {
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->coil_class)) {
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->coil_class)) {
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->coil_class == 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->coil_class)) {
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->coil_class == FWD_COILC_EEG) {
3398 for (p = 0; p < 3; p++)
3399 Bval(p, k) = 0.0;
3400 }
3401 }
3402 return OK;
3403}
constexpr auto FIFFV_MNE_COORD_RAS
constexpr auto FIFFV_COORD_HEAD
constexpr auto FIFFV_COORD_MRI_DISPLAY
constexpr auto FIFFV_MOVE
constexpr auto FIFFV_MNE_COORD_CTF_HEAD
constexpr auto FIFFV_MNE_COORD_CTF_DEVICE
constexpr auto FIFFV_COORD_MRI_SLICE
constexpr auto FIFFV_COORD_DEVICE
constexpr auto FIFFV_MNE_COORD_MRI_VOXEL
constexpr auto FIFFV_MNE_COORD_MNI_TAL
constexpr auto FIFFV_COORD_UNKNOWN
constexpr auto FIFFV_COORD_MRI
constexpr auto FIFFV_NO_MOVE
constexpr auto FIFFV_MNE_COORD_FS_TAL_GTZ
constexpr auto FIFFV_COORD_ISOTRAK
constexpr auto FIFFV_COORD_HPI
constexpr auto FIFFV_MNE_COORD_FS_TAL_LTZ
FIFF binary tag-stream layer: wraps a QIODevice to read and write FIFF tags, directories,...
constexpr auto FIFFV_BEM_SURF_ID_HEAD
Definition fiff_file.h:743
constexpr auto FIFFB_BEM
Definition fiff_file.h:394
constexpr auto FIFFV_BEM_SURF_ID_SKULL
Definition fiff_file.h:742
constexpr auto FIFFB_BEM_SURF
Definition fiff_file.h:395
constexpr auto FIFFV_BEM_APPROX_LINEAR
Definition fiff_file.h:770
constexpr auto FIFFV_BEM_APPROX_CONST
Definition fiff_file.h:769
constexpr auto FIFFT_INT
Definition fiff_file.h:222
constexpr auto FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:740
constexpr auto FIFF_BEM_POT_SOLUTION
Definition fiff_file.h:732
constexpr auto FIFF_BEM_COORD_FRAME
Definition fiff_file.h:734
constexpr auto FIFF_BEM_APPROX
Definition fiff_file.h:733
Matrix paired with row and column name lists, the on-disk form of FIFFB_PROJ_ITEM / FIFFB_MNE_NAMED_M...
constexpr double EPS
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).
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
static std::unique_ptr< MNELIB::MNESourceSpace > 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.
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
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)
static int fwd_comp_field_vec(const Eigen::Vector3f &rd, FwdCoilSet &coils, Eigen::Ref< Eigen::MatrixXf > res, void *client)
static int fwd_comp_field(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > res, void *client)
static std::unique_ptr< 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.
This defines a source space.
static std::unique_ptr< 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