34#include <Eigen/Sparse>
35#include <unsupported/Eigen/KroneckerProduct>
46#include <QtConcurrent>
48#include <QRegularExpression>
64bool check_matching_chnames_conventions(
const QStringList& chNamesA,
const QStringList& chNamesB,
bool bCheckForNewNamingConvention =
false)
66 bool bMatching =
false;
68 if(chNamesA.isEmpty()) {
69 qWarning(
"Warning in check_matching_chnames_conventions - chNamesA list is empty. Nothing to compare");
71 if(chNamesB.isEmpty()) {
72 qWarning(
"Warning in check_matching_chnames_conventions - chNamesB list is empty. Nothing to compare");
75 QString replaceStringOldConv, replaceStringNewConv;
77 for(
int i = 0; i < chNamesA.size(); ++i) {
78 if(chNamesB.contains(chNamesA.at(i))) {
80 }
else if(bCheckForNewNamingConvention) {
81 replaceStringNewConv = chNamesA.at(i);
82 replaceStringNewConv.replace(
" ",
"");
84 if(chNamesB.contains(replaceStringNewConv)) {
87 QRegularExpression xRegExp(
"[0-9]{1,100}");
88 QRegularExpressionMatch match = xRegExp.match(chNamesA.at(i));
89 QStringList xList = match.capturedTexts();
91 for(
int k = 0; k < xList.size(); ++k) {
92 replaceStringOldConv = chNamesA.at(i);
93 replaceStringOldConv.replace(xList.at(k),QString(
"%1%2").arg(
" ").arg(xList.at(k)));
95 if(chNamesB.contains(replaceStringNewConv) || chNamesB.contains(replaceStringOldConv)) {
151 if(!
read(p_IODevice, *
this, force_fixed,
surf_ori, include, exclude, bExcludeBads))
153 qWarning(
"\tForward solution not found.");
167,
sol(p_MNEForwardSolution.
sol)
172,
src(p_MNEForwardSolution.
src)
182 if (
this != &other) {
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;
242 int nmeg = megIdx.size();
243 int neeg = eegIdx.size();
249 for (
int k = 0; k <
src.size(); ++k)
250 nvert +=
src[k].nuse;
279 if (!
info.filename.isEmpty())
281 if (!
info.meas_id.isEmpty())
283 t_pStream->write_coord_trans(
info.dev_head_t);
285 int totalChan = nmeg + neeg;
289 QList<FiffChInfo> allChs;
290 for (
int k = 0; k < nmeg; ++k)
291 allChs.append(
info.chs[megIdx[k]]);
292 for (
int k = 0; k < neeg; ++k)
293 allChs.append(
info.chs[eegIdx[k]]);
294 for (
int p = 0; p < allChs.size(); ++p) {
295 allChs[p].scanNo = p + 1;
296 t_pStream->write_ch_info(allChs[p]);
299 t_pStream->write_bad_channels(
info.bads);
307 for (
int k = 0; k <
src.size(); ++k) {
308 if (
src[k].writeToStream(t_pStream,
false) ==
FIFF_FAIL) {
320 int nRows = rowIdx.size();
321 int nCols = combined.
ncol;
322 MatrixXd data(nRows, nCols);
323 QStringList row_names;
324 for (
int r = 0; r < nRows; ++r) {
325 data.row(r) = combined.
data.row(rowIdx[r]);
326 row_names.append(combined.
row_names[rowIdx[r]]);
391 t_pStream->end_file();
398 if (
auto* qf =
dynamic_cast<QFile*
>(&p_IODevice)) {
399 QFile fileIn(qf->fileName());
402 const auto& dir = t_pStreamIn->dir();
403 for (
int i = 0; i < dir.size(); ++i) {
405 fiff_int_t dirpos = (fiff_int_t)t_pStreamIn->write_dir_entries(dir);
407 t_pStreamIn->write_dir_pointer(dirpos, dir[i]->pos);
411 t_pStreamIn->close();
421 qint32 p_iClusterSize,
425 QString p_sMethod)
const
427 qInfo(
"Cluster forward solution using %s.", p_sMethod.toUtf8().constData());
432 if(!check_matching_chnames_conventions(p_pNoise_cov.
names, p_pInfo.
ch_names) && !p_pNoise_cov.
names.isEmpty() && !p_pInfo.
ch_names.isEmpty()) {
433 if(check_matching_chnames_conventions(p_pNoise_cov.
names, p_pInfo.
ch_names,
true)) {
434 qWarning(
"MNEForwardSolution::cluster_forward_solution - Cov names do match with info channel names but have a different naming convention.");
437 qWarning(
"MNEForwardSolution::cluster_forward_solution - Cov channel names do not match with info channel names.");
447 qWarning(
"Error: Fixed orientation not implemented yet!");
451 MatrixXd t_G_Whitened(0,0);
452 bool t_bUseWhitened =
false;
460 MatrixXd p_outWhitener;
461 qint32 p_outNumNonZero;
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.");
466 t_G_Whitened = p_outWhitener*t_G_Whitened;
467 t_bUseWhitened =
true;
478 for(qint32 h = 0; h < this->
src.size(); ++h )
486 for(qint32 j = 0; j < h; ++j)
487 offset += this->
src[j].nuse;
490 qInfo(
"Cluster Left Hemisphere");
492 qInfo(
"Cluster Right Hemisphere");
496 VectorXi label_ids = t_CurrentColorTable.
getLabelIds();
499 VectorXi vertno_labeled = VectorXi::Zero(this->
src[h].vertno.rows());
502 for(qint32 i = 0; i < vertno_labeled.rows(); ++i)
503 vertno_labeled[i] = p_AnnotationSet[h].getLabelIds()[this->
src[h].vertno[i]];
505 std::vector<RegionData> regionDataIn;
510 for (qint32 i = 0; i < label_ids.rows(); ++i)
512 if (label_ids[i] != 0)
514 QString curr_name = t_CurrentColorTable.
struct_names[i];
515 qInfo(
"\tCluster %d / %ld %s...", i+1, label_ids.rows(), curr_name.toUtf8().constData());
520 VectorXi idcs = VectorXi::Zero(vertno_labeled.rows());
524 for(qint32 j = 0; j < vertno_labeled.rows(); ++j)
526 if(vertno_labeled[j] == label_ids[i])
532 idcs.conservativeResize(c);
535 MatrixXd t_G(this->
sol->data.rows(), idcs.rows()*3);
536 MatrixXd t_G_Whitened_Roi(t_G_Whitened.rows(), idcs.rows()*3);
538 for(qint32 j = 0; j < idcs.rows(); ++j)
540 t_G.block(0, j*3, t_G.rows(), 3) = this->
sol->data.block(0, (idcs[j]+offset)*3, t_G.rows(), 3);
542 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);
545 qint32 nSens = t_G.rows();
546 qint32 nSources = t_G.cols()/3;
554 t_sensG.
nClusters =
static_cast<int>(ceil(
static_cast<double>(nSources) /
static_cast<double>(p_iClusterSize)));
558 qInfo(
"%d Cluster(s)...", t_sensG.
nClusters);
561 t_sensG.
matRoiG = MatrixXd(t_G.cols()/3, 3*nSens);
563 t_sensG.
matRoiGWhitened = MatrixXd(t_G_Whitened_Roi.cols()/3, 3*nSens);
565 for(qint32 j = 0; j < nSens; ++j)
567 for(qint32 k = 0; k < t_sensG.
matRoiG.rows(); ++k)
568 t_sensG.
matRoiG.block(k,j*3,1,3) = t_G.block(j,k*3,1,3);
571 t_sensG.
matRoiGWhitened.block(k,j*3,1,3) = t_G_Whitened_Roi.block(j,k*3,1,3);
578 regionDataIn.push_back(std::move(t_sensG));
584 qWarning(
"failed! FsLabel contains no sources.");
592 qInfo(
"Clustering...");
593 QFuture< RegionDataOut > res;
595 res.waitForFinished();
600 MatrixXd t_G_partial;
604 auto itIn = regionDataIn.cbegin();
605 QFuture<RegionDataOut>::const_iterator itOut;
606 for (itOut = res.constBegin(); itOut != res.constEnd(); ++itOut)
608 nClusters = itOut->ctrs.rows();
609 nSens = itOut->ctrs.cols()/3;
610 t_G_partial = MatrixXd::Zero(nSens, nClusters*3);
616 for(qint32 j = 0; j < nSens; ++j)
617 for(qint32 k = 0; k < nClusters; ++k)
618 t_G_partial.block(j, k*3, 1, 3) = itOut->ctrs.block(k,j*3,1,3);
623 for(qint32 j = 0; j < nClusters; ++j)
625 VectorXi clusterIdcs = VectorXi::Zero(itOut->roiIdx.rows());
626 VectorXd clusterDistance = VectorXd::Zero(itOut->roiIdx.rows());
627 MatrixX3f clusterSource_rr = MatrixX3f::Zero(itOut->roiIdx.rows(), 3);
628 qint32 nClusterIdcs = 0;
629 for(qint32 k = 0; k < itOut->roiIdx.rows(); ++k)
631 if(itOut->roiIdx[k] == j)
633 clusterIdcs[nClusterIdcs] = itIn->idcs[k];
635 qint32 offset = h == 0 ? 0 : this->
src[0].nuse;
636 clusterSource_rr.row(nClusterIdcs) = this->
source_rr.row(offset + itIn->idcs[k]);
637 clusterDistance[nClusterIdcs] = itOut->D(k,j);
641 clusterIdcs.conservativeResize(nClusterIdcs);
642 clusterSource_rr.conservativeResize(nClusterIdcs,3);
643 clusterDistance.conservativeResize(nClusterIdcs);
645 VectorXi clusterVertnos = VectorXi::Zero(clusterIdcs.size());
646 for(qint32 k = 0; k < clusterVertnos.size(); ++k)
647 clusterVertnos(k) = this->
src[h].vertno[clusterIdcs(k)];
659 if(t_G_partial.rows() > 0 && t_G_partial.cols() > 0)
661 t_G_new.conservativeResize(t_G_partial.rows(), t_G_new.cols() + t_G_partial.cols());
662 t_G_new.block(0, t_G_new.cols() - t_G_partial.cols(), t_G_new.rows(), t_G_partial.cols()) = t_G_partial;
665 for(qint32 k = 0; k < nClusters; ++k)
669 double 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());
670 double sqec_min = sqec;
672 for(qint32 j = 1; j < itIn->idcs.rows(); ++j)
674 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());
684 qint32 sel_idx = itIn->idcs[j_min];
701 p_fwdOut.
src[h].vertno.conservativeResize(count);
709 qint32 totalNumOfClust = 0;
710 for (qint32 h = 0; h < 2; ++h)
714 p_D = MatrixXd::Zero(this->
sol->data.cols(), totalNumOfClust);
716 p_D = MatrixXd::Zero(this->
sol->data.cols(), totalNumOfClust*3);
718 QList<VectorXi> t_vertnos = this->
src.get_vertno();
720 qint32 currentCluster = 0;
721 for (qint32 h = 0; h < 2; ++h)
723 int hemiOffset = h == 0 ? 0 : t_vertnos[0].size();
724 for(qint32 i = 0; i < p_fwdOut.
src.
hemisphereAt(h)->cluster_info.clusterVertnos.size(); ++i)
729 idx_sel.array() += hemiOffset;
731 double selectWeight = 1.0/idx_sel.size();
734 for(qint32 j = 0; j < idx_sel.size(); ++j)
735 p_D.col(currentCluster)[idx_sel(j)] = selectWeight;
739 qint32 clustOffset = currentCluster*3;
740 for(qint32 j = 0; j < idx_sel.size(); ++j)
742 qint32 idx_sel_Offset = idx_sel(j)*3;
744 p_D(idx_sel_Offset,clustOffset) = selectWeight;
746 p_D(idx_sel_Offset+1, clustOffset+1) = selectWeight;
748 p_D(idx_sel_Offset+2, clustOffset+2) = selectWeight;
758 p_fwdOut.
sol->data = t_G_new;
759 p_fwdOut.
sol->ncol = t_G_new.cols();
773 qint32 np = isFixed ? p_fwdOut.
sol->data.cols() : p_fwdOut.
sol->data.cols()/3;
775 if(p_iNumDipoles > np)
778 VectorXi sel(p_iNumDipoles);
780 float t_fStep =
static_cast<float>(np) /
static_cast<float>(p_iNumDipoles);
782 for(qint32 i = 0; i < p_iNumDipoles; ++i)
784 float t_fCurrent =
static_cast<float>(i) * t_fStep;
785 sel[i] = (quint32)floor(t_fCurrent);
790 p_D = MatrixXd::Zero(p_fwdOut.
sol->data.cols(), p_iNumDipoles);
791 for(qint32 i = 0; i < p_iNumDipoles; ++i)
796 p_D = MatrixXd::Zero(p_fwdOut.
sol->data.cols(), p_iNumDipoles*3);
797 for(qint32 i = 0; i < p_iNumDipoles; ++i)
798 for(qint32 j = 0; j < 3; ++j)
799 p_D((sel[i]*3)+j, (i*3)+j) = 1;
803 p_fwdOut.
sol->data = this->
sol->data * p_D;
805 MatrixX3f rr(p_iNumDipoles,3);
807 MatrixX3f nn(p_iNumDipoles,3);
809 for(qint32 i = 0; i < p_iNumDipoles; ++i)
818 p_fwdOut.
sol->ncol = p_fwdOut.
sol->data.cols();
820 p_fwdOut.
nsource = p_iNumDipoles;
829 qInfo(
"\tCreating the depth weighting matrix...");
840 d = (G.array().square()).rowwise().sum();
844 qint32 n_pos = G.cols() / 3;
845 d = VectorXd::Zero(n_pos);
847 for (qint32 k = 0; k < n_pos; ++k)
849 Gk = G.block(0,3*k, G.rows(), 3);
850 JacobiSVD<MatrixXd>
svd(Gk.transpose()*Gk);
851 d[k] =
svd.singularValues().maxCoeff();
856 if(patch_areas.cols() > 0)
858 qWarning(
"\tToDo!!!!! >>> Patch areas taken into account in the depth weighting");
862 VectorXd w = d.cwiseInverse();
866 double weight_limit = pow(limit, 2);
867 if (!limit_depth_chs)
873 limit = ws[ind] * weight_limit;
878 limit = ws[ws.size()-1];
881 if (ws[ws.size()-1] > weight_limit * ws[0])
883 double th = weight_limit * ws[0];
884 for(qint32 i = 0; i < ws.size(); ++i)
897 qInfo(
"\tlimit = %d/%ld = %f", n_limit + 1, d.size(), sqrt(limit / ws[0]));
898 double scale = 1.0 / limit;
899 qInfo(
"\tscale = %g exp = %g", scale, exp);
901 VectorXd t_w = w.array() / limit;
902 for(qint32 i = 0; i < t_w.size(); ++i)
903 t_w[i] = t_w[i] > 1 ? 1 : t_w[i];
904 wpp = t_w.array().pow(exp);
908 depth_prior.
data = wpp;
911 depth_prior.
data.resize(wpp.rows()*3, 1);
914 for(qint32 i = 0; i < wpp.rows(); ++i)
918 depth_prior.
data(idx, 0) = v;
919 depth_prior.
data(idx+1, 0) = v;
920 depth_prior.
data(idx+2, 0) = v;
925 depth_prior.
diag =
true;
926 depth_prior.
dim = depth_prior.
data.rows();
927 depth_prior.
nfree = 1;
937 qint32 n_sources = this->
sol->data.cols();
939 if (0 <= loose && loose <= 1)
941 qDebug() <<
"this->surf_ori" << this->
surf_ori;
944 qWarning(
"\tForward operator is not oriented in surface coordinates. loose parameter should be None not %f.", loose);
946 qInfo(
"\tSetting loose to %f.", loose);
951 qInfo(
"\tIgnoring loose parameter with forward operator with fixed orientation.");
957 if(loose < 0 || loose > 1)
959 qWarning(
"Warning: Loose value should be in interval [0,1] not %f.\n", loose);
960 loose = loose > 1 ? 1 : 0;
961 qInfo(
"Setting loose to %f.", loose);
966 orient_prior.
data = VectorXd::Ones(n_sources);
967 if(!is_fixed_ori && (0 <= loose && loose <= 1))
969 qInfo(
"\tApplying loose dipole orientations. Loose value of %f.", loose);
970 for(qint32 i = 0; i < n_sources; i+=3)
971 orient_prior.
data.block(i,0,2,1).array() *= loose;
974 orient_prior.
diag =
true;
975 orient_prior.
dim = orient_prior.
data.size();
976 orient_prior.
nfree = 1;
984 const QStringList& exclude)
const
988 if(include.size() == 0 && exclude.size() == 0)
994 quint32 nuse = sel.size();
998 qInfo(
"Nothing remains after picking. Returning original forward solution.");
1001 qInfo(
"\t%d out of %d channels remain after picking", nuse, fwd.
nchan);
1004 MatrixXd newData(nuse, fwd.
sol->data.cols());
1005 for(quint32 i = 0; i < nuse; ++i)
1006 newData.row(i) = fwd.
sol->data.row(sel[i]);
1008 fwd.
sol->data = newData;
1009 fwd.
sol->nrow = nuse;
1011 QStringList ch_names;
1012 for(qint32 i = 0; i < sel.cols(); ++i)
1013 ch_names << fwd.
sol->row_names[sel(i)];
1015 fwd.
sol->row_names = ch_names;
1017 QList<FiffChInfo> chs;
1018 for(qint32 i = 0; i < sel.cols(); ++i)
1019 chs.append(fwd.
info.
chs[sel(i)]);
1024 for(qint32 i = 0; i < fwd.
info.
bads.size(); ++i)
1025 if(ch_names.contains(fwd.
info.
bads[i]))
1031 newData.resize(nuse, fwd.
sol_grad->data.cols());
1032 for(quint32 i = 0; i < nuse; ++i)
1033 newData.row(i) = fwd.
sol_grad->data.row(sel[i]);
1036 QStringList row_names;
1037 for(qint32 i = 0; i < sel.cols(); ++i)
1038 row_names << fwd.
sol_grad->row_names[sel(i)];
1039 fwd.
sol_grad->row_names = row_names;
1049 VectorXi selVertices;
1052 for(qint32 i = 0; i < p_qListLabels.size(); ++i)
1054 VectorXi currentSelection;
1055 this->
src.label_src_vertno_sel(p_qListLabels[i], currentSelection);
1057 selVertices.conservativeResize(iSize+currentSelection.size());
1058 selVertices.block(iSize,0,currentSelection.size(),1) = currentSelection;
1059 iSize = selVertices.size();
1066 MatrixX3f rr(selVertices.size(),3);
1067 MatrixX3f nn(selVertices.size(),3);
1069 for(qint32 i = 0; i < selVertices.size(); ++i)
1071 rr.block(i, 0, 1, 3) = selectedFwd.
source_rr.row(selVertices[i]);
1072 nn.block(i, 0, 1, 3) = selectedFwd.
source_nn.row(selVertices[i]);
1079 MatrixXd G(selectedFwd.
sol->data.rows(),selSolIdcs.size());
1080 qint32 rows = G.rows();
1082 for(qint32 i = 0; i < selSolIdcs.size(); ++i)
1083 G.block(0, i, rows, 1) = selectedFwd.
sol->data.col(selSolIdcs[i]);
1085 selectedFwd.
sol->data = G;
1086 selectedFwd.
sol->nrow = selectedFwd.
sol->data.rows();
1087 selectedFwd.
sol->ncol = selectedFwd.
sol->data.cols();
1088 selectedFwd.
nsource = selectedFwd.
sol->ncol / 3;
1099 RowVectorXi sel =
info.pick_types(meg, eeg,
false, include, exclude);
1101 QStringList include_ch_names;
1102 for(qint32 i = 0; i < sel.cols(); ++i)
1103 include_ch_names <<
info.ch_names[sel[i]];
1116 MatrixXd &p_outWhitener,
1117 qint32 &p_outNumNonZero)
const
1119 QStringList fwd_ch_names, ch_names;
1120 for(qint32 i = 0; i < this->
info.chs.size(); ++i)
1121 fwd_ch_names << this->
info.chs[i].ch_name;
1124 for(qint32 i = 0; i < p_info.
chs.size(); ++i)
1125 if(!p_info.
bads.contains(p_info.
chs[i].ch_name)
1126 && !p_noise_cov.
bads.contains(p_info.
chs[i].ch_name)
1127 && p_noise_cov.
names.contains(p_info.
chs[i].ch_name)
1128 && fwd_ch_names.contains(p_info.
chs[i].ch_name))
1129 ch_names << p_info.
chs[i].ch_name;
1131 qint32 n_chan = ch_names.size();
1132 qInfo(
"Computing inverse operator with %d channels.", n_chan);
1140 p_outNumNonZero = 0;
1141 VectorXi t_vecNonZero = VectorXi::Zero(n_chan);
1142 for(qint32 i = 0; i < p_outNoiseCov.
eig.rows(); ++i)
1144 if(p_outNoiseCov.
eig[i] > 0)
1146 t_vecNonZero[p_outNumNonZero] = i;
1150 if(p_outNumNonZero > 0)
1151 t_vecNonZero.conservativeResize(p_outNumNonZero);
1153 if(p_outNumNonZero > 0)
1157 qWarning(
"Warning in MNEForwardSolution::prepare_forward: if (p_pca) havent been debugged.");
1158 p_outWhitener = MatrixXd::Zero(n_chan, p_outNumNonZero);
1160 for(qint32 i = 0; i < p_outNumNonZero; ++i)
1161 p_outWhitener.col(t_vecNonZero[i]) = p_outNoiseCov.
eigvec.col(t_vecNonZero[i]).array() / sqrt(p_outNoiseCov.
eig(t_vecNonZero[i]));
1162 qInfo(
"\tReducing data rank to %d.", p_outNumNonZero);
1166 qInfo(
"Creating non pca whitener.");
1167 p_outWhitener = MatrixXd::Zero(n_chan, n_chan);
1168 for(qint32 i = 0; i < p_outNumNonZero; ++i)
1169 p_outWhitener(t_vecNonZero[i],t_vecNonZero[i]) = 1.0 / sqrt(p_outNoiseCov.
eig(t_vecNonZero[i]));
1171 p_outWhitener *= p_outNoiseCov.
eigvec;
1175 VectorXi fwd_idx = VectorXi::Zero(ch_names.size());
1176 VectorXi info_idx = VectorXi::Zero(ch_names.size());
1178 qint32 count_fwd_idx = 0;
1179 qint32 count_info_idx = 0;
1180 for(qint32 i = 0; i < ch_names.size(); ++i)
1182 idx = fwd_ch_names.indexOf(ch_names[i]);
1185 fwd_idx[count_fwd_idx] = idx;
1188 idx = p_info.
ch_names.indexOf(ch_names[i]);
1191 info_idx[count_info_idx] = idx;
1195 fwd_idx.conservativeResize(count_fwd_idx);
1196 info_idx.conservativeResize(count_info_idx);
1198 gain.resize(count_fwd_idx, this->
sol->data.cols());
1199 for(qint32 i = 0; i < count_fwd_idx; ++i)
1200 gain.row(i) = this->
sol->data.row(fwd_idx[i]);
1202 p_outFwdInfo = p_info.
pick_info(info_idx);
1204 qInfo(
"\tTotal rank is %d", p_outNumNonZero);
1213 const QStringList& include,
1214 const QStringList& exclude,
1219 qInfo(
"Reading forward solution from %s...", t_pStream->streamName().toUtf8().constData());
1220 if(!t_pStream->open())
1227 if (fwds.size() == 0)
1230 qWarning(
"No forward solutions in %s", t_pStream->streamName().toUtf8().constData());
1237 if (parent_mri.size() == 0)
1240 qWarning(
"No parent MRI information in %s", t_pStream->streamName().toUtf8().constData());
1248 qWarning(
"Could not read the source spaces");
1253 for(qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1254 t_SourceSpace[k].
id = t_SourceSpace[k].find_source_space_hemi();
1262 bads = t_pStream->read_bad_channels(t_pStream->dirtree());
1265 qInfo(
"\t%lld bad channels ( ", bads.size());
1266 for(qint32 i = 0; i < bads.size(); ++i)
1267 qInfo(
"\"%s\" ", bads[i].toUtf8().constData());
1278 for(qint32 k = 0; k < fwds.size(); ++k)
1283 qWarning(
"Methods not listed for one of the forward solutions");
1288 qInfo(
"MEG solution found");
1293 qInfo(
"EEG solution found");
1300 if (read_one(t_pStream, megnode, megfwd))
1303 ori = QString(
"fixed");
1305 ori = QString(
"free");
1306 qInfo(
"\tRead MEG forward solution (%d sources, %d channels, %s orientations)", megfwd.
nsource,megfwd.
nchan,ori.toUtf8().constData());
1309 if (read_one(t_pStream, eegnode, eegfwd))
1312 ori = QString(
"fixed");
1314 ori = QString(
"free");
1315 qInfo(
"\tRead EEG forward solution (%d sources, %d channels, %s orientations)", eegfwd.
nsource,eegfwd.
nchan,ori.toUtf8().constData());
1325 if (megfwd.
sol->data.cols() != eegfwd.
sol->data.cols() ||
1331 qWarning(
"The MEG and EEG forward solutions do not match");
1336 fwd.
sol->data = MatrixXd(megfwd.
sol->nrow + eegfwd.
sol->nrow, megfwd.
sol->ncol);
1338 fwd.
sol->data.block(0,0,megfwd.
sol->nrow,megfwd.
sol->ncol) = megfwd.
sol->data;
1339 fwd.
sol->data.block(megfwd.
sol->nrow,0,eegfwd.
sol->nrow,eegfwd.
sol->ncol) = eegfwd.
sol->data;
1340 fwd.
sol->nrow = megfwd.
sol->nrow + eegfwd.
sol->nrow;
1341 fwd.
sol->row_names.append(eegfwd.
sol->row_names);
1354 qInfo(
"\tMEG and EEG forward solutions combined");
1357 fwd = std::move(megfwd);
1359 fwd = std::move(eegfwd);
1367 qWarning(
"MRI/head coordinate transformation not found");
1380 qWarning(
"MRI/head coordinate transformation not found");
1389 t_pStream->read_meas_info_base(t_pStream->dirtree(), fwd.
info);
1399 qWarning(
"Only forward solutions computed in MRI or head coordinates are acceptable");
1406 for(qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1407 nuse += t_SourceSpace[k].nuse;
1410 qDebug() <<
"Source spaces do not match the forward solution.\n";
1414 qInfo(
"\tSource spaces transformed to the forward solution coordinate frame");
1415 fwd.
src = t_SourceSpace;
1424 for(qint32 k = 0; k < t_SourceSpace.
size();++k)
1426 for(qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1428 fwd.
source_rr.block(q,0,1,3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q),0,1,3);
1429 fwd.
source_nn.block(q,0,1,3) = t_SourceSpace[k].nn.block(t_SourceSpace[k].vertno(q),0,1,3);
1431 nuse += t_SourceSpace[k].nuse;
1438 qInfo(
"\tChanging to fixed-orientation forward solution...");
1440 MatrixXd tmp = fwd.
source_nn.transpose().cast<
double>();
1442 fwd.
sol->data *= fix_rot;
1448 SparseMatrix<double> t_matKron;
1449 SparseMatrix<double> t_eye(3,3);
1450 for (qint32 i = 0; i < 3; ++i)
1451 t_eye.insert(i,i) = 1.0f;
1452 t_matKron = kroneckerProduct(fix_rot,t_eye);
1464 qInfo(
"\tConverting to surface-based source orientations...");
1466 bool use_ave_nn =
false;
1468 if(hemi0 && hemi0->patch_inds.size() > 0)
1471 qInfo(
"\tAverage patch normals will be employed in the rotation to the local surface coordinates...");
1479 qWarning(
"Warning source_ori: Rotating the source coordinate system haven't been verified --> Singular Vectors U are different from MATLAB!");
1481 for(qint32 k = 0; k < t_SourceSpace.
size();++k)
1484 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1485 fwd.
source_rr.block(q+nuse,0,1,3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q),0,1,3);
1487 for (qint32 p = 0; p < t_SourceSpace[k].nuse; ++p)
1496 VectorXi t_vIdx = hemiK->
pinfo[hemiK->patch_inds[p]];
1497 Matrix3Xf t_nn(3, t_vIdx.size());
1498 for(qint32 i = 0; i < t_vIdx.size(); ++i)
1499 t_nn.col(i) = t_SourceSpace[k].nn.block(t_vIdx[i],0,1,3).transpose();
1500 nn = t_nn.rowwise().sum();
1501 nn.array() /= nn.norm();
1504 nn = t_SourceSpace[k].nn.block(t_SourceSpace[k].vertno(p),0,1,3).transpose();
1506 Matrix3f tmp = Matrix3f::Identity(nn.rows(), nn.rows()) - nn*nn.transpose();
1508 JacobiSVD<MatrixXf> t_svd(tmp, Eigen::ComputeThinU);
1510 VectorXf t_s = t_svd.singularValues();
1511 MatrixXf U = t_svd.matrixU();
1517 if ((nn.transpose() * U.block(0,2,3,1))(0,0) < 0)
1519 fwd.
source_nn.block(pp, 0, 3, 3) = U.transpose();
1522 nuse += t_SourceSpace[k].nuse;
1524 MatrixXd tmp = fwd.
source_nn.transpose().cast<
double>();
1527 fwd.
sol->data *= surf_rot;
1531 SparseMatrix<double> t_matKron;
1532 SparseMatrix<double> t_eye(3,3);
1533 for (qint32 i = 0; i < 3; ++i)
1534 t_eye.insert(i,i) = 1.0f;
1535 t_matKron = kroneckerProduct(surf_rot,t_eye);
1542 qInfo(
"\tCartesian source orientations...");
1545 for(qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1547 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1548 fwd.
source_rr.block(q+nuse,0,1,3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q),0,1,3);
1550 nuse += t_SourceSpace[k].nuse;
1553 MatrixXf t_ones = MatrixXf::Ones(fwd.
nsource,1);
1554 Matrix3f t_eye = Matrix3f::Identity();
1555 fwd.
source_nn = kroneckerProduct(t_ones,t_eye);
1563 QStringList exclude_bads = exclude;
1564 if (bads.size() > 0)
1566 for(qint32 k = 0; k < bads.size(); ++k)
1567 if(!exclude_bads.contains(bads[k],Qt::CaseInsensitive))
1568 exclude_bads << bads[k];
1598 qWarning(
"Source orientation tag not found.");
1607 qWarning(
"Coordinate frame tag not found.");
1616 qWarning(
"Number of sources not found.");
1620 one.
nsource = *t_pTag->toInt();
1622 if(!p_Node->find_tag(p_pStream,
FIFF_NCHAN, t_pTag))
1625 qWarning(
"Number of channels not found.");
1629 one.
nchan = *t_pTag->toInt();
1632 one.
sol->transpose_named_matrix();
1636 qWarning(
"Forward solution data not found.");
1642 one.
sol_grad->transpose_named_matrix();
1646 if (one.
sol->data.rows() != one.
nchan ||
1650 qWarning(
"Forward solution matrix has wrong dimensions.");
1660 qWarning(
"Forward solution gradient matrix has wrong dimensions.");
1672 if(
info.chs.size() != G.rows())
1674 qWarning(
"Error G.rows() and length of info.chs do not match: %ld != %lli", G.rows(),
info.chs.size());
1678 RowVectorXi sel =
info.pick_types(QString(
"grad"));
1681 for(qint32 i = 0; i < sel.size(); ++i)
1682 G.row(i) = G.row(sel[i]);
1683 G.conservativeResize(sel.size(), G.cols());
1684 qInfo(
"\t%ld planar channels", sel.size());
1688 sel =
info.pick_types(QString(
"mag"));
1691 for(qint32 i = 0; i < sel.size(); ++i)
1692 G.row(i) = G.row(sel[i]);
1693 G.conservativeResize(sel.size(), G.cols());
1694 qInfo(
"\t%ld magnetometer or axial gradiometer channels", sel.size());
1698 sel =
info.pick_types(
false,
true);
1701 for(qint32 i = 0; i < sel.size(); ++i)
1702 G.row(i) = G.row(sel[i]);
1703 G.conservativeResize(sel.size(), G.cols());
1704 qInfo(
"\t%ld EEG channels", sel.size());
1707 qWarning(
"Could not find MEG or EEG channels");
1718 qWarning(
"Cannot convert to fixed orientation: requires surface-oriented, free-orientation forward solution");
1722 for(qint32 i = 2; i < this->
sol->data.cols(); i += 3)
1723 this->
sol->data.col(count) = this->
sol->data.col(i);
1724 this->
sol->data.conservativeResize(this->
sol->data.rows(), count);
1725 this->
sol->ncol = this->
sol->ncol / 3;
1727 qInfo(
"\tConverted the forward solution into the fixed-orientation mode.");
1734 auto* hemi =
src.hemisphereAt(0);
1735 return hemi && hemi->isClustered();
1742 MatrixX3f matSourceVertLeft, matSourceVertRight, matSourcePositions;
1744 if(lPickedLabels.isEmpty()) {
1745 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - picked label list is empty. Returning.";
1746 return matSourcePositions;
1749 if(tSurfSetInflated.
isEmpty()) {
1750 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - tSurfSetInflated is empty. Returning.";
1751 return matSourcePositions;
1755 for(
int j = 0; j < this->
src[0].vertno.rows(); ++j) {
1756 for(
int k = 0; k < lPickedLabels.size(); k++) {
1757 if(this->
src[0].vertno(j) == lPickedLabels.at(k).label_id) {
1758 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows()+1,3);
1759 matSourceVertLeft.row(matSourceVertLeft.rows()-1) = tSurfSetInflated[0].rr().row(this->
src.hemisphereAt(0)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[0].offset().transpose();
1765 for(
int j = 0; j < this->
src[1].vertno.rows(); ++j) {
1766 for(
int k = 0; k < lPickedLabels.size(); k++) {
1767 if(this->
src[1].vertno(j) == lPickedLabels.at(k).label_id) {
1768 matSourceVertRight.conservativeResize(matSourceVertRight.rows()+1,3);
1769 matSourceVertRight.row(matSourceVertRight.rows()-1) = tSurfSetInflated[1].rr().row(this->
src.hemisphereAt(1)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[1].offset().transpose();
1775 for(
int j = 0; j < this->
src[0].vertno.rows(); ++j) {
1776 for(
int k = 0; k < lPickedLabels.size(); k++) {
1777 for(
int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1778 if(this->
src[0].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 0) {
1779 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows()+1,3);
1780 matSourceVertLeft.row(matSourceVertLeft.rows()-1) = tSurfSetInflated[0].rr().row(this->
src[0].vertno(j)) - tSurfSetInflated[0].offset().transpose();
1787 for(
int j = 0; j < this->
src[1].vertno.rows(); ++j) {
1788 for(
int k = 0; k < lPickedLabels.size(); k++) {
1789 for(
int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1790 if(this->
src[1].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 1) {
1791 matSourceVertRight.conservativeResize(matSourceVertRight.rows()+1,3);
1792 matSourceVertRight.row(matSourceVertRight.rows()-1) = tSurfSetInflated[1].rr().row(this->
src[1].vertno(j)) - tSurfSetInflated[1].offset().transpose();
1800 matSourcePositions.resize(matSourceVertLeft.rows()+matSourceVertRight.rows(),3);
1801 matSourcePositions << matSourceVertLeft, matSourceVertRight;
1803 return matSourcePositions;
In-memory representation of a FreeSurfer colour/structure lookup table (FreeSurferColorLUT / embedded...
Bi-hemispheric grouping of FreeSurfer surfaces (lh + rh) loaded as a single object.
Reader and in-memory representation of a FreeSurfer/MNE surface label (.label).
Forward solution (gain matrix mapping source dipoles to sensor measurements).
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
#define FIFF_MNE_COORD_FRAME
#define FIFF_MNE_FORWARD_SOLUTION_GRAD
#define FIFF_MNE_SOURCE_ORIENTATION
#define FIFF_MNE_FORWARD_SOLUTION
#define FIFF_MNE_INCLUDED_METHODS
#define FIFF_MNE_SOURCE_SPACE_NPOINTS
#define FIFFV_MNE_FIXED_ORI
#define FIFFB_MNE_FORWARD_SOLUTION
#define FIFFB_MNE_PARENT_MEAS_FILE
#define FIFFV_MNE_ORIENT_PRIOR_COV
#define FIFF_MNE_FILE_NAME
#define FIFFB_MNE_PARENT_MRI_FILE
#define FIFFV_MNE_DEPTH_PRIOR_COV
#define FIFFV_MNE_FREE_ORI
#define FIFF_PARENT_BLOCK_ID
#define FIFF_PARENT_FILE_ID
Static MATLAB-style FIFF facade: thin wrapper functions kept for parity with the historical mne-matla...
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
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...
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.
Shared utilities (I/O helpers, spectral analysis, layout management, warp algorithms).
FIFF noise / data covariance: matrix, channel names, kind, applied projectors, bads,...
FiffCov prepare_noise_cov(const FiffInfo &p_info, const QStringList &p_chNames) const
QSharedPointer< FiffDirNode > SPtr
Full FIFF measurement info: per-channel descriptors, sampling and filter setup, projectors,...
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)
FIFF named matrix: dense / sparse Eigen matrix plus row-name and column-name string lists.
QSharedDataPointer< FiffNamedMatrix > SDPtr
void transpose_named_matrix()
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
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.
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)
static Eigen::VectorXi intersect(const Eigen::VectorXi &v1, const Eigen::VectorXi &v2, Eigen::VectorXi &idx_sel)
static Eigen::SparseMatrix< double > make_block_diag(const Eigen::MatrixXd &A, qint32 n)
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
FIFFLIB::fiff_int_t nsource
static void restrict_gain_matrix(Eigen::MatrixXd &G, const FIFFLIB::FiffInfo &info)
MNEForwardSolution reduce_forward_solution(qint32 p_iNumDipoles, Eigen::MatrixXd &p_D) const
FIFFLIB::FiffInfoBase info
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
FIFFLIB::fiff_int_t source_ori
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
bool isFixedOrient() 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=true)
Eigen::VectorXi tripletSelection(const Eigen::VectorXi &p_vecIdxSelection) const
Eigen::MatrixX3f source_nn
Eigen::MatrixX3f source_rr
FIFFLIB::FiffCov compute_orient_prior(float loose=0.2)
MNEForwardSolution pick_regions(const QList< FSLIB::FsLabel > &p_qListLabels) const
FIFFLIB::fiff_int_t coord_frame
FIFFLIB::fiff_int_t nchan
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
QList< Eigen::VectorXi > pinfo
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)