v2.0.0
Loading...
Searching...
No Matches
inv_dipole_fit_data.cpp
Go to the documentation of this file.
1//=============================================================================================================
36
37//=============================================================================================================
38// INCLUDES
39//=============================================================================================================
40
41#include <fwd/fwd_types.h>
42
43#include "inv_dipole_fit_data.h"
44#include "inv_guess_data.h"
45#include <mne/mne_meas_data.h>
47#include <mne/mne_proj_item.h>
48#include <mne/mne_cov_matrix.h>
49#include "inv_ecd.h"
50
51#include <fiff/fiff_stream.h>
52#include <fiff/fiff_info.h>
54#include <fwd/fwd_bem_model.h>
55#include <mne/mne_surface.h>
56
57#include <fwd/fwd_comp_data.h>
58
60#include <math/sphere.h>
61
62#include <Eigen/Dense>
63
64#include <QFile>
65#include <QTextStream>
66#include <QCoreApplication>
67#include <QDebug>
68
69#include <cmath>
70
71using namespace Eigen;
72using namespace FIFFLIB;
73using namespace MNELIB;
74using namespace FWDLIB;
75using namespace INVLIB;
76
77// CTF coil type constants
78
79#ifndef FIFFV_COIL_CTF_GRAD
80#define FIFFV_COIL_CTF_GRAD 5001
81#endif
82
83#ifndef FIFFV_COIL_CTF_REF_MAG
84#define FIFFV_COIL_CTF_REF_MAG 5002
85#endif
86
87#ifndef FIFFV_COIL_CTF_REF_GRAD
88#define FIFFV_COIL_CTF_REF_GRAD 5003
89#endif
90
91#ifndef FIFFV_COIL_CTF_OFFDIAG_REF_GRAD
92#define FIFFV_COIL_CTF_OFFDIAG_REF_GRAD 5004
93#endif
94
95constexpr int FAIL = -1;
96constexpr int OK = 0;
97
98//=============================================================================================================
99// DEFINE MEMBER METHODS
100//=============================================================================================================
101
104, nmeg (0)
105, neeg (0)
106, funcs (nullptr)
107, fixed_noise (false)
108, nave (1)
110, fit_mag_dipoles (false)
111, user (nullptr)
112, r0(Eigen::Vector3f::Zero())
113{
114}
115
116//=============================================================================================================
117
119{
120 // unique_ptr members auto-cleanup (including sphere_funcs, bem_funcs, mag_dipole_funcs)
121}
122
123//=============================================================================================================
124
129{
130 FwdCompData* comp;
132 int fit_sphere_to_bem = true;
133
134 if (!d->bemname.isEmpty()) {
135 /*
136 * Set up the boundary-element model
137 */
138 QString bemsolname = FwdBemModel::fwd_bem_make_bem_sol_name(d->bemname);
139 d->bemname = bemsolname;
140
141 qInfo("\nSetting up the BEM model using %s...",d->bemname.toUtf8().constData());
142 qInfo("\nLoading surfaces...");
144 if (d->bem_model) {
145 qInfo("Three-layer model surfaces loaded.");
146 }
147 else {
149 if (!d->bem_model)
150 return FAIL;
151 qInfo("Homogeneous model surface loaded.");
152 }
153 if (d->neeg > 0 && d->bem_model->nsurf == 1) {
154 qCritical("Cannot use a homogeneous model in EEG calculations.");
155 return FAIL;
156 }
157 qInfo("\nLoading the solution matrix...");
158 if (d->bem_model->fwd_bem_load_recompute_solution(d->bemname,FWD_BEM_UNKNOWN,false) == FAIL)
159 return FAIL;
160 qInfo("Employing the head->MRI coordinate transform with the BEM model.");
161 Q_ASSERT(d->mri_head_t);
162 if (d->bem_model->fwd_bem_set_head_mri_t(*d->mri_head_t) == FAIL)
163 return FAIL;
164 qInfo("BEM model %s is now set up",d->bem_model->sol_name.toUtf8().constData());
165 /*
166 * Find the best-fitting sphere
167 */
168 if (fit_sphere_to_bem) {
169 MNESurface* inner_skull;
170 float simplex_size = 2e-2;
171 float R;
172 VectorXf r0_vec;
173
174 if ((inner_skull = d->bem_model->fwd_bem_find_surface(FIFFV_BEM_SURF_ID_BRAIN)) == nullptr)
175 return FAIL;
176
177 if (!UTILSLIB::Sphere::fit_sphere_to_points(inner_skull->rr,simplex_size,r0_vec,R))
178 return FAIL;
179 d->r0 = r0_vec.head<3>();
180
181 FiffCoordTrans::apply_trans(d->r0.data(),*d->mri_head_t,true);
182 qInfo("Fitted sphere model origin : %6.1f %6.1f %6.1f mm rad = %6.1f mm.",
183 1000*d->r0[0],1000*d->r0[1],1000*d->r0[2],1000*R);
184 }
185 d->bem_funcs = std::make_unique<dipoleFitFuncsRec>();
186 f = d->bem_funcs.get();
187 if (d->nmeg > 0) {
188 /*
189 * Use the new compensated field computation
190 * It works the same way independent of whether or not the compensation is in effect
191 */
192 comp = FwdCompData::fwd_make_comp_data(comp_data,d->meg_coils.get(),comp_coils,
193 FwdBemModel::fwd_bem_field,nullptr,nullptr,d->bem_model.get());
194 if (!comp)
195 return FAIL;
196 qInfo("Compensation setup done.");
197
198 qInfo("MEG solution matrix...");
199 if (d->bem_model->fwd_bem_specify_coils(d->meg_coils.get()) == FAIL)
200 return FAIL;
201 if (d->bem_model->fwd_bem_specify_coils(comp->comp_coils) == FAIL)
202 return FAIL;
203 qInfo("[done]");
204
206 f->meg_vec_field = nullptr;
207 f->meg_client = comp;
208 f->meg_client_free = [](void* d) { delete static_cast<FwdCompData*>(d); };
209 }
210 if (d->neeg > 0) {
211 qInfo("\tEEG solution matrix...");
212 if (d->bem_model->fwd_bem_specify_els(d->eeg_els.get()) == FAIL)
213 return FAIL;
214 qInfo("[done]");
216 f->eeg_vec_pot = nullptr;
217 f->eeg_client = d->bem_model.get();
218 }
219 }
220 if (d->neeg > 0 && !d->eeg_model) {
221 qCritical("EEG sphere model not defined.");
222 return FAIL;
223 }
224 d->sphere_funcs = std::make_unique<dipoleFitFuncsRec>();
225 f = d->sphere_funcs.get();
226 if (d->neeg > 0) {
227 d->eeg_model->r0 = d->r0;
230 f->eeg_client = d->eeg_model.get();
231 }
232 if (d->nmeg > 0) {
233 /*
234 * Use the new compensated field computation
235 * It works the same way independent of whether or not the compensation is in effect
236 */
237 comp = FwdCompData::fwd_make_comp_data(comp_data,d->meg_coils.get(),comp_coils,
240 nullptr,
241 d->r0.data());
242 if (!comp)
243 return FAIL;
246 f->meg_client = comp;
247 f->meg_client_free = [](void* d) { delete static_cast<FwdCompData*>(d); };
248 }
249 qInfo("Sphere model origin : %6.1f %6.1f %6.1f mm.",
250 1000*d->r0[0],1000*d->r0[1],1000*d->r0[2]);
251 /*
252 * Finally add the magnetic dipole fitting functions (for special purposes)
253 */
254 d->mag_dipole_funcs = std::make_unique<dipoleFitFuncsRec>();
255 f = d->mag_dipole_funcs.get();
256 if (d->nmeg > 0) {
257 /*
258 * Use the new compensated field computation
259 * It works the same way independent of whether or not the compensation is in effect
260 */
261 comp = FwdCompData::fwd_make_comp_data(comp_data,d->meg_coils.get(),comp_coils,
264 nullptr,
265 nullptr);
266 if (!comp)
267 return FAIL;
270 f->meg_client = comp;
271 f->meg_client_free = [](void* d) { delete static_cast<FwdCompData*>(d); };
272 }
275 /*
276 * Select the appropriate fitting function
277 */
278 d->funcs = !d->bemname.isEmpty() ? d->bem_funcs.get() : d->sphere_funcs.get();
279
280 qWarning("");
281 return OK;
282}
283
284//=============================================================================================================
285
289std::unique_ptr<MNECovMatrix> InvDipoleFitData::ad_hoc_noise(FwdCoilSet *meg, FwdCoilSet *eeg, float grad_std, float mag_std, float eeg_std)
290{
291 int nchan;
292 Eigen::VectorXd stds;
293 QStringList names, ch_names;
294 int k,n;
295
296 qInfo("Using standard noise values "
297 "(MEG grad : %6.1f fT/cm MEG mag : %6.1f fT EEG : %6.1f uV)\n",
298 1e13*grad_std,1e15*mag_std,1e6*eeg_std);
299
300 nchan = 0;
301 if (meg)
302 nchan = nchan + meg->ncoil();
303 if (eeg)
304 nchan = nchan + eeg->ncoil();
305
306 stds.resize(nchan);
307
308 n = 0;
309 if (meg) {
310 for (k = 0; k < meg->ncoil(); k++, n++) {
311 if (meg->coils[k]->is_axial_coil()) {
312 stds[n] = mag_std*mag_std;
313#ifdef TEST_REF
314 if (meg->coils[k]->type == FIFFV_COIL_CTF_REF_MAG ||
315 meg->coils[k]->type == FIFFV_COIL_CTF_REF_GRAD ||
316 meg->coils[k]->type == FIFFV_COIL_CTF_OFFDIAG_REF_GRAD)
317 stds[n] = 1e6*stds[n];
318#endif
319 }
320 else
321 stds[n] = grad_std*grad_std;
322 ch_names.append(meg->coils[k]->chname);
323 }
324 }
325 if (eeg) {
326 for (k = 0; k < eeg->ncoil(); k++, n++) {
327 stds[n] = eeg_std*eeg_std;
328 ch_names.append(eeg->coils[k]->chname);
329 }
330 }
331 names = ch_names;
332 return MNECovMatrix::create(FIFFV_MNE_NOISE_COV,nchan,names,Eigen::VectorXd(),stds);
333}
334
335//=============================================================================================================
336
338{
339 float nave_ratio = static_cast<float>(f->nave) / static_cast<float>(nave);
340 int k;
341
342 if (!f->noise)
343 return OK;
344
345 if (f->noise->cov.size() > 0) {
346 qInfo("Decomposing the sensor noise covariance matrix...");
347 if (f->noise->decompose_eigen() == FAIL)
348 return FAIL;
349
350 for (k = 0; k < f->noise->ncov*(f->noise->ncov+1)/2; k++)
351 f->noise->cov[k] = nave_ratio*f->noise->cov[k];
352 for (k = 0; k < f->noise->ncov; k++) {
353 f->noise->lambda[k] = nave_ratio*f->noise->lambda[k];
354 if (f->noise->lambda[k] < 0.0)
355 f->noise->lambda[k] = 0.0;
356 }
357 if (f->noise->add_inv() == FAIL)
358 return FAIL;
359 }
360 else {
361 for (k = 0; k < f->noise->ncov; k++)
362 f->noise->cov_diag[k] = nave_ratio*f->noise->cov_diag[k];
363 qInfo("Decomposition not needed for a diagonal noise covariance matrix.");
364 if (f->noise->add_inv() == FAIL)
365 return FAIL;
366 }
367 qInfo("Effective nave is now %d",nave);
368 f->nave = nave;
369 return OK;
370}
371
372//=============================================================================================================
373
375{
376 float nave_ratio = static_cast<float>(f->nave) / static_cast<float>(nave);
377 int k;
378
379 if (!f->noise)
380 return OK;
381 if (f->fixed_noise)
382 return OK;
383
384 if (f->noise->cov.size() > 0) {
385 /*
386 * Do the decomposition and check that the matrix is positive definite
387 */
388 qInfo("Decomposing the noise covariance...");
389 if (f->noise->cov.size() > 0) {
390 if (f->noise->decompose_eigen() == FAIL)
391 return FAIL;
392 for (k = 0; k < f->noise->ncov; k++) {
393 if (f->noise->lambda[k] < 0.0)
394 f->noise->lambda[k] = 0.0;
395 }
396 }
397 for (k = 0; k < f->noise->ncov*(f->noise->ncov+1)/2; k++)
398 f->noise->cov[k] = nave_ratio*f->noise->cov[k];
399 for (k = 0; k < f->noise->ncov; k++) {
400 f->noise->lambda[k] = nave_ratio*f->noise->lambda[k];
401 if (f->noise->lambda[k] < 0.0)
402 f->noise->lambda[k] = 0.0;
403 }
404 if (f->noise->add_inv() == FAIL)
405 return FAIL;
406 }
407 else {
408 for (k = 0; k < f->noise->ncov; k++)
409 f->noise->cov_diag[k] = nave_ratio*f->noise->cov_diag[k];
410 qInfo("Decomposition not needed for a diagonal noise covariance matrix.");
411 if (f->noise->add_inv() == FAIL)
412 return FAIL;
413 }
414 qInfo("Effective nave is now %d",nave);
415 f->nave = nave;
416 return OK;
417}
418
419//=============================================================================================================
420
431 MNEMeasData* meas,
432 int nave_in,
433 const int* sels)
434{
435 int nave, j, k;
436 float nonsel_w = 30;
437 int min_nchan = 20;
438
439 if (!f || !f->noise_orig)
440 return OK;
441 if (!meas)
442 nave = 1;
443 else {
444 if (nave_in < 0)
445 nave = meas->current->nave;
446 else
447 nave = nave_in;
448 }
449 /*
450 * Channel selection
451 */
452 if (meas && sels) {
453 std::vector<float> wVec(f->noise_orig->ncov);
454 float *w = wVec.data();
455 int nomit_meg, nomit_eeg, nmeg, neeg;
456
457 nmeg = neeg = 0;
458 nomit_meg = nomit_eeg = 0;
459 for (k = 0; k < f->noise_orig->ncov; k++) {
460 if (f->noise_orig->ch_class[k] == MNE_COV_CH_EEG)
461 neeg++;
462 else
463 nmeg++;
464 /* Check whether this channel is selected in the measurement */
465 bool selected = false;
466 for (int c = 0; c < meas->nchan; c++) {
467 if (QString::compare(f->noise_orig->names[k],
468 meas->chs[c].ch_name,
469 Qt::CaseInsensitive) == 0) {
470 selected = sels[c] != 0;
471 break;
472 }
473 }
474 if (selected)
475 w[k] = 1.0;
476 else {
477 w[k] = nonsel_w;
478 if (f->noise_orig->ch_class[k] == MNE_COV_CH_EEG)
479 nomit_eeg++;
480 else
481 nomit_meg++;
482 }
483 }
484 f->noise.reset();
485 if (nmeg > 0 && nmeg - nomit_meg > 0 && nmeg - nomit_meg < min_nchan) {
486 qCritical("Too few MEG channels remaining");
487 return FAIL;
488 }
489 if (neeg > 0 && neeg - nomit_eeg > 0 && neeg - nomit_eeg < min_nchan) {
490 qCritical("Too few EEG channels remaining");
491 return FAIL;
492 }
493 f->noise = f->noise_orig->dup();
494 if (nomit_meg + nomit_eeg > 0) {
495 if (f->noise->cov.size() > 0) {
496 for (j = 0; j < f->noise->ncov; j++)
497 for (k = 0; k <= j; k++) {
498 f->noise->cov[MNECovMatrix::lt_packed_index(j, k)] *= w[j] * w[k];
499 }
500 }
501 else {
502 for (j = 0; j < f->noise->ncov; j++) {
503 f->noise->cov_diag[j] *= w[j] * w[j];
504 }
505 }
506 }
507 }
508 else {
509 if (f->noise && f->nave == nave)
510 return OK;
511 f->noise = f->noise_orig->dup();
512 }
513
515}
516
517//=============================================================================================================
518
523 const QString &measname,
524 const QString& bemname,
525 Vector3f *r0,
527 int accurate_coils,
528 const QString &badname,
529 const QString &noisename,
530 float grad_std,
531 float mag_std,
532 float eeg_std,
533 float mag_reg,
534 float grad_reg,
535 float eeg_reg,
536 int diagnoise,
537 const QList<QString> &projnames,
538 int include_meg,
539 int include_eeg)
540{
541 auto res = std::make_unique<InvDipoleFitData>();
542 int k;
543 QStringList badlist;
544 int nbad = 0;
545 QStringList file_bads;
546 int file_nbad;
548 std::unique_ptr<MNECovMatrix> cov;
549 std::unique_ptr<FwdCoilSet> templates;
550 std::unique_ptr<MNECTFCompDataSet> comp_data;
551 std::unique_ptr<FwdCoilSet> comp_coils;
552
553 /*
554 * Read the coordinate transformations
555 */
556 if (!mriname.isEmpty()) {
557 res->mri_head_t = std::make_unique<FiffCoordTrans>(FiffCoordTrans::readMriTransform(mriname));
558 if (res->mri_head_t->isEmpty())
559 return nullptr;
560 }
561 else if (!bemname.isEmpty()) {
562 qWarning("Source of MRI / head transform required for the BEM model is missing");
563 return nullptr;
564 }
565 else {
566 float move[] = { 0.0, 0.0, 0.0 };
567 float rot[3][3] = { { 1.0, 0.0, 0.0 },
568 { 0.0, 1.0, 0.0 },
569 { 0.0, 0.0, 1.0 } };
570 Eigen::Matrix3f rotMat;
571 rotMat << rot[0][0], rot[0][1], rot[0][2],
572 rot[1][0], rot[1][1], rot[1][2],
573 rot[2][0], rot[2][1], rot[2][2];
574 Eigen::Vector3f moveVec = Eigen::Map<Eigen::Vector3f>(move);
575 res->mri_head_t = std::make_unique<FiffCoordTrans>(FIFFV_COORD_MRI,FIFFV_COORD_HEAD,rotMat,moveVec);
576 }
577
578 res->mri_head_t->print();
579 res->meg_head_t = std::make_unique<FiffCoordTrans>(FiffCoordTrans::readMeasTransform(measname));
580 if (res->meg_head_t->isEmpty())
581 return nullptr;
582 res->meg_head_t->print();
583 /*
584 * Read the bad channel lists
585 */
586 if (!badname.isEmpty()) {
587 if (!FiffInfoBase::readBadChannelsFromFile(badname, badlist))
588 return nullptr;
589 nbad = badlist.size();
590 qInfo("%d bad channels read from %s.", nbad, badname.toUtf8().data());
591 }
592 {
593 QFile measFile(measname);
594 FiffStream::SPtr measStream(new FiffStream(&measFile));
595 if (measStream->open()) {
596 file_bads = measStream->read_bad_channels(measStream->dirtree());
597 file_nbad = file_bads.size();
598 measStream->close();
599 }
600 }
601 if (file_nbad > 0) {
602 if (badlist.isEmpty())
603 nbad = 0;
604 for (k = 0; k < file_nbad; k++) {
605 badlist.append(file_bads[k]);
606 nbad++;
607 }
608 file_bads.clear();
609 qInfo("%d bad channels read from the data file.",file_nbad);
610 }
611 qInfo("%d bad channels total.",nbad);
612 /*
613 * Read the channel information
614 */
615 if (!FiffInfo::readMegEegChannels(measname,
616 include_meg,
617 include_eeg,
618 badlist,
619 res->chs,
620 res->nmeg,
621 res->neeg))
622 return nullptr;
623
624 if (res->nmeg > 0)
625 qInfo("Will use %3d MEG channels from %s",res->nmeg,measname.toUtf8().data());
626 if (res->neeg > 0)
627 qInfo("Will use %3d EEG channels from %s",res->neeg,measname.toUtf8().data());
628 {
629 int nch_total = res->nmeg + res->neeg;
630 res->ch_names.clear();
631 for (int i = 0; i < nch_total; i++)
632 res->ch_names.append(res->chs[i].ch_name);
633 }
634 /*
635 * Make coil definitions
636 */
637 res->coord_frame = coord_frame;
639 //#ifdef USE_SHARE_PATH
640 // char *coilfile = mne_compose_mne_name("share/mne","coil_def.dat");
641 //#else
642 // const char *path = "setup/mne";
643 // const char *filename = "coil_def.dat";
644 // const char *coilfile = mne_compose_mne_name(path,filename);
645
646 // QString qPath("/usr/pubsw/packages/mne/stable/share/mne/coil_def.dat");
647
648 QString qPath = QString(QCoreApplication::applicationDirPath() + "/../resources/general/coilDefinitions/coil_def.dat");
649 QFile file(qPath);
650 if ( !QCoreApplication::startingUp() )
651 qPath = QCoreApplication::applicationDirPath() + QString("/../resources/general/coilDefinitions/coil_def.dat");
652 else if (!file.exists())
653 qPath = "../resources/general/coilDefinitions/coil_def.dat";
654
655 QByteArray coilfileBytes = qPath.toUtf8();
656 const char *coilfile = coilfileBytes.constData();
657 //#endif
658
659 if (!coilfile)
660 return nullptr;
661 templates = FwdCoilSet::read_coil_defs(coilfile);
662 if (!templates) {
663 return nullptr;
664 }
665
666 Q_ASSERT(res->meg_head_t);
667 res->meg_coils = templates->create_meg_coils(res->chs,
668 res->nmeg,
670 *res->meg_head_t);
671 if (!res->meg_coils)
672 return nullptr;
673 res->eeg_els = FwdCoilSet::create_eeg_els(res->chs.mid(res->nmeg),
674 res->neeg);
675 if (!res->eeg_els)
676 return nullptr;
677 qInfo("Head coordinate coil definitions created.");
678 }
679 else {
680 qWarning("Cannot handle computations in %s coordinates",FiffCoordTrans::frame_name(coord_frame).toUtf8().constData());
681 return nullptr;
682 }
683 /*
684 * Forward model setup
685 */
686 res->bemname = bemname;
687 if (r0) {
688 res->r0 = *r0;
689 }
690 res->eeg_model.reset(eeg_model);
691 /*
692 * Compensation data
693 */
694 comp_data = MNECTFCompDataSet::read(measname);
695 if (!comp_data)
696 return nullptr;
697 if (comp_data->ncomp > 0) { /* Compensation channel information may be needed */
698 QList<FiffChInfo> comp_chs;
699 int ncomp = 0;
700
701 qInfo("%d compensation data sets in %s",comp_data->ncomp,measname.toUtf8().data());
702 {
703 QFile compFile(measname);
704 FiffStream::SPtr compStream(new FiffStream(&compFile));
705 if (!compStream->open())
706 return nullptr;
707 FiffInfo compInfo;
708 FiffDirNode::SPtr compInfoNode;
709 if (!compStream->read_meas_info(compStream->dirtree(), compInfo, compInfoNode)) {
710 compStream->close();
711 return nullptr;
712 }
713 compStream->close();
714 for (int k = 0; k < compInfo.chs.size(); k++) {
715 if (compInfo.chs[k].kind == FIFFV_REF_MEG_CH) {
716 comp_chs.append(compInfo.chs[k]);
717 ncomp++;
718 }
719 }
720 }
721 if (ncomp > 0) {
722 comp_coils = templates->create_meg_coils(comp_chs,
723 ncomp,
725 *res->meg_head_t);
726 if (!comp_coils) {
727 return nullptr;
728 }
729 qInfo("%d compensation channels in %s",comp_coils->ncoil(),measname.toUtf8().data());
730 }
731 }
732 else { /* Get rid of the empty data set */
733 comp_data.reset();
734 }
735 /*
736 * Ready to set up the forward model
737 */
738 if (setup_forward_model(res.get(),comp_data.get(),comp_coils.get()) == FAIL)
739 return nullptr;
740 res->column_norm = COLUMN_NORM_LOC;
741 /*
742 * Projection data should go here
743 */
744 if (!MNEProjOp::makeProjection(projnames,
745 res->chs,
746 res->nmeg+res->neeg,
747 res->proj))
748 return nullptr;
749 if (res->proj && res->proj->nitems > 0) {
750 qInfo("Final projection operator is:");
751 { QTextStream errStream(stderr); res->proj->report(errStream, QStringLiteral("\t")); }
752
753 if (res->proj->assign_channels(res->ch_names,res->nmeg+res->neeg) == FAIL)
754 return nullptr;
755 if (res->proj->make_proj() == FAIL)
756 return nullptr;
757 }
758 else
759 qInfo("No projection will be applied to the data.");
760
761 /*
762 * Noise covariance
763 */
764 if (!noisename.isEmpty()) {
765 if ((cov = MNECovMatrix::read(noisename,FIFFV_MNE_SENSOR_COV)) == nullptr)
766 return nullptr;
767 qInfo("Read a %s noise-covariance matrix from %s",
768 cov->cov_diag.size() > 0 ? "diagonal" : "full", noisename.toUtf8().data());
769 }
770 else {
771 if ((cov = ad_hoc_noise(res->meg_coils.get(),res->eeg_els.get(),grad_std,mag_std,eeg_std)) == nullptr)
772 return nullptr;
773 }
774 res->noise = cov->pick_chs_omit(res->ch_names,
775 res->nmeg+res->neeg,
776 true,
777 res->chs);
778 if (!res->noise) {
779 return nullptr;
780 }
781
782 qInfo("Picked appropriate channels from the noise-covariance matrix.");
783 cov.reset();
784
785 /*
786 * Apply the projection operator to the noise-covariance matrix
787 */
788 if (res->proj && res->proj->nitems > 0 && res->proj->nvec > 0) {
789 if (res->proj->apply_cov(res->noise.get()) == FAIL)
790 return nullptr;
791 qInfo("Projection applied to the covariance matrix.");
792 }
793
794 /*
795 * Force diagonal noise covariance?
796 */
797 if (diagnoise) {
798 res->noise->revert_to_diag();
799 qInfo("Using only the main diagonal of the noise-covariance matrix.");
800 }
801
802 /*
803 * Regularize the possibly deficient noise-covariance matrix
804 */
805 if (res->noise->cov.size() > 0) {
806 Eigen::Vector3f regs;
807 int do_it;
808
809 regs[MNE_COV_CH_MEG_MAG] = mag_reg;
810 regs[MNE_COV_CH_MEG_GRAD] = grad_reg;
811 regs[MNE_COV_CH_EEG] = eeg_reg;
812 /*
813 * Classify the channels
814 */
815 if (res->noise->classify_channels(res->chs,
816 res->nmeg+res->neeg) == FAIL)
817 return nullptr;
818 /*
819 * Do we need to do anything?
820 */
821 for (k = 0, do_it = 0; k < res->noise->ncov; k++) {
822 if (res->noise->ch_class[k] != MNE_COV_CH_UNKNOWN &&
823 regs[res->noise->ch_class[k]] > 0.0)
824 do_it++;
825 }
826 /*
827 * Apply regularization if necessary
828 */
829 if (do_it > 0)
830 res->noise->regularize(regs);
831 else
832 qInfo("No regularization applied to the noise-covariance matrix");
833 }
834
835 /*
836 * Do the decomposition and check that the matrix is positive definite
837 */
838 qInfo("Decomposing the noise covariance...");
839 if (res->noise->cov.size() > 0) {
840 if (res->noise->decompose_eigen() == FAIL)
841 return nullptr;
842 qInfo("Eigenvalue decomposition done.");
843 for (k = 0; k < res->noise->ncov; k++) {
844 if (res->noise->lambda[k] < 0.0)
845 res->noise->lambda[k] = 0.0;
846 }
847 }
848 else {
849 qInfo("Decomposition not needed for a diagonal covariance matrix.");
850 if (res->noise->add_inv() == FAIL)
851 return nullptr;
852 }
853
854 badlist.clear();
855 return res.release();
856}
857
858//=============================================================================================================
859
860// Dipole forward computation
861
862void print_fields(const Eigen::Vector3f& rd,
863 const Eigen::Vector3f& Q,
864 float time,
865 float integ,
866 InvDipoleFitData* fit,
867 MNEMeasData* data)
868
869{
870 Eigen::VectorXf oneVec(data->nchan);
871 int k;
872 int nch = fit->nmeg + fit->neeg;
873
874 if (data->current->getValuesAtTime(time, integ, data->nchan, false, oneVec.data()) == FAIL) {
875 qWarning("Cannot pick time: %7.1f ms",1000*time);
876 return;
877 }
878 for (k = 0; k < data->nchan; k++)
879 if (data->chs[k].chpos.coil_type == FIFFV_COIL_CTF_REF_GRAD ||
880 data->chs[k].chpos.coil_type == FIFFV_COIL_CTF_OFFDIAG_REF_GRAD) {
881 qInfo("%g ",1e15*oneVec[k]);
882 }
883 qInfo("");
884
885 Eigen::MatrixXf fwd = Eigen::MatrixXf::Zero(nch, 3);
886 if (InvDipoleFitData::compute_dipole_field(*fit,rd,false,fwd) == FAIL)
887 return;
888
889 for (k = 0; k < data->nchan; k++)
890 if (data->chs[k].chpos.coil_type == FIFFV_COIL_CTF_REF_GRAD ||
891 data->chs[k].chpos.coil_type == FIFFV_COIL_CTF_OFFDIAG_REF_GRAD) {
892 qInfo("%g ",1e15*(Q[0]*fwd(k,0)+Q[1]*fwd(k,1)+Q[2]*fwd(k,2)));
893 }
894 qInfo("");
895
896 return;
897}
898
899//=============================================================================================================
900
905 float **rd,
906 int ndip,
907 InvDipoleForward* old)
908{
909 InvDipoleForward* res;
910 float S[3];
911 int k,p;
912 /*
913 * Allocate data if necessary
914 */
915 if (old && old->ndip == ndip && old->nch == d->nmeg+d->neeg) {
916 res = old;
917 }
918 else {
919 delete old; old = nullptr;
920 res = new InvDipoleForward;
921 int nch = d->nmeg + d->neeg;
922 int m = 3 * ndip;
923 res->fwd.resize(m, nch);
924 res->uu.resize(m, nch);
925 res->vv.resize(m, m);
926 res->sing.resize(m);
927 res->scales.resize(m);
928 res->rd.resize(ndip, 3);
929 res->nch = nch;
930 res->ndip = ndip;
931 }
932
933 for (k = 0; k < ndip; k++) {
934 res->rd.row(k) = Eigen::Map<const Eigen::Vector3f>(rd[k]).transpose();
935 /*
936 * Calculate the field of three orthogonal dipoles
937 */
938 Eigen::MatrixXf this_fwd(d->nmeg + d->neeg, 3);
939 Eigen::Map<const Eigen::Vector3f> rd_k(rd[k]);
940 if ((InvDipoleFitData::compute_dipole_field(*d,rd_k,true,this_fwd)) == FAIL) {
941 if (!old)
942 delete res;
943 return nullptr;
944 }
945 for (int p = 0; p < 3; p++)
946 res->fwd.row(3*k+p) = this_fwd.col(p).transpose();
947 /*
948 * Choice of column normalization
949 * (componentwise normalization is not recommended)
950 */
952 for (p = 0; p < 3; p++)
953 S[p] = res->fwd.row(3*k+p).squaredNorm();
954 if (d->column_norm == COLUMN_NORM_COMP) {
955 for (p = 0; p < 3; p++)
956 res->scales[3*k+p] = sqrt(S[p]);
957 }
958 else {
959 /*
960 * Divide by three or not?
961 */
962 res->scales[3*k+0] = res->scales[3*k+1] = res->scales[3*k+2] = sqrt(S[0]+S[1]+S[2])/3.0;
963 }
964 for (p = 0; p < 3; p++) {
965 if (res->scales[3*k+p] > 0.0) {
966 res->scales[3*k+p] = 1.0/res->scales[3*k+p];
967 res->fwd.row(3*k+p) *= res->scales[3*k+p];
968 }
969 else
970 res->scales[3*k+p] = 1.0;
971 }
972 }
973 else {
974 res->scales[3*k] = 1.0;
975 res->scales[3*k+1] = 1.0;
976 res->scales[3*k+2] = 1.0;
977 }
978 }
979
980 /*
981 * SVD: A = U · Σ · V^T where A is m×n (3*ndip × nch)
982 * uu stores right singular vectors (V^T rows, length nch) for data-space projections
983 * vv stores left singular vectors (U^T rows, length m) for dipole-moment reconstruction
984 */
985 {
986 int m = 3*ndip;
987 int n = d->nmeg+d->neeg;
988 int udim = std::min(m,n);
989 JacobiSVD<MatrixXf> svd(res->fwd, ComputeFullU | ComputeFullV);
990 res->sing = svd.singularValues();
991 res->uu = svd.matrixV().transpose().topRows(udim);
992 res->vv = svd.matrixU().transpose().topRows(udim);
993 }
994
995 return res;
996}
997
998//=============================================================================================================
999
1004 const Eigen::Vector3f& rd,
1005 InvDipoleForward* old)
1006{
1007 float *rds[1];
1008 rds[0] = const_cast<float*>(rd.data());
1009 return dipole_forward(d,rds,1,old);
1010}
1011
1012//=============================================================================================================
1013// Dipole fitting - evaluation
1017static float fit_eval(const VectorXf& rd, const void *user)
1018{
1019 InvDipoleFitData* fit = const_cast<InvDipoleFitData*>(static_cast<const InvDipoleFitData*>(user));
1020 InvDipoleForward* fwd;
1021 FitDipUserRec* fuser = fit->user;
1022 double Bm2,one;
1023 int ncomp,c;
1024
1025 fwd = fuser->fwd = InvDipoleFitData::dipole_forward_one(fit,rd.head<3>(),fuser->fwd);
1026 ncomp = fwd->sing[2]/fwd->sing[0] > fuser->limit ? 3 : 2;
1027 if (fuser->report_dim)
1028 qInfo("ncomp = %d",ncomp);
1029
1030 Eigen::Map<const VectorXf> Bmap(fuser->B, fwd->nch);
1031 for (c = 0, Bm2 = 0.0; c < ncomp; c++) {
1032 one = fwd->uu.row(c).dot(Bmap);
1033 Bm2 = Bm2 + one*one;
1034 }
1035 return fuser->B2-Bm2;
1036}
1037
1041static int find_best_guess(const Eigen::Ref<const Eigen::VectorXf>& B,
1042 int nch,
1043 InvGuessData* guess,
1044 float limit,
1045 int &bestp,
1046 float &goodp)
1047{
1048 int k,c;
1049 double B2,Bm2,this_good,one;
1050 int best = -1;
1051 float good = 0.0;
1052 InvDipoleForward* fwd;
1053 int ncomp;
1054
1055 B2 = B.squaredNorm();
1056 for (k = 0; k < guess->nguess; k++) {
1057 fwd = guess->guess_fwd[k].get();
1058 if (fwd->nch == nch) {
1059 ncomp = fwd->sing[2]/fwd->sing[0] > limit ? 3 : 2;
1060 for (c = 0, Bm2 = 0.0; c < ncomp; c++) {
1061 one = fwd->uu.row(c).dot(B);
1062 Bm2 = Bm2 + one*one;
1063 }
1064 this_good = 1.0 - (B2 - Bm2)/B2;
1065 if (this_good > good) {
1066 best = k;
1067 good = this_good;
1068 }
1069 }
1070 }
1071 if (best < 0) {
1072 qWarning("No reasonable initial guess found.");
1073 return FAIL;
1074 }
1075 bestp = best;
1076 goodp = good;
1077 return OK;
1078}
1079
1083static MatrixXf make_initial_dipole_simplex(const Eigen::Vector3f& r0,
1084 float size)
1085{
1086 /*
1087 * For this definition of a regular tetrahedron, see
1088 *
1089 * http://mathworld.wolfram.com/Tetrahedron.html
1090 *
1091 */
1092 float x = sqrt(3.0f)/3.0f;
1093 float r = sqrt(6.0f)/12.0f;
1094 float R = 3*r;
1095 float d = x/2.0f;
1096 float rr[][3] = { { x , 0.0f, -r },
1097 { -d, 0.5f, -r },
1098 { -d, -0.5f, -r },
1099 { 0.0f, 0.0f, R } };
1100
1101 MatrixXf simplex = MatrixXf::Zero(4, 3);
1102
1103 for (int j = 0; j < 4; j++) {
1104 simplex.row(j) = Eigen::Map<const Vector3f>(rr[j]).transpose() * size + r0.transpose();
1105 }
1106 return simplex;
1107}
1108
1109static bool dipole_report_func(int loop,
1110 const VectorXf& fitpar,
1111 double fval_lo,
1112 double fval_hi,
1113 double par_diff)
1114{
1115 qInfo("loop %d rd %7.2f %7.2f %7.2f fval %g %g par diff %g",
1116 loop,1000*fitpar[0],1000*fitpar[1],1000*fitpar[2],fval_lo,fval_hi,1000*par_diff);
1117
1118 return true;
1119}
1120
1124static int fit_Q(InvDipoleFitData* fit,
1125 const Eigen::Ref<const Eigen::VectorXf>& B,
1126 const Eigen::Vector3f& rd,
1127 float limit,
1128 Eigen::Vector3f& Q,
1129 int &ncomp,
1130 float &res)
1131{
1132 int c;
1134 float Bm2,one;
1135
1136 if (!fwd)
1137 return FAIL;
1138
1139 ncomp = fwd->sing[2]/fwd->sing[0] > limit ? 3 : 2;
1140
1141 Q.setZero();
1142 for (c = 0, Bm2 = 0.0; c < ncomp; c++) {
1143 one = fwd->uu.row(c).dot(B);
1144 Q += (one/fwd->sing[c]) * fwd->vv.row(c).head(3).transpose();
1145 Bm2 = Bm2 + one*one;
1146 }
1147 /*
1148 * Counteract the effect of column normalization
1149 */
1150 for (c = 0; c < 3; c++)
1151 Q[c] = fwd->scales[c]*Q[c];
1152 res = B.squaredNorm() - Bm2;
1153
1154 delete fwd;
1155
1156 return OK;
1157}
1158
1159//=============================================================================================================
1160// Dipole fitting - main entry point
1173 InvGuessData* guess,
1174 float time,
1175 Eigen::Ref<Eigen::VectorXf> B,
1176 int verbose,
1177 InvEcd& res
1178 )
1179{
1180 VectorXf vals(4); /* Values at the vertices */
1181 float limit = 0.2f; /* (pseudo) radial component omission limit */
1182 float size = 1e-2f; /* Size of the initial simplex */
1183 float ftol[] = { 1e-2f, 1e-2f }; /* Tolerances on the the two passes */
1184 float atol[] = { 0.2e-3f, 0.2e-3f }; /* If dipole movement between two iterations is less than this,
1185 we consider to have converged */
1186 int ntol = 2;
1187 int max_eval = 1000; /* Limit for fit function evaluations */
1188 int report_interval = verbose ? 1 : -1; /* How often to report the intermediate result */
1189
1190 int best;
1191 float good,final_val;
1192 Eigen::Vector3f rd_final, Q;
1194 int k,neval,neval_tot,nchan,ncomp;
1195 int fit_fail;
1196 Vector3f rd_guess;
1197
1198 nchan = fit->nmeg+fit->neeg;
1199 user.fwd = nullptr;
1200
1201 if (fit->proj && fit->proj->project_vector(B,true) == FAIL)
1202 return false;
1203
1204 if (fit->noise->whiten_vector(B,B,nchan) == FAIL)
1205 return false;
1206 /*
1207 * Get the initial guess
1208 */
1209 if (find_best_guess(B,nchan,guess,limit,best,good) < 0)
1210 return false;
1211
1212 user.limit = limit;
1213 user.B = B.data();
1214 user.B2 = B.squaredNorm();
1215 user.fwd = nullptr;
1216 user.report_dim = false;
1217 fit->user = &user;
1218
1219 rd_guess = guess->rr.row(best).transpose();
1220 rd_final = rd_guess;
1221
1222 neval_tot = 0;
1223 fit_fail = false;
1224 for (k = 0; k < ntol; k++) {
1225 /*
1226 * Do first pass with the sphere model
1227 */
1228 if (k == 0)
1229 fit->funcs = fit->sphere_funcs.get();
1230 else
1231 fit->funcs = !fit->bemname.isEmpty() ? fit->bem_funcs.get() : fit->sphere_funcs.get();
1232
1233 MatrixXf simplexMat = make_initial_dipole_simplex(rd_guess,size);
1234 for (int p = 0; p < 4; p++)
1235 vals[p] = fit_eval(simplexMat.row(p),fit);
1236
1237 // Capture fit pointer in type-safe lambda — no void* needed
1238 auto cost = [fit](const VectorXf& x) -> float { return fit_eval(x, fit); };
1239
1241 simplexMat, /* The initial simplex */
1242 vals, /* Function values at the vertices */
1243 ftol[k], /* Relative convergence tolerance for the target function */
1244 atol[k], /* Absolute tolerance for the change in the parameters */
1245 cost, /* The cost function (captures fit data) */
1246 max_eval, /* Maximum number of function evaluations */
1247 neval, /* Number of function evaluations */
1248 report_interval, /* How often to report (-1 = no_reporting) */
1249 dipole_report_func)) {
1250 if (k == 0) {
1251 delete user.fwd;
1252 return false;
1253 }
1254 else {
1255 float rv = 2.0f*(vals.maxCoeff()-vals.minCoeff())/(vals.maxCoeff()+vals.minCoeff());
1256 qWarning("Warning (t = %8.1f ms) : g = %6.1f %% final val = %7.3f rtol = %f",
1257 1000*time,100*(1 - vals[0]/user.B2),vals[0],rv);
1258 fit_fail = true;
1259 }
1260 }
1261 rd_final = simplexMat.row(0).transpose();
1262 rd_guess = simplexMat.row(0).transpose();
1263
1264 neval_tot += neval;
1265 final_val = vals[0];
1266 }
1267 /*
1268 * Confidence limits should be computed here
1269 */
1270 /*
1271 * Compute the dipole moment at the final point
1272 */
1273 if (fit_Q(fit,B,rd_final,user.limit,Q,ncomp,final_val) == OK) {
1274 res.time = time;
1275 res.valid = true;
1276 res.rd = rd_final;
1277 res.Q = Q;
1278 res.good = 1.0 - final_val/user.B2;
1279 if (fit_fail)
1280 res.good = -res.good;
1281 res.khi2 = final_val;
1282 if (fit->proj)
1283 res.nfree = nchan-3-ncomp-fit->proj->nvec;
1284 else
1285 res.nfree = nchan-3-ncomp;
1286 res.neval = neval_tot;
1287 }
1288 else {
1289 delete user.fwd;
1290 return false;
1291 }
1292 delete user.fwd;
1293
1294 return true;
1295}
1296
1297//=============================================================================================================
1298
1304int InvDipoleFitData::compute_dipole_field(InvDipoleFitData& d, const Eigen::Vector3f& rd, int whiten, Eigen::Ref<Eigen::MatrixXf> fwd)
1305{
1306 static const Eigen::Vector3f Qx(1.0f, 0.0f, 0.0f);
1307 static const Eigen::Vector3f Qy(0.0f, 1.0f, 0.0f);
1308 static const Eigen::Vector3f Qz(0.0f, 0.0f, 1.0f);
1309 int nch = d.nmeg + d.neeg;
1310 int k;
1311 /*
1312 * Compute the fields
1313 */
1314 if (d.nmeg > 0) {
1315 int nmeg = d.meg_coils->ncoil();
1316 if (d.funcs->meg_vec_field) {
1317 /*
1318 * Use the vector field function: computes all three dipole
1319 * orientations at once. Output is 3 x ncoil, we need nch x 3.
1320 */
1321 Eigen::MatrixXf vec_meg(3, nmeg);
1322 if (d.funcs->meg_vec_field(rd,*d.meg_coils,vec_meg,d.funcs->meg_client) != OK)
1323 return FAIL;
1324 fwd.topRows(nmeg) = vec_meg.transpose();
1325 } else {
1326 auto fwd0 = fwd.col(0).head(nmeg);
1327 auto fwd1 = fwd.col(1).head(nmeg);
1328 auto fwd2 = fwd.col(2).head(nmeg);
1329 if (d.funcs->meg_field(rd,Qx,*d.meg_coils,fwd0,d.funcs->meg_client) != OK)
1330 return FAIL;
1331 if (d.funcs->meg_field(rd,Qy,*d.meg_coils,fwd1,d.funcs->meg_client) != OK)
1332 return FAIL;
1333 if (d.funcs->meg_field(rd,Qz,*d.meg_coils,fwd2,d.funcs->meg_client) != OK)
1334 return FAIL;
1335 }
1336 }
1337
1338 if (d.neeg > 0) {
1339 int neeg = d.eeg_els->ncoil();
1340 if (d.funcs->eeg_vec_pot) {
1341 /*
1342 * Use the vector potential function: computes all three dipole
1343 * orientations at once. Output is 3 x ncoil, we need nch x 3.
1344 */
1345 Eigen::MatrixXf vec_eeg(3, neeg);
1346 if (d.funcs->eeg_vec_pot(rd,*d.eeg_els,vec_eeg,d.funcs->eeg_client) != OK)
1347 return FAIL;
1348 fwd.block(d.nmeg, 0, neeg, 3) = vec_eeg.transpose();
1349 } else {
1350 auto fwd0 = fwd.col(0).segment(d.nmeg, neeg);
1351 auto fwd1 = fwd.col(1).segment(d.nmeg, neeg);
1352 auto fwd2 = fwd.col(2).segment(d.nmeg, neeg);
1353 if (d.funcs->eeg_pot(rd,Qx,*d.eeg_els,fwd0,d.funcs->eeg_client) != OK)
1354 return FAIL;
1355 if (d.funcs->eeg_pot(rd,Qy,*d.eeg_els,fwd1,d.funcs->eeg_client) != OK)
1356 return FAIL;
1357 if (d.funcs->eeg_pot(rd,Qz,*d.eeg_els,fwd2,d.funcs->eeg_client) != OK)
1358 return FAIL;
1359 }
1360 }
1361
1362 /*
1363 * Apply projection
1364 */
1365 for (k = 0; k < 3; k++)
1366 if (d.proj && d.proj->project_vector(fwd.col(k),true) == FAIL)
1367 return FAIL;
1368
1369 /*
1370 * Whiten
1371 */
1372 if (d.noise && whiten) {
1373 for (k = 0; k < 3; k++) {
1374 auto col_k = fwd.col(k);
1375 if (d.noise->whiten_vector(col_k,col_k,nch) == FAIL)
1376 return FAIL;
1377 }
1378 }
1379
1380 return OK;
1381}
Sphere class declaration.
SimplexAlgorithm Template Implementation.
#define FIFFV_COIL_CTF_OFFDIAG_REF_GRAD
InvDipoleForward * dipole_forward(InvDipoleFitData *d, float **rd, int ndip, InvDipoleForward *old)
Compute the forward solution for one or more dipoles, applying projections and whitening.
void print_fields(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, float time, float integ, InvDipoleFitData *fit, MNEMeasData *data)
Electric Current Dipole (InvEcd) class declaration.
Dipole Fit Data class declaration.
constexpr int COLUMN_NORM_NONE
constexpr int COLUMN_NORM_COMP
constexpr int COLUMN_NORM_LOC
InvGuessData class declaration.
FwdBemModel class declaration.
FwdCompData class declaration.
Forward library type definitions.
constexpr int FAIL
constexpr int OK
FiffInfo class declaration.
FiffCoordTrans class declaration.
#define FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:749
FiffStream class declaration.
#define FIFFV_MNE_SENSOR_COV
#define FIFFV_COIL_CTF_REF_GRAD
#define FIFFV_MNE_NOISE_COV
#define FIFFV_REF_MEG_CH
#define FIFFV_COIL_CTF_REF_MAG
#define FIFFV_COORD_HEAD
#define FIFFV_COORD_MRI
#define FIFFV_COORD_UNKNOWN
MNECovMatrix class declaration.
#define MNE_COV_CH_MEG_MAG
#define MNE_COV_CH_EEG
#define MNE_COV_CH_MEG_GRAD
#define MNE_COV_CH_UNKNOWN
MNEProjItem class declaration.
MNEMeasData class declaration.
MNEMeasDataSet class declaration.
MNESurface class declaration.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
Forward modelling (BEM, MEG/EEG lead fields).
Definition compute_fwd.h:91
constexpr int FWD_COIL_ACCURACY_NORMAL
Definition fwd_coil.h:81
constexpr int FWD_COIL_ACCURACY_ACCURATE
Definition fwd_coil.h:82
constexpr int FWD_BEM_UNKNOWN
QSharedPointer< FiffDirNode > SPtr
FIFF measurement file information.
Definition fiff_info.h:86
static bool readMegEegChannels(const QString &name, bool do_meg, bool do_eeg, const QStringList &bads, QList< FiffChInfo > &chsp, int &nmegp, int &neegp)
QList< FiffChInfo > chs
static bool readBadChannelsFromFile(const QString &name, QStringList &listOut)
FIFF File I/O routines.
QSharedPointer< FiffStream > SPtr
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 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 QString fwd_bem_make_bem_sol_name(const QString &name)
Build a standard BEM solution file name from a model name.
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.
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 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_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 FwdBemModel::UPtr fwd_bem_load_homog_surface(const QString &name)
Load a single-layer (homogeneous) BEM model from a FIFF file.
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.
Collection of FwdCoil objects representing a full MEG or EEG sensor array.
static FwdCoilSet::UPtr read_coil_defs(const QString &name)
static FwdCoilSet::UPtr create_eeg_els(const QList< FIFFLIB::FiffChInfo > &chs, int nch, const FIFFLIB::FiffCoordTrans &t=FIFFLIB::FiffCoordTrans())
std::vector< FwdCoil::UPtr > coils
This structure is used in the compensated field calculations.
static int fwd_comp_field_vec(const Eigen::Vector3f &rd, FwdCoilSet &coils, Eigen::Ref< Eigen::MatrixXf > res, void *client)
FwdCoilSet * comp_coils
static int fwd_comp_field(const Eigen::Vector3f &rd, const Eigen::Vector3f &Q, FwdCoilSet &coils, Eigen::Ref< Eigen::VectorXf > res, void *client)
static FwdCompData * fwd_make_comp_data(MNELIB::MNECTFCompDataSet *set, FwdCoilSet *coils, FwdCoilSet *comp_coils, fwdFieldFunc field, fwdVecFieldFunc vec_field, fwdFieldGradFunc field_grad, void *client)
Multi-layer spherical head model for EEG forward computation.
static int fwd_eeg_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)
Forward field computation function pointers and client data for MEG and EEG dipole fitting.
MNELIB::mneUserFreeFunc meg_client_free
Workspace for the dipole fitting objective function, holding forward model, measured field,...
Dipole fit workspace holding sensor geometry, forward model, noise covariance, and projection data.
std::unique_ptr< FWDLIB::FwdEegSphereModel > eeg_model
std::unique_ptr< FWDLIB::FwdCoilSet > meg_coils
static InvDipoleFitData * setup_dipole_fit_data(const QString &mriname, const QString &measname, const QString &bemname, Eigen::Vector3f *r0, FWDLIB::FwdEegSphereModel *eeg_model, int accurate_coils, const QString &badname, const QString &noisename, float grad_std, float mag_std, float eeg_std, float mag_reg, float grad_reg, float eeg_reg, int diagnoise, const QList< QString > &projnames, int include_meg, int include_eeg)
Master setup: read all inputs and build a ready-to-use fit workspace.
std::unique_ptr< FIFFLIB::FiffCoordTrans > mri_head_t
static int scale_dipole_fit_noise_cov(InvDipoleFitData *f, int nave)
Scale dipole-fit noise covariance for a given number of averages.
std::unique_ptr< MNELIB::MNEProjOp > proj
static InvDipoleForward * dipole_forward_one(InvDipoleFitData *d, const Eigen::Vector3f &rd, InvDipoleForward *old)
Compute the forward solution for a single dipole position.
std::unique_ptr< MNELIB::MNECovMatrix > noise
std::unique_ptr< dipoleFitFuncsRec > bem_funcs
static int scale_noise_cov(InvDipoleFitData *f, int nave)
Scale the noise-covariance matrix for a given number of averages.
std::unique_ptr< dipoleFitFuncsRec > sphere_funcs
static bool fit_one(InvDipoleFitData *fit, InvGuessData *guess, float time, Eigen::Ref< Eigen::VectorXf > B, int verbose, InvEcd &res)
Fit a single dipole to the given data.
static int compute_dipole_field(InvDipoleFitData &d, const Eigen::Vector3f &rd, int whiten, Eigen::Ref< Eigen::MatrixXf > fwd)
Compute the forward field for a dipole at the given location.
static int setup_forward_model(InvDipoleFitData *d, MNELIB::MNECTFCompDataSet *comp_data, FWDLIB::FwdCoilSet *comp_coils)
Set up the sphere-model and (optionally) BEM forward functions.
std::unique_ptr< FWDLIB::FwdBemModel > bem_model
static int select_dipole_fit_noise_cov(InvDipoleFitData *f, MNELIB::MNEMeasData *meas, int nave, const int *sels)
Select and weight the noise-covariance for the active channel set.
std::unique_ptr< FWDLIB::FwdCoilSet > eeg_els
std::unique_ptr< dipoleFitFuncsRec > mag_dipole_funcs
static std::unique_ptr< MNELIB::MNECovMatrix > ad_hoc_noise(FWDLIB::FwdCoilSet *meg, FWDLIB::FwdCoilSet *eeg, float grad_std, float mag_std, float eeg_std)
Create an ad-hoc diagonal noise-covariance matrix.
std::unique_ptr< MNELIB::MNECovMatrix > noise_orig
Stores forward field matrices and SVD decomposition for magnetic dipole fitting.
Eigen::Matrix< float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > uu
Eigen::Matrix< float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > vv
Eigen::Matrix< float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor > fwd
Single equivalent current dipole with position, orientation, amplitude, and goodness-of-fit.
Definition inv_ecd.h:73
Eigen::Vector3f Q
Definition inv_ecd.h:108
Eigen::Vector3f rd
Definition inv_ecd.h:107
Precomputed guess point grid with forward fields for initial dipole position candidates.
std::vector< InvDipoleForward::UPtr > guess_fwd
Eigen::Matrix< float, Eigen::Dynamic, 3, Eigen::RowMajor > rr
static bool simplex_minimize(Eigen::Matrix< T, Eigen::Dynamic, Eigen::Dynamic > &p, Eigen::Matrix< T, Eigen::Dynamic, 1 > &y, T ftol, T stol, CostFunc &&func, int max_eval, int &neval, int report, ReportFunc &&report_func)
static bool fit_sphere_to_points(const Eigen::MatrixXf &rr, float simplex_size, Eigen::VectorXf &r0, float &R)
static std::unique_ptr< MNECovMatrix > read(const QString &name, int kind)
static std::unique_ptr< MNECovMatrix > create(int kind, int ncov, const QStringList &names, const Eigen::VectorXd &cov, const Eigen::VectorXd &cov_diag)
static int lt_packed_index(int j, int k)
Collection of CTF third-order gradient compensation operators.
static std::unique_ptr< MNECTFCompDataSet > read(const QString &name)
Measurement data container for MNE inverse and dipole-fit computations.
MNEMeasDataSet * current
QList< FIFFLIB::FiffChInfo > chs
int getValuesAtTime(float time, float integ, int nch, bool use_abs, float *value) const
static bool makeProjection(const QList< QString > &projnames, const QList< FIFFLIB::FiffChInfo > &chs, int nch, std::unique_ptr< MNEProjOp > &result)
Load and combine SSP projection operators from files for the selected channels.
This defines a surface.
Definition mne_surface.h:79
static FiffCoordTrans readMriTransform(const QString &name)
static FiffCoordTrans readMeasTransform(const QString &name)
Eigen::MatrixX3f apply_trans(const Eigen::MatrixX3f &rr, bool do_move=true) const
static QString frame_name(int frame)