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,"\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]);
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.data(),nchan,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);
1237 simplexMat, /* The initial simplex */
1238 vals, /* Function values at the vertices */
1239 ftol[k], /* Relative convergence tolerance for the target function */
1240 atol[k], /* Absolute tolerance for the change in the parameters */
1241 fit_eval, /* The function to be evaluated */
1242 fit, /* Data to be passed to the above function in each evaluation */
1243 max_eval, /* Maximum number of function evaluations */
1244 neval, /* Number of function evaluations */
1245 report_interval, /* How often to report (-1 = no_reporting) */
1246 dipole_report_func)) {
1247 if (k == 0) {
1248 delete user.fwd;
1249 return false;
1250 }
1251 else {
1252 float rv = 2.0f*(vals.maxCoeff()-vals.minCoeff())/(vals.maxCoeff()+vals.minCoeff());
1253 qWarning("Warning (t = %8.1f ms) : g = %6.1f %% final val = %7.3f rtol = %f",
1254 1000*time,100*(1 - vals[0]/user.B2),vals[0],rv);
1255 fit_fail = true;
1256 }
1257 }
1258 rd_final = simplexMat.row(0).transpose();
1259 rd_guess = simplexMat.row(0).transpose();
1260
1261 neval_tot += neval;
1262 final_val = vals[0];
1263 }
1264 /*
1265 * Confidence limits should be computed here
1266 */
1267 /*
1268 * Compute the dipole moment at the final point
1269 */
1270 if (fit_Q(fit,B,rd_final,user.limit,Q,ncomp,final_val) == OK) {
1271 res.time = time;
1272 res.valid = true;
1273 res.rd = rd_final;
1274 res.Q = Q;
1275 res.good = 1.0 - final_val/user.B2;
1276 if (fit_fail)
1277 res.good = -res.good;
1278 res.khi2 = final_val;
1279 if (fit->proj)
1280 res.nfree = nchan-3-ncomp-fit->proj->nvec;
1281 else
1282 res.nfree = nchan-3-ncomp;
1283 res.neval = neval_tot;
1284 }
1285 else {
1286 delete user.fwd;
1287 return false;
1288 }
1289 delete user.fwd;
1290
1291 return true;
1292}
1293
1294//=============================================================================================================
1295
1301int InvDipoleFitData::compute_dipole_field(InvDipoleFitData& d, const Eigen::Vector3f& rd, int whiten, Eigen::Ref<Eigen::MatrixXf> fwd)
1302{
1303 static const Eigen::Vector3f Qx(1.0f, 0.0f, 0.0f);
1304 static const Eigen::Vector3f Qy(0.0f, 1.0f, 0.0f);
1305 static const Eigen::Vector3f Qz(0.0f, 0.0f, 1.0f);
1306 int nch = d.nmeg + d.neeg;
1307 int k;
1308 /*
1309 * Compute the fields
1310 */
1311 if (d.nmeg > 0) {
1312 int nmeg = d.meg_coils->ncoil();
1313 if (d.funcs->meg_vec_field) {
1314 /*
1315 * Use the vector field function: computes all three dipole
1316 * orientations at once. Output is 3 x ncoil, we need nch x 3.
1317 */
1318 Eigen::MatrixXf vec_meg(3, nmeg);
1319 if (d.funcs->meg_vec_field(rd,*d.meg_coils,vec_meg,d.funcs->meg_client) != OK)
1320 return FAIL;
1321 fwd.topRows(nmeg) = vec_meg.transpose();
1322 } else {
1323 auto fwd0 = fwd.col(0).head(nmeg);
1324 auto fwd1 = fwd.col(1).head(nmeg);
1325 auto fwd2 = fwd.col(2).head(nmeg);
1326 if (d.funcs->meg_field(rd,Qx,*d.meg_coils,fwd0,d.funcs->meg_client) != OK)
1327 return FAIL;
1328 if (d.funcs->meg_field(rd,Qy,*d.meg_coils,fwd1,d.funcs->meg_client) != OK)
1329 return FAIL;
1330 if (d.funcs->meg_field(rd,Qz,*d.meg_coils,fwd2,d.funcs->meg_client) != OK)
1331 return FAIL;
1332 }
1333 }
1334
1335 if (d.neeg > 0) {
1336 int neeg = d.eeg_els->ncoil();
1337 if (d.funcs->eeg_vec_pot) {
1338 /*
1339 * Use the vector potential function: computes all three dipole
1340 * orientations at once. Output is 3 x ncoil, we need nch x 3.
1341 */
1342 Eigen::MatrixXf vec_eeg(3, neeg);
1343 if (d.funcs->eeg_vec_pot(rd,*d.eeg_els,vec_eeg,d.funcs->eeg_client) != OK)
1344 return FAIL;
1345 fwd.block(d.nmeg, 0, neeg, 3) = vec_eeg.transpose();
1346 } else {
1347 auto fwd0 = fwd.col(0).segment(d.nmeg, neeg);
1348 auto fwd1 = fwd.col(1).segment(d.nmeg, neeg);
1349 auto fwd2 = fwd.col(2).segment(d.nmeg, neeg);
1350 if (d.funcs->eeg_pot(rd,Qx,*d.eeg_els,fwd0,d.funcs->eeg_client) != OK)
1351 return FAIL;
1352 if (d.funcs->eeg_pot(rd,Qy,*d.eeg_els,fwd1,d.funcs->eeg_client) != OK)
1353 return FAIL;
1354 if (d.funcs->eeg_pot(rd,Qz,*d.eeg_els,fwd2,d.funcs->eeg_client) != OK)
1355 return FAIL;
1356 }
1357 }
1358
1359 /*
1360 * Apply projection
1361 */
1362 for (k = 0; k < 3; k++)
1363 if (d.proj && d.proj->project_vector(fwd.col(k).data(),nch,true) == FAIL)
1364 return FAIL;
1365
1366 /*
1367 * Whiten
1368 */
1369 if (d.noise && whiten) {
1370 for (k = 0; k < 3; k++) {
1371 auto col_k = fwd.col(k);
1372 if (d.noise->whiten_vector(col_k,col_k,nch) == FAIL)
1373 return FAIL;
1374 }
1375 }
1376
1377 return OK;
1378}
#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)
#define OK
#define FAIL
Dipole Fit Data class declaration.
constexpr int COLUMN_NORM_NONE
constexpr int COLUMN_NORM_COMP
constexpr int COLUMN_NORM_LOC
InvGuessData class declaration.
Electric Current Dipole (InvEcd) class declaration.
FiffInfo 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
FiffStream class declaration.
#define FIFFV_BEM_SURF_ID_BRAIN
Definition fiff_file.h:749
FiffCoordTrans class declaration.
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.
MNESurface class declaration.
MNEMeasDataSet class declaration.
MNEMeasData class declaration.
SimplexAlgorithm Template Implementation.
Sphere class declaration.
Forward library type definitions.
FwdCompData class declaration.
FwdBemModel class declaration.
Core MNE data structures (source spaces, source estimates, hemispheres).
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
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, T(*func)(const Eigen::Matrix< T, Eigen::Dynamic, 1 > &x, const void *user_data), const void *user_data, int max_eval, int &neval, int report, bool(*report_func)(int loop, const Eigen::Matrix< T, Eigen::Dynamic, 1 > &fitpar, double fval_lo, double fval_hi, double par_diff))
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)