v2.0.0
Loading...
Searching...
No Matches
mne_forward_solution.cpp
Go to the documentation of this file.
1//=============================================================================================================
18
19//=============================================================================================================
20// INCLUDES
21//=============================================================================================================
22
24
25#include <fiff/fiff.h>
27
28//=============================================================================================================
29// EIGEN INCLUDES
30//=============================================================================================================
31
32#include <Eigen/SVD>
33#include <Eigen/Dense>
34#include <Eigen/Sparse>
35#include <unsupported/Eigen/KroneckerProduct>
36
37//=============================================================================================================
38
39#include <fs/fs_colortable.h>
40#include <fs/fs_label.h>
41#include <fs/fs_surfaceset.h>
42#include <math/linalg.h>
43#include <math/kmeans.h>
44
45#include <algorithm>
46#include <QtConcurrent>
47#include <QFuture>
48#include <QRegularExpression>
49
50//=============================================================================================================
51// USED NAMESPACES
52//=============================================================================================================
53
54using namespace MNELIB;
55using namespace FSLIB;
56using namespace UTILSLIB;
57using namespace Eigen;
58using namespace FIFFLIB;
59
60//=============================================================================================================
61
62namespace
63{
64
65bool check_matching_chnames_conventions(const QStringList& chNamesA, const QStringList& chNamesB, bool bCheckForNewNamingConvention = false)
66{
67 bool bMatching = false;
68
69 if (chNamesA.isEmpty()) {
70 qWarning("Warning in check_matching_chnames_conventions - chNamesA list is empty. Nothing to compare");
71 }
72 if (chNamesB.isEmpty()) {
73 qWarning("Warning in check_matching_chnames_conventions - chNamesB list is empty. Nothing to compare");
74 }
75
76 QString replaceStringOldConv, replaceStringNewConv;
77
78 for (int i = 0; i < chNamesA.size(); ++i) {
79 if (chNamesB.contains(chNamesA.at(i))) {
80 bMatching = true;
81 } else if (bCheckForNewNamingConvention) {
82 replaceStringNewConv = chNamesA.at(i);
83 replaceStringNewConv.replace(" ", "");
84
85 if (chNamesB.contains(replaceStringNewConv)) {
86 bMatching = true;
87 } else {
88 QRegularExpression xRegExp("[0-9]{1,100}");
89 QRegularExpressionMatch match = xRegExp.match(chNamesA.at(i));
90 QStringList xList = match.capturedTexts();
91
92 for (int k = 0; k < xList.size(); ++k) {
93 replaceStringOldConv = chNamesA.at(i);
94 replaceStringOldConv.replace(xList.at(k), QString("%1%2").arg(" ").arg(xList.at(k)));
95
96 if (chNamesB.contains(replaceStringNewConv) || chNamesB.contains(replaceStringOldConv)) {
97 bMatching = true;
98 } else {
99 bMatching = false;
100 }
101 }
102 }
103 }
104 }
105
106 return bMatching;
107}
108
109} // anonymous namespace
110
111//=============================================================================================================
112// CONSTANTS
113//=============================================================================================================
114
115// Return codes and axis indices (kept for documentation).
116[[maybe_unused]] constexpr int FAIL = -1;
117[[maybe_unused]] constexpr int OK = 0;
118[[maybe_unused]] constexpr int X = 0;
119[[maybe_unused]] constexpr int Y = 1;
120[[maybe_unused]] constexpr int Z = 2;
121
122//=============================================================================================================
123// DEFINE MEMBER METHODS
124//=============================================================================================================
125
127: source_ori(-1)
128, surf_ori(false)
129, coord_frame(-1)
130, nsource(-1)
131, nchan(-1)
132, sol(new FiffNamedMatrix)
134, source_rr(MatrixX3f::Zero(0, 3))
135, source_nn(MatrixX3f::Zero(0, 3))
136{
137}
138
139//=============================================================================================================
140
141MNEForwardSolution::MNEForwardSolution(QIODevice& p_IODevice, bool force_fixed, bool surf_ori, const QStringList& include, const QStringList& exclude, bool bExcludeBads)
142: source_ori(-1)
144, coord_frame(-1)
145, nsource(-1)
146, nchan(-1)
147, sol(new FiffNamedMatrix)
149, source_rr(MatrixX3f::Zero(0, 3))
150, source_nn(MatrixX3f::Zero(0, 3))
151{
152 if (!read(p_IODevice, *this, force_fixed, surf_ori, include, exclude, bExcludeBads)) {
153 qWarning("\tForward solution not found.");
154 return;
155 }
156}
157
158//=============================================================================================================
159
161: info(p_MNEForwardSolution.info)
162, source_ori(p_MNEForwardSolution.source_ori)
163, surf_ori(p_MNEForwardSolution.surf_ori)
164, coord_frame(p_MNEForwardSolution.coord_frame)
165, nsource(p_MNEForwardSolution.nsource)
166, nchan(p_MNEForwardSolution.nchan)
167, sol(p_MNEForwardSolution.sol)
168, sol_grad(p_MNEForwardSolution.sol_grad)
169, mri_head_t(p_MNEForwardSolution.mri_head_t)
170, mri_filename(p_MNEForwardSolution.mri_filename)
171, mri_id(p_MNEForwardSolution.mri_id)
172, src(p_MNEForwardSolution.src)
173, source_rr(p_MNEForwardSolution.source_rr)
174, source_nn(p_MNEForwardSolution.source_nn)
175{
176}
177
178//=============================================================================================================
179
181{
182 if (this != &other) {
183 info = other.info;
184 source_ori = other.source_ori;
185 surf_ori = other.surf_ori;
186 coord_frame = other.coord_frame;
187 nsource = other.nsource;
188 nchan = other.nchan;
189 sol = other.sol;
190 sol_grad = other.sol_grad;
191 mri_head_t = other.mri_head_t;
193 mri_id = other.mri_id;
194 src = other.src;
195 source_rr = other.source_rr;
196 source_nn = other.source_nn;
197 }
198 return *this;
199}
200
201//=============================================================================================================
202
206
207//=============================================================================================================
208
210{
211 info.clear();
212 source_ori = -1;
213 surf_ori = false;
214 coord_frame = -1;
215 nsource = -1;
216 nchan = -1;
219 mri_head_t.clear();
220 mri_filename.clear();
221 mri_id.clear();
222 src.clear();
223 source_rr = MatrixX3f(0, 3);
224 source_nn = MatrixX3f(0, 3);
225}
226
227//=============================================================================================================
228
229bool MNEForwardSolution::write(QIODevice& p_IODevice) const
230{
231 //
232 // Classify channels into MEG and EEG index sets
233 //
234 std::vector<int> megIdx, eegIdx;
235 for (int k = 0; k < info.chs.size(); ++k) {
236 fiff_int_t kind = info.chs[k].kind;
237 if (kind == FIFFV_MEG_CH || kind == FIFFV_REF_MEG_CH)
238 megIdx.push_back(k);
239 else if (kind == FIFFV_EEG_CH)
240 eegIdx.push_back(k);
241 }
242 int nmeg = static_cast<int>(megIdx.size());
243 int neeg = static_cast<int>(eegIdx.size());
244
245 //
246 // Compute the total number of active source vertices
247 //
248 int nvert = 0;
249 for (int k = 0; k < src.size(); ++k)
250 nvert += src[k].nuse;
251
252 //
253 // Open the file, create the directory
254 //
255 FiffStream::SPtr t_pStream = FiffStream::start_file(p_IODevice);
256 if (!t_pStream) {
257 return false;
258 }
259 t_pStream->start_block(FIFFB_MNE);
260
261 //
262 // Information from the MRI file
263 //
264 {
265 t_pStream->start_block(FIFFB_MNE_PARENT_MRI_FILE);
266
267 if (!mri_filename.isEmpty())
268 t_pStream->write_string(FIFF_MNE_FILE_NAME, mri_filename);
269 if (!mri_id.isEmpty())
270 t_pStream->write_id(FIFF_PARENT_FILE_ID, mri_id);
271 t_pStream->write_coord_trans(mri_head_t);
272
273 t_pStream->end_block(FIFFB_MNE_PARENT_MRI_FILE);
274 }
275
276 //
277 // Information from the measurement file
278 //
279 {
280 t_pStream->start_block(FIFFB_MNE_PARENT_MEAS_FILE);
281
282 if (!info.filename.isEmpty())
283 t_pStream->write_string(FIFF_MNE_FILE_NAME, info.filename);
284 if (!info.meas_id.isEmpty())
285 t_pStream->write_id(FIFF_PARENT_BLOCK_ID, info.meas_id);
286 t_pStream->write_coord_trans(info.dev_head_t);
287
288 int totalChan = nmeg + neeg;
289 t_pStream->write_int(FIFF_NCHAN, &totalChan);
290
291 // Write channel infos with sequential scanNo
292 QList<FiffChInfo> allChs;
293 for (int k = 0; k < nmeg; ++k)
294 allChs.append(info.chs[megIdx[k]]);
295 for (int k = 0; k < neeg; ++k)
296 allChs.append(info.chs[eegIdx[k]]);
297 for (int p = 0; p < allChs.size(); ++p) {
298 allChs[p].scanNo = p + 1;
299 t_pStream->write_ch_info(allChs[p]);
300 }
301
302 t_pStream->write_bad_channels(info.bads);
303
304 t_pStream->end_block(FIFFB_MNE_PARENT_MEAS_FILE);
305 }
306
307 //
308 // Write the source spaces
309 //
310 for (int k = 0; k < src.size(); ++k) {
311 if (src[k].writeToStream(t_pStream, false) == FIFF_FAIL) {
312 t_pStream->close();
313 return false;
314 }
315 }
316
317 //
318 // Extract sub-matrices for MEG and EEG from the combined sol
319 //
320 auto extractRows = [](const FiffNamedMatrix& combined,
321 const std::vector<int>& rowIdx) -> FiffNamedMatrix {
322 int nRows = static_cast<int>(rowIdx.size());
323 int nCols = combined.ncol;
324 MatrixXd data(nRows, nCols);
325 QStringList row_names;
326 for (int r = 0; r < nRows; ++r) {
327 data.row(r) = combined.data.row(rowIdx[r]);
328 row_names.append(combined.row_names[rowIdx[r]]);
329 }
330 FiffNamedMatrix sub;
331 sub.nrow = nRows;
332 sub.ncol = nCols;
333 sub.row_names = row_names;
334 sub.col_names = combined.col_names;
335 sub.data = data;
336 return sub;
337 };
338
340 int frame = coord_frame;
341
342 //
343 // MEG forward solution
344 //
345 if (nmeg > 0) {
346 t_pStream->start_block(FIFFB_MNE_FORWARD_SOLUTION);
347
348 int val = FIFFV_MNE_MEG;
349 t_pStream->write_int(FIFF_MNE_INCLUDED_METHODS, &val);
350 t_pStream->write_int(FIFF_MNE_COORD_FRAME, &frame);
351 t_pStream->write_int(FIFF_MNE_SOURCE_ORIENTATION, &ori_val);
352 t_pStream->write_int(FIFF_MNE_SOURCE_SPACE_NPOINTS, &nvert);
353 t_pStream->write_int(FIFF_NCHAN, &nmeg);
354
355 FiffNamedMatrix megSol = extractRows(*sol.data(), megIdx);
356 megSol.transpose_named_matrix();
357 t_pStream->write_named_matrix(FIFF_MNE_FORWARD_SOLUTION, megSol);
358
359 if (!sol_grad->isEmpty()) {
360 FiffNamedMatrix megGrad = extractRows(*sol_grad.data(), megIdx);
361 megGrad.transpose_named_matrix();
362 t_pStream->write_named_matrix(FIFF_MNE_FORWARD_SOLUTION_GRAD, megGrad);
363 }
364 t_pStream->end_block(FIFFB_MNE_FORWARD_SOLUTION);
365 }
366
367 //
368 // EEG forward solution
369 //
370 if (neeg > 0) {
371 t_pStream->start_block(FIFFB_MNE_FORWARD_SOLUTION);
372
373 int val = FIFFV_MNE_EEG;
374 t_pStream->write_int(FIFF_MNE_INCLUDED_METHODS, &val);
375 t_pStream->write_int(FIFF_MNE_COORD_FRAME, &frame);
376 t_pStream->write_int(FIFF_MNE_SOURCE_ORIENTATION, &ori_val);
377 t_pStream->write_int(FIFF_NCHAN, &neeg);
378 t_pStream->write_int(FIFF_MNE_SOURCE_SPACE_NPOINTS, &nvert);
379
380 FiffNamedMatrix eegSol = extractRows(*sol.data(), eegIdx);
381 eegSol.transpose_named_matrix();
382 t_pStream->write_named_matrix(FIFF_MNE_FORWARD_SOLUTION, eegSol);
383
384 if (!sol_grad->isEmpty()) {
385 FiffNamedMatrix eegGrad = extractRows(*sol_grad.data(), eegIdx);
386 eegGrad.transpose_named_matrix();
387 t_pStream->write_named_matrix(FIFF_MNE_FORWARD_SOLUTION_GRAD, eegGrad);
388 }
389 t_pStream->end_block(FIFFB_MNE_FORWARD_SOLUTION);
390 }
391
392 t_pStream->end_block(FIFFB_MNE);
393 t_pStream->end_file();
394 t_pStream->close();
395 t_pStream.clear();
396
397 //
398 // Update the directory
399 //
400 if (auto* qf = dynamic_cast<QFile*>(&p_IODevice)) {
401 QFile fileIn(qf->fileName());
402 FiffStream::SPtr t_pStreamIn = FiffStream::open_update(fileIn);
403 if (t_pStreamIn) {
404 const auto& dir = t_pStreamIn->dir();
405 for (int i = 0; i < dir.size(); ++i) {
406 if (dir[i]->kind == FIFF_DIR_POINTER) {
407 fiff_int_t dirpos = (fiff_int_t)t_pStreamIn->write_dir_entries(dir);
408 if (dirpos >= 0)
409 t_pStreamIn->write_dir_pointer(dirpos, dir[i]->pos);
410 break;
411 }
412 }
413 t_pStreamIn->close();
414 }
415 }
416
417 return true;
418}
419
420//=============================================================================================================
421
423 qint32 p_iClusterSize,
424 MatrixXd& p_D,
425 const FiffCov& p_pNoise_cov,
426 const FiffInfo& p_pInfo,
427 QString p_sMethod) const
428{
429 qInfo("Cluster forward solution using %s.", p_sMethod.toUtf8().constData());
430
431 MNEForwardSolution p_fwdOut = MNEForwardSolution(*this);
432
433 //Check if cov naming conventions are matching
434 if (!check_matching_chnames_conventions(p_pNoise_cov.names, p_pInfo.ch_names) && !p_pNoise_cov.names.isEmpty() && !p_pInfo.ch_names.isEmpty()) {
435 if (check_matching_chnames_conventions(p_pNoise_cov.names, p_pInfo.ch_names, true)) {
436 qWarning("MNEForwardSolution::cluster_forward_solution - Cov names do match with info channel names but have a different naming convention.");
437 //return p_fwdOut;
438 } else {
439 qWarning("MNEForwardSolution::cluster_forward_solution - Cov channel names do not match with info channel names.");
440 //return p_fwdOut;
441 }
442 }
443
444 //
445 // Check consisty
446 //
447 if (this->isFixedOrient()) {
448 qWarning("Error: Fixed orientation not implemented yet!");
449 return p_fwdOut;
450 }
451
452 MatrixXd t_G_Whitened(0, 0);
453 bool t_bUseWhitened = false;
454 //
455 //Whiten gain matrix before clustering -> cause diffenerent units Magnetometer, Gradiometer and EEG
456 //
457 if (!p_pNoise_cov.isEmpty() && !p_pInfo.isEmpty()) {
458 FiffInfo p_outFwdInfo;
459 FiffCov p_outNoiseCov;
460 MatrixXd p_outWhitener;
461 qint32 p_outNumNonZero;
462 //do whitening with noise cov
463 this->prepare_forward(p_pInfo, p_pNoise_cov, false, p_outFwdInfo, t_G_Whitened, p_outNoiseCov, p_outWhitener, p_outNumNonZero);
464 qInfo("\tWhitening the forward solution.");
465
466 t_G_Whitened = p_outWhitener * t_G_Whitened;
467 t_bUseWhitened = true;
468 }
469
470 //
471 // Assemble input data
472 //
473 qint32 count;
474 qint32 offset;
475
476 MatrixXd t_G_new;
477
478 for (qint32 h = 0; h < this->src.size(); ++h) {
479 count = 0;
480 offset = 0;
481
482 // Offset for continuous indexing;
483 if (h > 0)
484 for (qint32 j = 0; j < h; ++j)
485 offset += this->src[j].nuse;
486
487 if (h == 0)
488 qInfo("Cluster Left Hemisphere");
489 else
490 qInfo("Cluster Right Hemisphere");
491
492 const FsAnnotation annotation = p_AnnotationSet[h];
493 FsColortable t_CurrentColorTable = annotation.getColortable();
494 VectorXi label_ids = t_CurrentColorTable.getLabelIds();
495
496 // Get label ids for every vertex
497 VectorXi vertno_labeled = VectorXi::Zero(this->src[h].vertno.rows());
498
499 //ToDo make this more universal -> using FsLabel instead of annotations - obsolete when using Labels
500 for (qint32 i = 0; i < vertno_labeled.rows(); ++i)
501 vertno_labeled[i] = p_AnnotationSet[h].getLabelIds()[this->src[h].vertno[i]];
502
503 std::vector<RegionData> regionDataIn;
504
505 //
506 // Generate cluster input data
507 //
508 for (qint32 i = 0; i < label_ids.rows(); ++i) {
509 if (label_ids[i] != 0) {
510 QString curr_name = t_CurrentColorTable.struct_names[i]; //obj.label2AtlasName(label(i));
511 qInfo("\tCluster %d / %ld %s...", i + 1, label_ids.rows(), curr_name.toUtf8().constData());
512
513 //
514 // Get source space indeces
515 //
516 VectorXi idcs = VectorXi::Zero(vertno_labeled.rows());
517 qint32 c = 0;
518
519 //Select ROIs //change this use label info with a hash tabel
520 for (qint32 j = 0; j < vertno_labeled.rows(); ++j) {
521 if (vertno_labeled[j] == label_ids[i]) {
522 idcs[c] = j;
523 ++c;
524 }
525 }
526 idcs.conservativeResize(c);
527
528 //get selected G
529 MatrixXd t_G(this->sol->data.rows(), idcs.rows() * 3);
530 MatrixXd t_G_Whitened_Roi(t_G_Whitened.rows(), idcs.rows() * 3);
531
532 for (qint32 j = 0; j < idcs.rows(); ++j) {
533 t_G.block(0, j * 3, t_G.rows(), 3) = this->sol->data.block(0, (idcs[j] + offset) * 3, t_G.rows(), 3);
534 if (t_bUseWhitened)
535 t_G_Whitened_Roi.block(0, j * 3, t_G_Whitened_Roi.rows(), 3) = t_G_Whitened.block(0, (idcs[j] + offset) * 3, t_G_Whitened_Roi.rows(), 3);
536 }
537
538 qint32 nSens = t_G.rows();
539 qint32 nSources = t_G.cols() / 3;
540
541 if (nSources > 0) {
542 RegionData t_sensG;
543
544 t_sensG.idcs = idcs;
545 t_sensG.iLabelIdxIn = i;
546 t_sensG.nClusters = static_cast<int>(ceil(static_cast<double>(nSources) / static_cast<double>(p_iClusterSize)));
547
548 t_sensG.matRoiGOrig = t_G;
549
550 qInfo("%d Cluster(s)...", t_sensG.nClusters);
551
552 // Reshape Input data -> sources rows; sensors columns
553 t_sensG.matRoiG = MatrixXd(t_G.cols() / 3, 3 * nSens);
554 for (qint32 j = 0; j < nSens; ++j) {
555 for (qint32 k = 0; k < t_sensG.matRoiG.rows(); ++k)
556 t_sensG.matRoiG.block(k, j * 3, 1, 3) = t_G.block(j, k * 3, 1, 3);
557 }
558 // The whitened gain only has the channels prepare_forward() kept, so fewer rows than t_G
559 if (t_bUseWhitened) {
560 const qint32 nSensWhitened = static_cast<qint32>(t_G_Whitened_Roi.rows());
561 t_sensG.matRoiGWhitened = MatrixXd(t_G_Whitened_Roi.cols() / 3, 3 * nSensWhitened);
562 for (qint32 j = 0; j < nSensWhitened; ++j) {
563 for (qint32 k = 0; k < t_sensG.matRoiGWhitened.rows(); ++k)
564 t_sensG.matRoiGWhitened.block(k, j * 3, 1, 3) = t_G_Whitened_Roi.block(j, k * 3, 1, 3);
565 }
566 }
567
568 t_sensG.bUseWhitened = t_bUseWhitened;
569
570 t_sensG.sDistMeasure = p_sMethod;
571
572 regionDataIn.push_back(std::move(t_sensG));
573
574 qInfo("[added]");
575 } else {
576 qWarning("failed! FsLabel contains no sources.");
577 }
578 }
579 }
580
581 //
582 // Calculate clusters
583 //
584 qInfo("Clustering...");
585 QFuture<RegionDataOut> res;
586 res = QtConcurrent::mapped(regionDataIn, &RegionData::cluster);
587 res.waitForFinished();
588
589 //
590 // Assign results
591 //
592 MatrixXd t_G_partial;
593
594 qint32 nClusters;
595 qint32 nSens;
596 auto itIn = regionDataIn.cbegin();
597 QFuture<RegionDataOut>::const_iterator itOut;
598 for (itOut = res.constBegin(); itOut != res.constEnd(); ++itOut) {
599 nClusters = itOut->ctrs.rows();
600 nSens = itOut->ctrs.cols() / 3;
601 t_G_partial = MatrixXd::Zero(nSens, nClusters * 3);
602
603 //
604 // Assign the centroid for each cluster to the partial G
605 //
606 //ToDo change this use indeces found with whitened data
607 for (qint32 j = 0; j < nSens; ++j)
608 for (qint32 k = 0; k < nClusters; ++k)
609 t_G_partial.block(j, k * 3, 1, 3) = itOut->ctrs.block(k, j * 3, 1, 3);
610
611 //
612 // Get cluster indizes and its distances to the centroid
613 //
614 for (qint32 j = 0; j < nClusters; ++j) {
615 VectorXi clusterIdcs = VectorXi::Zero(itOut->roiIdx.rows());
616 VectorXd clusterDistance = VectorXd::Zero(itOut->roiIdx.rows());
617 MatrixX3f clusterSource_rr = MatrixX3f::Zero(itOut->roiIdx.rows(), 3);
618 qint32 nClusterIdcs = 0;
619 for (qint32 k = 0; k < itOut->roiIdx.rows(); ++k) {
620 if (itOut->roiIdx[k] == j) {
621 clusterIdcs[nClusterIdcs] = itIn->idcs[k];
622
623 const qint32 hemiOffset = h == 0 ? 0 : this->src[0].nuse;
624 clusterSource_rr.row(nClusterIdcs) = this->source_rr.row(hemiOffset + itIn->idcs[k]);
625 clusterDistance[nClusterIdcs] = itOut->D(k, j);
626 ++nClusterIdcs;
627 }
628 }
629 clusterIdcs.conservativeResize(nClusterIdcs);
630 clusterSource_rr.conservativeResize(nClusterIdcs, 3);
631 clusterDistance.conservativeResize(nClusterIdcs);
632
633 VectorXi clusterVertnos = VectorXi::Zero(clusterIdcs.size());
634 for (qint32 k = 0; k < clusterVertnos.size(); ++k)
635 clusterVertnos(k) = this->src[h].vertno[clusterIdcs(k)];
636
637 p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterVertnos.append(clusterVertnos);
638 p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterSource_rr.append(clusterSource_rr);
639 p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterDistances.append(clusterDistance);
640 p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterLabelIds.append(label_ids[itOut->iLabelIdxOut]);
641 p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterLabelNames.append(t_CurrentColorTable.getNames()[itOut->iLabelIdxOut]);
642 }
643
644 //
645 // Assign partial G to new LeadField
646 //
647 if (t_G_partial.rows() > 0 && t_G_partial.cols() > 0) {
648 t_G_new.conservativeResize(t_G_partial.rows(), t_G_new.cols() + t_G_partial.cols());
649 t_G_new.block(0, t_G_new.cols() - t_G_partial.cols(), t_G_new.rows(), t_G_partial.cols()) = t_G_partial;
650
651 // Map the centroids to the closest rr
652 for (qint32 k = 0; k < nClusters; ++k) {
653 double sqec = sqrt((itIn->matRoiGOrig.block(0, 0, itIn->matRoiGOrig.rows(), 3) - t_G_partial.block(0, k * 3, t_G_partial.rows(), 3)).array().pow(2).sum());
654 double sqec_min = sqec;
655 qint32 j_min = 0;
656 for (qint32 j = 1; j < itIn->idcs.rows(); ++j) {
657 sqec = sqrt((itIn->matRoiGOrig.block(0, j * 3, itIn->matRoiGOrig.rows(), 3) - t_G_partial.block(0, k * 3, t_G_partial.rows(), 3)).array().pow(2).sum());
658
659 if (sqec < sqec_min) {
660 sqec_min = sqec;
661 j_min = j;
662 }
663 }
664
665 // Take the closest coordinates
666 qint32 sel_idx = itIn->idcs[j_min];
667
668 p_fwdOut.src.hemisphereAt(h)->cluster_info.centroidVertno.append(this->src[h].vertno[sel_idx]);
669 p_fwdOut.src.hemisphereAt(h)->cluster_info.centroidSource_rr.append(this->src[h].rr.row(this->src[h].vertno[sel_idx]));
670 // Option 2 label ID
671 p_fwdOut.src[h].vertno[count] = p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterLabelIds[count];
672
673 ++count;
674 }
675 }
676
677 ++itIn;
678 }
679
680 //
681 // Assemble new hemisphere information
682 //
683 p_fwdOut.src[h].vertno.conservativeResize(count);
684
685 qInfo("[done]");
686 }
687
688 //
689 // Cluster operator D (sources x clusters)
690 //
691 qint32 totalNumOfClust = 0;
692 for (qint32 h = 0; h < 2; ++h)
693 totalNumOfClust += p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterVertnos.size();
694
695 if (this->isFixedOrient())
696 p_D = MatrixXd::Zero(this->sol->data.cols(), totalNumOfClust);
697 else
698 p_D = MatrixXd::Zero(this->sol->data.cols(), totalNumOfClust * 3);
699
700 QList<VectorXi> t_vertnos = this->src.get_vertno();
701
702 qint32 currentCluster = 0;
703 for (qint32 h = 0; h < 2; ++h) {
704 int hemiOffset = h == 0 ? 0 : t_vertnos[0].size();
705 for (qint32 i = 0; i < p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterVertnos.size(); ++i) {
706 VectorXi idx_sel;
707 Linalg::intersect(t_vertnos[h], p_fwdOut.src.hemisphereAt(h)->cluster_info.clusterVertnos[i], idx_sel);
708
709 idx_sel.array() += hemiOffset;
710
711 double selectWeight = 1.0 / idx_sel.size();
712 if (this->isFixedOrient()) {
713 for (qint32 j = 0; j < idx_sel.size(); ++j)
714 p_D.col(currentCluster)[idx_sel(j)] = selectWeight;
715 } else {
716 qint32 clustOffset = currentCluster * 3;
717 for (qint32 j = 0; j < idx_sel.size(); ++j) {
718 qint32 idx_sel_Offset = idx_sel(j) * 3;
719 //x
720 p_D(idx_sel_Offset, clustOffset) = selectWeight;
721 //y
722 p_D(idx_sel_Offset + 1, clustOffset + 1) = selectWeight;
723 //z
724 p_D(idx_sel_Offset + 2, clustOffset + 2) = selectWeight;
725 }
726 }
727 ++currentCluster;
728 }
729 }
730
731 //
732 // Put it all together
733 //
734 p_fwdOut.sol->data = t_G_new;
735 p_fwdOut.sol->ncol = t_G_new.cols();
736
737 p_fwdOut.nsource = p_fwdOut.sol->ncol / 3;
738
739 return p_fwdOut;
740}
741
742//=============================================================================================================
743
744MNEForwardSolution MNEForwardSolution::reduce_forward_solution(qint32 p_iNumDipoles, MatrixXd& p_D) const
745{
746 MNEForwardSolution p_fwdOut = MNEForwardSolution(*this);
747
748 bool isFixed = p_fwdOut.isFixedOrient();
749 qint32 np = isFixed ? p_fwdOut.sol->data.cols() : p_fwdOut.sol->data.cols() / 3;
750
751 if (p_iNumDipoles > np)
752 return p_fwdOut;
753
754 VectorXi sel(p_iNumDipoles);
755
756 float t_fStep = static_cast<float>(np) / static_cast<float>(p_iNumDipoles);
757
758 for (qint32 i = 0; i < p_iNumDipoles; ++i) {
759 float t_fCurrent = static_cast<float>(i) * t_fStep;
760 sel[i] = (quint32)floor(t_fCurrent);
761 }
762
763 if (isFixed) {
764 p_D = MatrixXd::Zero(p_fwdOut.sol->data.cols(), p_iNumDipoles);
765 for (qint32 i = 0; i < p_iNumDipoles; ++i)
766 p_D(sel[i], i) = 1;
767 } else {
768 p_D = MatrixXd::Zero(p_fwdOut.sol->data.cols(), p_iNumDipoles * 3);
769 for (qint32 i = 0; i < p_iNumDipoles; ++i)
770 for (qint32 j = 0; j < 3; ++j)
771 p_D((sel[i] * 3) + j, (i * 3) + j) = 1;
772 }
773
774 // New gain matrix
775 p_fwdOut.sol->data = this->sol->data * p_D;
776
777 MatrixX3f rr(p_iNumDipoles, 3);
778
779 MatrixX3f nn(p_iNumDipoles, 3);
780
781 for (qint32 i = 0; i < p_iNumDipoles; ++i) {
782 rr.row(i) = this->source_rr.row(sel(i));
783 nn.row(i) = this->source_nn.row(sel(i));
784 }
785
786 p_fwdOut.source_rr = rr;
787 p_fwdOut.source_nn = nn;
788
789 p_fwdOut.sol->ncol = p_fwdOut.sol->data.cols();
790
791 p_fwdOut.nsource = p_iNumDipoles;
792
793 return p_fwdOut;
794}
795
796//=============================================================================================================
797
798FiffCov MNEForwardSolution::compute_depth_prior(const MatrixXd& Gain, const FiffInfo& gain_info, bool is_fixed_ori, double exp, double limit, const MatrixXd& patch_areas, bool limit_depth_chs)
799{
800 qInfo("\tCreating the depth weighting matrix...");
801
802 MatrixXd G(Gain);
803 // If possible, pick best depth-weighting channels
804 if (limit_depth_chs)
806
807 VectorXd d;
808 // Compute the gain matrix
809 if (is_fixed_ori) {
810 d = G.array().square().colwise().sum().transpose();
811 // A spherical lead field can vanish at the centre
812 const double minNonZero = (d.array() != 0.0).select(d.array(), std::numeric_limits<double>::infinity()).minCoeff();
813 d = (d.array() == 0.0).select(minNonZero, d.array());
814 } else {
815 qint32 n_pos = G.cols() / 3;
816 d = VectorXd::Zero(n_pos);
817 MatrixXd Gk;
818 for (qint32 k = 0; k < n_pos; ++k) {
819 Gk = G.block(0, 3 * k, G.rows(), 3);
820 JacobiSVD<MatrixXd> svd(Gk.transpose() * Gk);
821 d[k] = svd.singularValues().maxCoeff();
822 }
823 }
824
825 if (patch_areas.size() > 0) {
826 d.array() /= patch_areas.reshaped().array().square();
827 qInfo("\tPatch areas taken into account in the depth weighting");
828 }
829
830 qint32 n_limit;
831 VectorXd w = d.cwiseInverse();
832 VectorXd ws = w;
833 VectorXd wpp;
834 Linalg::sort<double>(ws, false);
835 double weight_limit = pow(limit, 2);
836 if (!limit_depth_chs) {
837 // match old mne-python behavor
838 qint32 ind = 0;
839 ws.minCoeff(&ind);
840 n_limit = ind;
841 limit = ws[ind] * weight_limit;
842 } else {
843 // match C code behavior
844 limit = ws[ws.size() - 1];
845 qint32 ind = 0;
846 n_limit = d.size();
847 if (ws[ws.size() - 1] > weight_limit * ws[0]) {
848 double th = weight_limit * ws[0];
849 for (qint32 i = 0; i < ws.size(); ++i) {
850 if (ws[i] > th) {
851 ind = i;
852 break;
853 }
854 }
855 limit = ws[ind];
856 n_limit = ind;
857 }
858 }
859
860 qInfo("\tlimit = %d/%ld = %f", n_limit + 1, d.size(), sqrt(limit / ws[0]));
861 double scale = 1.0 / limit;
862 qInfo("\tscale = %g exp = %g", scale, exp);
863
864 VectorXd t_w = w.array() / limit;
865 for (qint32 i = 0; i < t_w.size(); ++i)
866 t_w[i] = t_w[i] > 1 ? 1 : t_w[i];
867 wpp = t_w.array().pow(exp);
868
869 FiffCov depth_prior;
870 if (is_fixed_ori)
871 depth_prior.data = wpp;
872 else {
873 depth_prior.data.resize(wpp.rows() * 3, 1);
874 qint32 idx = 0;
875 double v;
876 for (qint32 i = 0; i < wpp.rows(); ++i) {
877 idx = i * 3;
878 v = wpp[i];
879 depth_prior.data(idx, 0) = v;
880 depth_prior.data(idx + 1, 0) = v;
881 depth_prior.data(idx + 2, 0) = v;
882 }
883 }
884
885 depth_prior.kind = FIFFV_MNE_DEPTH_PRIOR_COV;
886 depth_prior.diag = true;
887 depth_prior.dim = depth_prior.data.rows();
888 depth_prior.nfree = 1;
889
890 return depth_prior;
891}
892
893//=============================================================================================================
894
896{
897 bool is_fixed_ori = this->isFixedOrient();
898 qint32 n_sources = this->sol->data.cols();
899
900 if (0 <= loose && loose <= 1) {
901 if (loose < 1 && !this->surf_ori) {
902 qWarning("\tForward operator is not oriented in surface coordinates. loose parameter should be None not %f.", loose);
903 loose = 1;
904 qInfo("\tSetting loose to %f.", loose);
905 }
906
907 if (is_fixed_ori) {
908 qInfo("\tIgnoring loose parameter with forward operator with fixed orientation.");
909 loose = 0.0;
910 }
911 } else {
912 if (loose < 0 || loose > 1) {
913 qWarning("Warning: Loose value should be in interval [0,1] not %f.\n", loose);
914 loose = loose > 1 ? 1 : 0;
915 qInfo("Setting loose to %f.", loose);
916 }
917 }
918
919 FiffCov orient_prior;
920 orient_prior.data = VectorXd::Ones(n_sources);
921 if (!is_fixed_ori && (0 <= loose && loose <= 1)) {
922 qInfo("\tApplying loose dipole orientations. Loose value of %f.", loose);
923 for (qint32 i = 0; i < n_sources; i += 3)
924 orient_prior.data.block(i, 0, 2, 1).array() *= loose;
925
926 orient_prior.kind = FIFFV_MNE_ORIENT_PRIOR_COV;
927 orient_prior.diag = true;
928 orient_prior.dim = orient_prior.data.size();
929 orient_prior.nfree = 1;
930 }
931 return orient_prior;
932}
933
934//=============================================================================================================
935
937 const QStringList& exclude) const
938{
939 MNEForwardSolution fwd(*this);
940
941 if (include.size() == 0 && exclude.size() == 0)
942 return fwd;
943
944 RowVectorXi sel = FiffInfo::pick_channels(fwd.sol->row_names, include, exclude);
945
946 // Do we have something?
947 quint32 nuse = sel.size();
948
949 if (nuse == 0) {
950 qInfo("Nothing remains after picking. Returning original forward solution.");
951 return fwd;
952 }
953 qInfo("\t%d out of %d channels remain after picking", nuse, fwd.nchan);
954
955 // Pick the correct rows of the forward operator
956 MatrixXd newData(nuse, fwd.sol->data.cols());
957 for (quint32 i = 0; i < nuse; ++i)
958 newData.row(i) = fwd.sol->data.row(sel[i]);
959
960 fwd.sol->data = newData;
961 fwd.sol->nrow = nuse;
962
963 QStringList ch_names;
964 for (qint32 i = 0; i < sel.cols(); ++i)
965 ch_names << fwd.sol->row_names[sel(i)];
966 fwd.nchan = nuse;
967 fwd.sol->row_names = ch_names;
968
969 QList<FiffChInfo> chs;
970 for (qint32 i = 0; i < sel.cols(); ++i)
971 chs.append(fwd.info.chs[sel(i)]);
972 fwd.info.chs = chs;
973 fwd.info.nchan = nuse;
974
975 QStringList bads;
976 for (qint32 i = 0; i < fwd.info.bads.size(); ++i)
977 if (ch_names.contains(fwd.info.bads[i]))
978 bads.append(fwd.info.bads[i]);
979 fwd.info.bads = bads;
980
981 if (!fwd.sol_grad->isEmpty()) {
982 newData.resize(nuse, fwd.sol_grad->data.cols());
983 for (quint32 i = 0; i < nuse; ++i)
984 newData.row(i) = fwd.sol_grad->data.row(sel[i]);
985 fwd.sol_grad->data = newData;
986 fwd.sol_grad->nrow = nuse;
987 QStringList row_names;
988 for (qint32 i = 0; i < sel.cols(); ++i)
989 row_names << fwd.sol_grad->row_names[sel(i)];
990 fwd.sol_grad->row_names = row_names;
991 }
992
993 return fwd;
994}
995
996//=============================================================================================================
997
998MNEForwardSolution MNEForwardSolution::pick_regions(const QList<FsLabel>& p_qListLabels) const
999{
1000 VectorXi selVertices;
1001
1002 qint32 iSize = 0;
1003 for (qint32 i = 0; i < p_qListLabels.size(); ++i) {
1004 VectorXi currentSelection;
1005 this->src.label_src_vertno_sel(p_qListLabels[i], currentSelection);
1006
1007 selVertices.conservativeResize(iSize + currentSelection.size());
1008 selVertices.block(iSize, 0, currentSelection.size(), 1) = currentSelection;
1009 iSize = selVertices.size();
1010 }
1011
1012 Linalg::sort(selVertices, false);
1013
1014 MNEForwardSolution selectedFwd(*this);
1015
1016 MatrixX3f rr(selVertices.size(), 3);
1017 for (qint32 i = 0; i < selVertices.size(); ++i)
1018 rr.row(i) = selectedFwd.source_rr.row(selVertices[i]);
1019
1020 // Free orientation stores three normals and three gain columns per source.
1021 const VectorXi selSolIdcs = isFixedOrient() ? selVertices : tripletSelection(selVertices);
1022
1023 MatrixX3f nn(selSolIdcs.size(), 3);
1024 MatrixXd G(selectedFwd.sol->data.rows(), selSolIdcs.size());
1025 for (qint32 i = 0; i < selSolIdcs.size(); ++i) {
1026 nn.row(i) = selectedFwd.source_nn.row(selSolIdcs[i]);
1027 G.col(i) = selectedFwd.sol->data.col(selSolIdcs[i]);
1028 }
1029
1030 selectedFwd.source_rr = rr;
1031 selectedFwd.source_nn = nn;
1032 selectedFwd.sol->data = G;
1033 selectedFwd.sol->nrow = selectedFwd.sol->data.rows();
1034 selectedFwd.sol->ncol = selectedFwd.sol->data.cols();
1035 selectedFwd.nsource = static_cast<int>(selVertices.size());
1036
1037 selectedFwd.src = selectedFwd.src.pick_regions(p_qListLabels);
1038
1039 return selectedFwd;
1040}
1041
1042//=============================================================================================================
1043
1044MNEForwardSolution MNEForwardSolution::pick_types(bool meg, bool eeg, const QStringList& include, const QStringList& exclude) const
1045{
1046 RowVectorXi sel = info.pick_types(meg, eeg, false, include, exclude);
1047
1048 QStringList include_ch_names;
1049 for (qint32 i = 0; i < sel.cols(); ++i)
1050 include_ch_names << info.ch_names[sel[i]];
1051
1052 return this->pick_channels(include_ch_names);
1053}
1054
1055//=============================================================================================================
1056
1058 const FiffCov& p_noise_cov,
1059 bool p_pca,
1060 FiffInfo& p_outFwdInfo,
1061 MatrixXd& gain,
1062 FiffCov& p_outNoiseCov,
1063 MatrixXd& p_outWhitener,
1064 qint32& p_outNumNonZero) const
1065{
1066 QStringList fwd_ch_names, ch_names;
1067 for (qint32 i = 0; i < this->info.chs.size(); ++i)
1068 fwd_ch_names << this->info.chs[i].ch_name;
1069
1070 ch_names.clear();
1071 for (qint32 i = 0; i < p_info.chs.size(); ++i)
1072 if (!p_info.bads.contains(p_info.chs[i].ch_name) && !p_noise_cov.bads.contains(p_info.chs[i].ch_name) && p_noise_cov.names.contains(p_info.chs[i].ch_name) && fwd_ch_names.contains(p_info.chs[i].ch_name))
1073 ch_names << p_info.chs[i].ch_name;
1074
1075 qint32 n_chan = ch_names.size();
1076 qInfo("Computing inverse operator with %d channels.", n_chan);
1077
1078 //
1079 // Handle noise cov
1080 //
1081 p_outNoiseCov = p_noise_cov.prepare_noise_cov(p_info, ch_names);
1082
1083 // Omit the zeroes due to projection
1084 p_outNumNonZero = 0;
1085 VectorXi t_vecNonZero = VectorXi::Zero(n_chan);
1086 for (qint32 i = 0; i < p_outNoiseCov.eig.rows(); ++i) {
1087 if (p_outNoiseCov.eig[i] > 0) {
1088 t_vecNonZero[p_outNumNonZero] = i;
1089 ++p_outNumNonZero;
1090 }
1091 }
1092 if (p_outNumNonZero > 0)
1093 t_vecNonZero.conservativeResize(p_outNumNonZero);
1094
1095 if (p_outNumNonZero > 0) {
1096 if (p_pca) {
1097 qWarning("Warning in MNEForwardSolution::prepare_forward: if (p_pca) havent been debugged.");
1098 p_outWhitener = MatrixXd::Zero(n_chan, p_outNumNonZero);
1099 // Rows of eigvec are the eigenvectors
1100 for (qint32 i = 0; i < p_outNumNonZero; ++i)
1101 p_outWhitener.col(t_vecNonZero[i]) = p_outNoiseCov.eigvec.col(t_vecNonZero[i]).array() / sqrt(p_outNoiseCov.eig(t_vecNonZero[i]));
1102 qInfo("\tReducing data rank to %d.", p_outNumNonZero);
1103 } else {
1104 qInfo("Creating non pca whitener.");
1105 p_outWhitener = MatrixXd::Zero(n_chan, n_chan);
1106 for (qint32 i = 0; i < p_outNumNonZero; ++i)
1107 p_outWhitener(t_vecNonZero[i], t_vecNonZero[i]) = 1.0 / sqrt(p_outNoiseCov.eig(t_vecNonZero[i]));
1108 // Cols of eigvec are the eigenvectors
1109 p_outWhitener *= p_outNoiseCov.eigvec;
1110 }
1111 }
1112
1113 VectorXi fwd_idx = VectorXi::Zero(ch_names.size());
1114 VectorXi info_idx = VectorXi::Zero(ch_names.size());
1115 qint32 idx;
1116 qint32 count_fwd_idx = 0;
1117 qint32 count_info_idx = 0;
1118 for (qint32 i = 0; i < ch_names.size(); ++i) {
1119 idx = fwd_ch_names.indexOf(ch_names[i]);
1120 if (idx > -1) {
1121 fwd_idx[count_fwd_idx] = idx;
1122 ++count_fwd_idx;
1123 }
1124 idx = p_info.ch_names.indexOf(ch_names[i]);
1125 if (idx > -1) {
1126 info_idx[count_info_idx] = idx;
1127 ++count_info_idx;
1128 }
1129 }
1130 fwd_idx.conservativeResize(count_fwd_idx);
1131 info_idx.conservativeResize(count_info_idx);
1132
1133 gain.resize(count_fwd_idx, this->sol->data.cols());
1134 for (qint32 i = 0; i < count_fwd_idx; ++i)
1135 gain.row(i) = this->sol->data.row(fwd_idx[i]);
1136
1137 p_outFwdInfo = p_info.pick_info(info_idx);
1138
1139 qInfo("\tTotal rank is %d", p_outNumNonZero);
1140}
1141
1142//=============================================================================================================
1143
1144bool MNEForwardSolution::read(QIODevice& p_IODevice,
1145 MNEForwardSolution& fwd,
1146 bool force_fixed,
1147 bool surf_ori,
1148 const QStringList& include,
1149 const QStringList& exclude,
1150 bool bExcludeBads)
1151{
1152 FiffStream::SPtr t_pStream(new FiffStream(&p_IODevice));
1153
1154 qInfo("Reading forward solution from %s...", t_pStream->streamName().toUtf8().constData());
1155 if (!t_pStream->open())
1156 return false;
1157 //
1158 // Find all forward solutions
1159 //
1160 QList<FiffDirNode::SPtr> fwds = t_pStream->dirtree()->dir_tree_find(FIFFB_MNE_FORWARD_SOLUTION);
1161
1162 if (fwds.size() == 0) {
1163 t_pStream->close();
1164 qWarning("No forward solutions in %s", t_pStream->streamName().toUtf8().constData());
1165 return false;
1166 }
1167 //
1168 // Parent MRI data
1169 //
1170 QList<FiffDirNode::SPtr> parent_mri = t_pStream->dirtree()->dir_tree_find(FIFFB_MNE_PARENT_MRI_FILE);
1171 if (parent_mri.size() == 0) {
1172 t_pStream->close();
1173 qWarning("No parent MRI information in %s", t_pStream->streamName().toUtf8().constData());
1174 return false;
1175 }
1176
1177 MNELIB::MNESourceSpaces t_SourceSpace;
1178 if (!MNELIB::MNESourceSpaces::readFromStream(t_pStream, true, t_SourceSpace)) {
1179 t_pStream->close();
1180 qWarning("Could not read the source spaces");
1181 //ToDo error(me,'Could not read the source spaces (%s)',mne_omit_first_line(lasterr));
1182 return false;
1183 }
1184
1185 for (qint32 k = 0; k < t_SourceSpace.size(); ++k)
1186 t_SourceSpace[k].id = t_SourceSpace[k].find_source_space_hemi();
1187
1188 //
1189 // Bad channel list
1190 //
1191 QStringList bads;
1192 if (bExcludeBads) {
1193 bads = t_pStream->read_bad_channels(t_pStream->dirtree());
1194 if (bads.size() > 0) {
1195 qInfo("\t%lld bad channels ( ", static_cast<long long>(bads.size()));
1196 for (qint32 i = 0; i < bads.size(); ++i)
1197 qInfo("\"%s\" ", bads[i].toUtf8().constData());
1198 qInfo(") read");
1199 }
1200 }
1201
1202 //
1203 // Locate and read the forward solutions
1204 //
1205 FiffTag::UPtr t_pTag;
1206 FiffDirNode::SPtr megnode;
1207 FiffDirNode::SPtr eegnode;
1208 for (qint32 k = 0; k < fwds.size(); ++k) {
1209 if (!fwds[k]->find_tag(t_pStream, FIFF_MNE_INCLUDED_METHODS, t_pTag)) {
1210 t_pStream->close();
1211 qWarning("Methods not listed for one of the forward solutions");
1212 return false;
1213 }
1214 if (*t_pTag->toInt() == FIFFV_MNE_MEG) {
1215 qInfo("MEG solution found");
1216 megnode = fwds[k];
1217 } else if (*t_pTag->toInt() == FIFFV_MNE_EEG) {
1218 qInfo("EEG solution found");
1219 eegnode = fwds[k];
1220 }
1221 }
1222
1223 MNEForwardSolution megfwd;
1224 QString ori;
1225 if (read_one(t_pStream, megnode, megfwd)) {
1226 if (megfwd.source_ori == FIFFV_MNE_FIXED_ORI)
1227 ori = QString("fixed");
1228 else
1229 ori = QString("free");
1230 qInfo("\tRead MEG forward solution (%d sources, %d channels, %s orientations)", megfwd.nsource, megfwd.nchan, ori.toUtf8().constData());
1231 }
1232 MNEForwardSolution eegfwd;
1233 if (read_one(t_pStream, eegnode, eegfwd)) {
1234 if (eegfwd.source_ori == FIFFV_MNE_FIXED_ORI)
1235 ori = QString("fixed");
1236 else
1237 ori = QString("free");
1238 qInfo("\tRead EEG forward solution (%d sources, %d channels, %s orientations)", eegfwd.nsource, eegfwd.nchan, ori.toUtf8().constData());
1239 }
1240
1241 //
1242 // Merge the MEG and EEG solutions together
1243 //
1244 fwd.clear();
1245
1246 if (!megfwd.isEmpty() && !eegfwd.isEmpty()) {
1247 if (megfwd.sol->data.cols() != eegfwd.sol->data.cols() ||
1248 megfwd.source_ori != eegfwd.source_ori ||
1249 megfwd.nsource != eegfwd.nsource ||
1250 megfwd.coord_frame != eegfwd.coord_frame) {
1251 t_pStream->close();
1252 qWarning("The MEG and EEG forward solutions do not match");
1253 return false;
1254 }
1255
1256 fwd = MNEForwardSolution(megfwd);
1257 fwd.sol->data = MatrixXd(megfwd.sol->nrow + eegfwd.sol->nrow, megfwd.sol->ncol);
1258
1259 fwd.sol->data.block(0, 0, megfwd.sol->nrow, megfwd.sol->ncol) = megfwd.sol->data;
1260 fwd.sol->data.block(megfwd.sol->nrow, 0, eegfwd.sol->nrow, eegfwd.sol->ncol) = eegfwd.sol->data;
1261 fwd.sol->nrow = megfwd.sol->nrow + eegfwd.sol->nrow;
1262 fwd.sol->row_names.append(eegfwd.sol->row_names);
1263
1264 if (!fwd.sol_grad->isEmpty()) {
1265 fwd.sol_grad->data.resize(megfwd.sol_grad->data.rows() + eegfwd.sol_grad->data.rows(), megfwd.sol_grad->data.cols());
1266
1267 fwd.sol_grad->data.block(0, 0, megfwd.sol_grad->data.rows(), megfwd.sol_grad->data.cols()) = megfwd.sol_grad->data;
1268 fwd.sol_grad->data.block(megfwd.sol_grad->data.rows(), 0, eegfwd.sol_grad->data.rows(), eegfwd.sol_grad->data.cols()) = eegfwd.sol_grad->data;
1269
1270 fwd.sol_grad->nrow = megfwd.sol_grad->nrow + eegfwd.sol_grad->nrow;
1271 fwd.sol_grad->row_names.append(eegfwd.sol_grad->row_names);
1272 }
1273 fwd.nchan = megfwd.nchan + eegfwd.nchan;
1274 qInfo("\tMEG and EEG forward solutions combined");
1275 } else if (!megfwd.isEmpty())
1276 fwd = std::move(megfwd); //not copied for the sake of speed
1277 else
1278 fwd = std::move(eegfwd); //not copied for the sake of speed
1279
1280 //
1281 // Get the MRI <-> head coordinate transformation
1282 //
1283 if (!parent_mri[0]->find_tag(t_pStream, FIFF_COORD_TRANS, t_pTag)) {
1284 t_pStream->close();
1285 qWarning("MRI/head coordinate transformation not found");
1286 return false;
1287 } else {
1288 fwd.mri_head_t = t_pTag->toCoordTrans();
1289
1293 t_pStream->close();
1294 qWarning("MRI/head coordinate transformation not found");
1295 return false;
1296 }
1297 }
1298 }
1299
1300 //
1301 // get parent MEG info -> from python package
1302 //
1303 t_pStream->read_meas_info_base(t_pStream->dirtree(), fwd.info);
1304
1305 t_pStream->close();
1306
1307 //
1308 // Transform the source spaces to the correct coordinate frame
1309 // if necessary
1310 //
1312 qWarning("Only forward solutions computed in MRI or head coordinates are acceptable");
1313 return false;
1314 }
1315
1316 //
1317 qint32 nuse = 0;
1318 t_SourceSpace.transform_source_space_to(fwd.coord_frame, fwd.mri_head_t);
1319 for (qint32 k = 0; k < t_SourceSpace.size(); ++k)
1320 nuse += t_SourceSpace[k].nuse;
1321
1322 if (nuse != fwd.nsource) {
1323 qWarning("Source spaces do not match the forward solution.");
1324 return false;
1325 }
1326
1327 qInfo("\tSource spaces transformed to the forward solution coordinate frame");
1328 fwd.src = t_SourceSpace; //not new MNESourceSpaces(t_SourceSpace); for sake of speed
1329 //
1330 // Handle the source locations and orientations
1331 //
1332 if (fwd.isFixedOrient() || force_fixed == true) {
1333 // Fixing a free solution uses the average patch normals when available (mne-python use_cps=True).
1334 const bool patchNormals = !fwd.isFixedOrient() && t_SourceSpace.hemisphereAt(0) && t_SourceSpace.hemisphereAt(0)->patch_inds.size() > 0;
1335 nuse = 0;
1336 fwd.source_rr = MatrixXf::Zero(fwd.nsource, 3);
1337 fwd.source_nn = MatrixXf::Zero(fwd.nsource, 3);
1338 for (qint32 k = 0; k < t_SourceSpace.size(); ++k) {
1339 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q) {
1340 fwd.source_rr.row(nuse + q) = t_SourceSpace[k].rr.row(t_SourceSpace[k].vertno(q));
1341 if (patchNormals) {
1342 auto* hemi = t_SourceSpace.hemisphereAt(k);
1343 RowVector3f nn = RowVector3f::Zero();
1344 for (int v : hemi->pinfo[hemi->patch_inds[q]])
1345 nn += t_SourceSpace[k].nn.row(v);
1346 fwd.source_nn.row(nuse + q) = nn.normalized();
1347 } else {
1348 fwd.source_nn.row(nuse + q) = t_SourceSpace[k].nn.row(t_SourceSpace[k].vertno(q));
1349 }
1350 }
1351 nuse += t_SourceSpace[k].nuse;
1352 }
1353 //
1354 // Modify the forward solution for fixed source orientations
1355 //
1356 if (fwd.source_ori != FIFFV_MNE_FIXED_ORI) {
1357 qInfo("\tChanging to fixed-orientation forward solution...");
1358
1359 MatrixXd tmp = fwd.source_nn.transpose().cast<double>();
1360 SparseMatrix<double> fix_rot = Linalg::make_block_diag(tmp, 1);
1361 fwd.sol->data *= fix_rot;
1362 fwd.sol->ncol = fwd.nsource;
1364
1365 if (!fwd.sol_grad->isEmpty()) {
1366 SparseMatrix<double> t_matKron;
1367 SparseMatrix<double> t_eye(3, 3);
1368 for (qint32 i = 0; i < 3; ++i)
1369 t_eye.insert(i, i) = 1.0f;
1370 t_matKron = kroneckerProduct(fix_rot, t_eye); //kron(fix_rot,eye(3));
1371 fwd.sol_grad->data *= t_matKron;
1372 fwd.sol_grad->ncol = 3 * fwd.nsource;
1373 }
1374 qInfo("[done]");
1375 }
1376 } else if (surf_ori) {
1377 fwd.surf_ori = false;
1378 fwd.convert_to_surf_ori();
1379 } else {
1380 qInfo("\tCartesian source orientations...");
1381 nuse = 0;
1382 fwd.source_rr = MatrixXf::Zero(fwd.nsource, 3);
1383 for (qint32 k = 0; k < t_SourceSpace.size(); ++k) {
1384 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1385 fwd.source_rr.block(q + nuse, 0, 1, 3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q), 0, 1, 3);
1386
1387 nuse += t_SourceSpace[k].nuse;
1388 }
1389
1390 MatrixXf t_ones = MatrixXf::Ones(fwd.nsource, 1);
1391 Matrix3f t_eye = Matrix3f::Identity();
1392 fwd.source_nn = kroneckerProduct(t_ones, t_eye);
1393
1394 qInfo("[done]");
1395 }
1396
1397 //
1398 // Do the channel selection
1399 //
1400 QStringList exclude_bads = exclude;
1401 if (bads.size() > 0) {
1402 for (qint32 k = 0; k < bads.size(); ++k)
1403 if (!exclude_bads.contains(bads[k], Qt::CaseInsensitive))
1404 exclude_bads << bads[k];
1405 }
1406
1407 fwd.surf_ori = surf_ori;
1408 fwd = fwd.pick_channels(include, exclude_bads);
1409
1410 //garbage collecting
1411 t_pStream->close();
1412
1413 return true;
1414}
1415
1416//=============================================================================================================
1417
1418bool MNEForwardSolution::read_one(FiffStream::SPtr& p_pStream,
1419 const FiffDirNode::SPtr& p_Node,
1420 MNEForwardSolution& one)
1421{
1422 //
1423 // Read all interesting stuff for one forward solution
1424 //
1425 if (!p_Node)
1426 return false;
1427
1428 one.clear();
1429 FiffTag::UPtr t_pTag;
1430
1431 if (!p_Node->find_tag(p_pStream, FIFF_MNE_SOURCE_ORIENTATION, t_pTag)) {
1432 p_pStream->close();
1433 qWarning("Source orientation tag not found.");
1434 return false;
1435 }
1436
1437 one.source_ori = *t_pTag->toInt();
1438
1439 if (!p_Node->find_tag(p_pStream, FIFF_MNE_COORD_FRAME, t_pTag)) {
1440 p_pStream->close();
1441 qWarning("Coordinate frame tag not found.");
1442 return false;
1443 }
1444
1445 one.coord_frame = *t_pTag->toInt();
1446
1447 if (!p_Node->find_tag(p_pStream, FIFF_MNE_SOURCE_SPACE_NPOINTS, t_pTag)) {
1448 p_pStream->close();
1449 qWarning("Number of sources not found.");
1450 return false;
1451 }
1452
1453 one.nsource = *t_pTag->toInt();
1454
1455 if (!p_Node->find_tag(p_pStream, FIFF_NCHAN, t_pTag)) {
1456 p_pStream->close();
1457 qWarning("Number of channels not found.");
1458 return false;
1459 }
1460
1461 one.nchan = *t_pTag->toInt();
1462
1463 if (p_pStream->read_named_matrix(p_Node, FIFF_MNE_FORWARD_SOLUTION, *one.sol.data()))
1464 one.sol->transpose_named_matrix();
1465 else {
1466 p_pStream->close();
1467 qWarning("Forward solution data not found.");
1468 //error(me,'Forward solution data not found (%s)',mne_omit_first_line(lasterr));
1469 return false;
1470 }
1471
1472 if (p_pStream->read_named_matrix(p_Node, FIFF_MNE_FORWARD_SOLUTION_GRAD, *one.sol_grad.data()))
1473 one.sol_grad->transpose_named_matrix();
1474 else
1475 one.sol_grad->clear();
1476
1477 if (one.sol->data.rows() != one.nchan ||
1478 (one.sol->data.cols() != one.nsource && one.sol->data.cols() != 3 * one.nsource)) {
1479 p_pStream->close();
1480 qWarning("Forward solution matrix has wrong dimensions.");
1481 //error(me,'Forward solution matrix has wrong dimensions');
1482 return false;
1483 }
1484 if (!one.sol_grad->isEmpty()) {
1485 if (one.sol_grad->data.rows() != one.nchan ||
1486 (one.sol_grad->data.cols() != 3 * one.nsource && one.sol_grad->data.cols() != 3 * 3 * one.nsource)) {
1487 p_pStream->close();
1488 qWarning("Forward solution gradient matrix has wrong dimensions.");
1489 //error(me,'Forward solution gradient matrix has wrong dimensions');
1490 }
1491 }
1492 return true;
1493}
1494
1495//=============================================================================================================
1496
1498{
1499 // Figure out which ones have been used
1500 if (info.chs.size() != G.rows()) {
1501 qWarning("Error G.rows() and length of info.chs do not match: %lld != %lld", static_cast<long long>(G.rows()), static_cast<long long>(info.chs.size()));
1502 return;
1503 }
1504
1505 RowVectorXi sel = info.pick_types(QString("grad"));
1506 if (sel.size() > 0) {
1507 for (qint32 i = 0; i < sel.size(); ++i)
1508 G.row(i) = G.row(sel[i]);
1509 G.conservativeResize(sel.size(), G.cols());
1510 qInfo("\t%ld planar channels", sel.size());
1511 } else {
1512 sel = info.pick_types(QString("mag"));
1513 if (sel.size() > 0) {
1514 for (qint32 i = 0; i < sel.size(); ++i)
1515 G.row(i) = G.row(sel[i]);
1516 G.conservativeResize(sel.size(), G.cols());
1517 qInfo("\t%ld magnetometer or axial gradiometer channels", sel.size());
1518 } else {
1519 sel = info.pick_types(false, true);
1520 if (sel.size() > 0) {
1521 for (qint32 i = 0; i < sel.size(); ++i)
1522 G.row(i) = G.row(sel[i]);
1523 G.conservativeResize(sel.size(), G.cols());
1524 qInfo("\t%ld EEG channels", sel.size());
1525 } else
1526 qWarning("Could not find MEG or EEG channels");
1527 }
1528 }
1529}
1530
1531//=============================================================================================================
1532
1534{
1535 if (this->surf_ori || this->isFixedOrient())
1536 return;
1537 qint32 nuse = 0;
1538 //
1539 // Rotate the local source coordinate systems
1540 //
1541 qInfo("\tConverting to surface-based source orientations...");
1542
1543 bool use_ave_nn = false;
1544 auto* hemi0 = src.hemisphereAt(0);
1545 if (hemi0 && hemi0->patch_inds.size() > 0) {
1546 use_ave_nn = true;
1547 qInfo("\tAverage patch normals will be employed in the rotation to the local surface coordinates...");
1548 }
1549
1550 qint32 pp = 0;
1551 this->source_rr = MatrixXf::Zero(this->nsource, 3);
1552 this->source_nn = MatrixXf::Zero(this->nsource * 3, 3);
1553
1554 for (qint32 k = 0; k < src.size(); ++k) {
1555 for (qint32 q = 0; q < src[k].nuse; ++q)
1556 this->source_rr.block(q + nuse, 0, 1, 3) = src[k].rr.block(src[k].vertno(q), 0, 1, 3);
1557
1558 for (qint32 p = 0; p < src[k].nuse; ++p) {
1559 //
1560 // Project out the surface normal and compute SVD
1561 //
1562 Vector3f nn;
1563 if (use_ave_nn) {
1564 auto* hemiK = src.hemisphereAt(k);
1565 VectorXi t_vIdx = hemiK->pinfo[hemiK->patch_inds[p]];
1566 Matrix3Xf t_nn(3, t_vIdx.size());
1567 for (qint32 i = 0; i < t_vIdx.size(); ++i)
1568 t_nn.col(i) = src[k].nn.block(t_vIdx[i], 0, 1, 3).transpose();
1569 nn = t_nn.rowwise().sum();
1570 nn.array() /= nn.norm();
1571 } else
1572 nn = src[k].nn.block(src[k].vertno(p), 0, 1, 3).transpose();
1573
1574 Matrix3f tmp = Matrix3f::Identity(nn.rows(), nn.rows()) - nn * nn.transpose();
1575
1576 JacobiSVD<MatrixXf> t_svd(tmp, Eigen::ComputeThinU);
1577 //Sort singular values and singular vectors
1578 VectorXf t_s = t_svd.singularValues();
1579 MatrixXf U = t_svd.matrixU();
1580 Linalg::sort<float>(t_s, U);
1581
1582 //
1583 // Make sure that ez is in the direction of nn
1584 //
1585 if ((nn.transpose() * U.block(0, 2, 3, 1))(0, 0) < 0)
1586 U *= -1;
1587 this->source_nn.block(pp, 0, 3, 3) = U.transpose();
1588 pp += 3;
1589 }
1590 nuse += src[k].nuse;
1591 }
1592 MatrixXd tmp = this->source_nn.transpose().cast<double>();
1593 SparseMatrix<double> surf_rot = Linalg::make_block_diag(tmp, 3);
1594
1595 this->sol->data *= surf_rot;
1596
1597 if (!this->sol_grad->isEmpty()) {
1598 SparseMatrix<double> t_matKron;
1599 SparseMatrix<double> t_eye(3, 3);
1600 for (qint32 i = 0; i < 3; ++i)
1601 t_eye.insert(i, i) = 1.0f;
1602 t_matKron = kroneckerProduct(surf_rot, t_eye); //kron(surf_rot,eye(3));
1603 this->sol_grad->data *= t_matKron;
1604 }
1605 this->surf_ori = true;
1606 qInfo("[done]");
1607}
1608
1609//=============================================================================================================
1610
1612{
1613 if (!this->surf_ori || this->isFixedOrient()) {
1614 qWarning("Cannot convert to fixed orientation: requires surface-oriented, free-orientation forward solution");
1615 return;
1616 }
1617 qint32 count = 0;
1618 for (qint32 i = 2; i < this->sol->data.cols(); i += 3) {
1619 this->sol->data.col(count) = this->sol->data.col(i);
1620 if (this->source_nn.rows() == this->sol->data.cols())
1621 this->source_nn.row(count) = this->source_nn.row(i);
1622 ++count;
1623 }
1624 this->sol->data.conservativeResize(this->sol->data.rows(), count);
1625 if (this->source_nn.rows() == 3 * count)
1626 this->source_nn.conservativeResize(count, Eigen::NoChange);
1627 this->sol->ncol = this->sol->ncol / 3;
1629 qInfo("\tConverted the forward solution into the fixed-orientation mode.");
1630}
1631
1632//=============================================================================================================
1633
1635{
1636 auto* hemi = src.hemisphereAt(0);
1637 return hemi && hemi->isClustered();
1638}
1639
1640//=============================================================================================================
1641
1642MatrixX3f MNEForwardSolution::getSourcePositionsByLabel(const QList<FsLabel>& lPickedLabels, const FsSurfaceSet& tSurfSetInflated)
1643{
1644 MatrixX3f matSourceVertLeft, matSourceVertRight, matSourcePositions;
1645
1646 if (lPickedLabels.isEmpty()) {
1647 qWarning() << "MNEForwardSolution::getSourcePositionsByLabel - picked label list is empty. Returning.";
1648 return matSourcePositions;
1649 }
1650
1651 if (tSurfSetInflated.isEmpty()) {
1652 qWarning() << "MNEForwardSolution::getSourcePositionsByLabel - tSurfSetInflated is empty. Returning.";
1653 return matSourcePositions;
1654 }
1655
1656 if (isClustered()) {
1657 for (int j = 0; j < this->src[0].vertno.rows(); ++j) {
1658 for (int k = 0; k < lPickedLabels.size(); k++) {
1659 if (this->src[0].vertno(j) == lPickedLabels.at(k).label_id) {
1660 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows() + 1, 3);
1661 matSourceVertLeft.row(matSourceVertLeft.rows() - 1) = tSurfSetInflated[0].rr().row(this->src.hemisphereAt(0)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[0].offset().transpose();
1662 break;
1663 }
1664 }
1665 }
1666
1667 for (int j = 0; j < this->src[1].vertno.rows(); ++j) {
1668 for (int k = 0; k < lPickedLabels.size(); k++) {
1669 if (this->src[1].vertno(j) == lPickedLabels.at(k).label_id) {
1670 matSourceVertRight.conservativeResize(matSourceVertRight.rows() + 1, 3);
1671 matSourceVertRight.row(matSourceVertRight.rows() - 1) = tSurfSetInflated[1].rr().row(this->src.hemisphereAt(1)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[1].offset().transpose();
1672 break;
1673 }
1674 }
1675 }
1676 } else {
1677 for (int j = 0; j < this->src[0].vertno.rows(); ++j) {
1678 for (int k = 0; k < lPickedLabels.size(); k++) {
1679 for (int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1680 if (this->src[0].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 0) {
1681 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows() + 1, 3);
1682 matSourceVertLeft.row(matSourceVertLeft.rows() - 1) = tSurfSetInflated[0].rr().row(this->src[0].vertno(j)) - tSurfSetInflated[0].offset().transpose();
1683 break;
1684 }
1685 }
1686 }
1687 }
1688
1689 for (int j = 0; j < this->src[1].vertno.rows(); ++j) {
1690 for (int k = 0; k < lPickedLabels.size(); k++) {
1691 for (int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1692 if (this->src[1].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 1) {
1693 matSourceVertRight.conservativeResize(matSourceVertRight.rows() + 1, 3);
1694 matSourceVertRight.row(matSourceVertRight.rows() - 1) = tSurfSetInflated[1].rr().row(this->src[1].vertno(j)) - tSurfSetInflated[1].offset().transpose();
1695 break;
1696 }
1697 }
1698 }
1699 }
1700 }
1701
1702 matSourcePositions.resize(matSourceVertLeft.rows() + matSourceVertRight.rows(), 3);
1703 matSourcePositions << matSourceVertLeft, matSourceVertRight;
1704
1705 return matSourcePositions;
1706}
Static MATLAB-style FIFF facade: thin wrapper functions kept for parity with the historical mne-matla...
#define FIFF_MNE_COORD_FRAME
#define FIFFV_EEG_CH
#define FIFF_MNE_FORWARD_SOLUTION_GRAD
#define FIFF_MNE_SOURCE_ORIENTATION
#define FIFF_MNE_FORWARD_SOLUTION
#define FIFF_FAIL
#define FIFFV_REF_MEG_CH
#define FIFFV_MEG_CH
#define FIFF_MNE_INCLUDED_METHODS
#define FIFF_MNE_SOURCE_SPACE_NPOINTS
#define FIFFV_COORD_HEAD
#define FIFFV_MNE_FIXED_ORI
#define FIFFV_COORD_MRI
#define FIFFB_MNE_FORWARD_SOLUTION
#define FIFFB_MNE_PARENT_MEAS_FILE
#define FIFFV_MNE_MEG
#define FIFFB_MNE
#define FIFFV_MNE_ORIENT_PRIOR_COV
#define FIFF_MNE_FILE_NAME
#define FIFFV_MNE_EEG
#define FIFFB_MNE_PARENT_MRI_FILE
#define FIFFV_MNE_DEPTH_PRIOR_COV
#define FIFFV_MNE_FREE_ORI
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
#define FIFF_PARENT_BLOCK_ID
Definition fiff_file.h:326
#define FIFF_NCHAN
Definition fiff_file.h:446
#define FIFF_DIR_POINTER
Definition fiff_file.h:317
#define FIFF_COORD_TRANS
Definition fiff_file.h:468
#define FIFF_PARENT_FILE_ID
Definition fiff_file.h:325
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
constexpr int FAIL
constexpr int Y
constexpr int Z
constexpr int OK
constexpr int X
Static linear-algebra helpers: SVD-based conditioning, block-diagonal assembly, sorted index pairs.
K-means partitional clustering with multiple distance metrics, initialisations and empty-cluster poli...
In-memory representation of a FreeSurfer colour/structure lookup table (FreeSurferColorLUT / embedded...
Reader and in-memory representation of a FreeSurfer/MNE surface label (.label).
Bi-hemispheric grouping of FreeSurfer surfaces (lh + rh) loaded as a single object.
Forward solution (gain matrix mapping source dipoles to sensor measurements).
Core MNE data structures (source spaces, source estimates, hemispheres).
FreeSurfer surface, annotation and parcellation I/O for mne-cpp.
FIFF file I/O, in-memory data structures and high-level readers/writers.
qint32 fiff_int_t
Definition fiff_types.h:86
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
Definition fiff_cov.h:82
fiff_int_t nfree
Definition fiff_cov.h:256
fiff_int_t dim
Definition fiff_cov.h:251
Eigen::MatrixXd eigvec
Definition fiff_cov.h:258
bool isEmpty() const
Definition fiff_cov.h:265
fiff_int_t kind
Definition fiff_cov.h:248
QStringList bads
Definition fiff_cov.h:255
QStringList names
Definition fiff_cov.h:252
Eigen::VectorXd eig
Definition fiff_cov.h:257
Eigen::MatrixXd data
Definition fiff_cov.h:253
FiffCov prepare_noise_cov(const FiffInfo &p_info, const QStringList &p_chNames) const
Definition fiff_cov.cpp:164
QSharedPointer< FiffDirNode > SPtr
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
Definition fiff_info.h:90
FiffInfo pick_info(const Eigen::RowVectorXi &sel=defaultVectorXi) const
static Eigen::RowVectorXi pick_channels(const QStringList &ch_names, const QStringList &include=defaultQStringList, const QStringList &exclude=defaultQStringList)
QList< FiffChInfo > chs
FIFF named matrix: dense / sparse Eigen matrix plus row-name and column-name string lists.
QSharedDataPointer< FiffNamedMatrix > SDPtr
FIFF tag-stream reader/writer: wraps a QIODevice and exposes typed read_* / write_* methods for every...
QSharedPointer< FiffStream > SPtr
static FiffStream::SPtr start_file(QIODevice &p_IODevice)
static FiffStream::SPtr open_update(QIODevice &p_IODevice)
std::unique_ptr< FiffTag > UPtr
Definition fiff_tag.h:165
Single-hemisphere FreeSurfer parcellation: vertex → region label plus embedded colortable.
FsColortable & getColortable()
Container holding the lh and/or rh FsAnnotation for one parcellation atlas.
FreeSurfer colour lookup table: region name + RGBA + packed label, indexed by entry.
QStringList struct_names
Eigen::VectorXi getLabelIds() const
QStringList getNames() const
Container holding the lh and/or rh FsSurface for one subject and one surface kind.
static Eigen::VectorXi sort(Eigen::Matrix< T, Eigen::Dynamic, 1 > &v, bool desc=true)
Definition linalg.h:298
static Eigen::VectorXi intersect(const Eigen::VectorXi &v1, const Eigen::VectorXi &v2, Eigen::VectorXi &idx_sel)
Definition linalg.cpp:160
static Eigen::SparseMatrix< double > make_block_diag(const Eigen::MatrixXd &A, qint32 n)
Definition linalg.cpp:193
QList< Eigen::VectorXd > clusterDistances
QList< QString > clusterLabelNames
QList< qint32 > clusterLabelIds
QList< Eigen::MatrixX3f > clusterSource_rr
QList< Eigen::VectorXi > clusterVertnos
QList< qint32 > centroidVertno
QList< Eigen::Vector3f > centroidSource_rr
Input parameters for cluster-based forward solution computation on a single cortical region.
Eigen::MatrixXd matRoiGWhitened
Eigen::MatrixXd matRoiGOrig
RegionDataOut cluster() const
In-memory representation of an -fwd.fif forward solution.
bool write(QIODevice &p_IODevice) const
static bool read(QIODevice &p_IODevice, MNEForwardSolution &fwd, bool force_fixed=false, bool surf_ori=false, const QStringList &include=FIFFLIB::defaultQStringList, const QStringList &exclude=FIFFLIB::defaultQStringList, bool bExcludeBads=false)
static void restrict_gain_matrix(Eigen::MatrixXd &G, const FIFFLIB::FiffInfo &info)
MNEForwardSolution reduce_forward_solution(qint32 p_iNumDipoles, Eigen::MatrixXd &p_D) const
static FIFFLIB::FiffCov compute_depth_prior(const Eigen::MatrixXd &Gain, const FIFFLIB::FiffInfo &gain_info, bool is_fixed_ori, double exp=0.8, double limit=10.0, const Eigen::MatrixXd &patch_areas=FIFFLIB::defaultConstMatrixXd, bool limit_depth_chs=false)
MNELIB::MNESourceSpaces src
MNEForwardSolution cluster_forward_solution(const FSLIB::FsAnnotationSet &p_AnnotationSet, qint32 p_iClusterSize, Eigen::MatrixXd &p_D=defaultD, const FIFFLIB::FiffCov &p_pNoise_cov=defaultCov, const FIFFLIB::FiffInfo &p_pInfo=defaultInfo, QString p_sMethod="cityblock") const
MNEForwardSolution & operator=(const MNEForwardSolution &other)
void prepare_forward(const FIFFLIB::FiffInfo &p_info, const FIFFLIB::FiffCov &p_noise_cov, bool p_pca, FIFFLIB::FiffInfo &p_outFwdInfo, Eigen::MatrixXd &gain, FIFFLIB::FiffCov &p_outNoiseCov, Eigen::MatrixXd &p_outWhitener, qint32 &p_outNumNonZero) const
FIFFLIB::FiffCoordTrans mri_head_t
Eigen::MatrixX3f getSourcePositionsByLabel(const QList< FSLIB::FsLabel > &lPickedLabels, const FSLIB::FsSurfaceSet &tSurfSetInflated)
MNEForwardSolution pick_channels(const QStringList &include=FIFFLIB::defaultQStringList, const QStringList &exclude=FIFFLIB::defaultQStringList) const
FIFFLIB::FiffNamedMatrix::SDPtr sol_grad
Eigen::VectorXi tripletSelection(const Eigen::VectorXi &p_vecIdxSelection) const
FIFFLIB::FiffCov compute_orient_prior(float loose=0.2)
MNEForwardSolution pick_regions(const QList< FSLIB::FsLabel > &p_qListLabels) const
MNEForwardSolution pick_types(bool meg, bool eeg, const QStringList &include=FIFFLIB::defaultQStringList, const QStringList &exclude=FIFFLIB::defaultQStringList) const
FIFFLIB::FiffNamedMatrix::SDPtr sol
MNEClusterInfo cluster_info
Eigen::VectorXi patch_inds
List of MNESourceSpace objects forming a subject source space.
MNESourceSpaces pick_regions(const QList< FSLIB::FsLabel > &p_qListLabels) const
bool transform_source_space_to(FIFFLIB::fiff_int_t dest, FIFFLIB::FiffCoordTrans &trans)
MNEHemisphere * hemisphereAt(qint32 idx)
static bool readFromStream(FIFFLIB::FiffStream::SPtr &p_pStream, bool add_geom, MNESourceSpaces &p_SourceSpace)