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