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