34#include <Eigen/Sparse>
35#include <unsupported/Eigen/KroneckerProduct>
46#include <QtConcurrent>
48#include <QRegularExpression>
65bool check_matching_chnames_conventions(
const QStringList& chNamesA,
const QStringList& chNamesB,
bool bCheckForNewNamingConvention =
false)
67 bool bMatching =
false;
69 if (chNamesA.isEmpty()) {
70 qWarning(
"Warning in check_matching_chnames_conventions - chNamesA list is empty. Nothing to compare");
72 if (chNamesB.isEmpty()) {
73 qWarning(
"Warning in check_matching_chnames_conventions - chNamesB list is empty. Nothing to compare");
76 QString replaceStringOldConv, replaceStringNewConv;
78 for (
int i = 0; i < chNamesA.size(); ++i) {
79 if (chNamesB.contains(chNamesA.at(i))) {
81 }
else if (bCheckForNewNamingConvention) {
82 replaceStringNewConv = chNamesA.at(i);
83 replaceStringNewConv.replace(
" ",
"");
85 if (chNamesB.contains(replaceStringNewConv)) {
88 QRegularExpression xRegExp(
"[0-9]{1,100}");
89 QRegularExpressionMatch match = xRegExp.match(chNamesA.at(i));
90 QStringList xList = match.capturedTexts();
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)));
96 if (chNamesB.contains(replaceStringNewConv) || chNamesB.contains(replaceStringOldConv)) {
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;
152 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) {
242 int nmeg =
static_cast<int>(megIdx.size());
243 int neeg =
static_cast<int>(eegIdx.size());
249 for (
int k = 0; k <
src.size(); ++k)
250 nvert +=
src[k].nuse;
282 if (!
info.filename.isEmpty())
284 if (!
info.meas_id.isEmpty())
286 t_pStream->write_coord_trans(
info.dev_head_t);
288 int totalChan = nmeg + neeg;
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]);
302 t_pStream->write_bad_channels(
info.bads);
310 for (
int k = 0; k <
src.size(); ++k) {
311 if (
src[k].writeToStream(t_pStream,
false) ==
FIFF_FAIL) {
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]]);
393 t_pStream->end_file();
400 if (
auto* qf =
dynamic_cast<QFile*
>(&p_IODevice)) {
401 QFile fileIn(qf->fileName());
404 const auto& dir = t_pStreamIn->dir();
405 for (
int i = 0; i < dir.size(); ++i) {
409 t_pStreamIn->write_dir_pointer(dirpos, dir[i]->pos);
413 t_pStreamIn->close();
423 qint32 p_iClusterSize,
427 QString p_sMethod)
const
429 qInfo(
"Cluster forward solution using %s.", p_sMethod.toUtf8().constData());
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.");
439 qWarning(
"MNEForwardSolution::cluster_forward_solution - Cov channel names do not match with info channel names.");
448 qWarning(
"Error: Fixed orientation not implemented yet!");
452 MatrixXd t_G_Whitened(0, 0);
453 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) {
484 for (qint32 j = 0; j < h; ++j)
485 offset += this->
src[j].nuse;
488 qInfo(
"Cluster Left Hemisphere");
490 qInfo(
"Cluster Right Hemisphere");
494 VectorXi label_ids = t_CurrentColorTable.
getLabelIds();
497 VectorXi vertno_labeled = VectorXi::Zero(this->
src[h].vertno.rows());
500 for (qint32 i = 0; i < vertno_labeled.rows(); ++i)
501 vertno_labeled[i] = p_AnnotationSet[h].getLabelIds()[this->
src[h].vertno[i]];
503 std::vector<RegionData> regionDataIn;
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];
511 qInfo(
"\tCluster %d / %ld %s...", i + 1, label_ids.rows(), curr_name.toUtf8().constData());
516 VectorXi idcs = VectorXi::Zero(vertno_labeled.rows());
520 for (qint32 j = 0; j < vertno_labeled.rows(); ++j) {
521 if (vertno_labeled[j] == label_ids[i]) {
526 idcs.conservativeResize(c);
529 MatrixXd t_G(this->
sol->data.rows(), idcs.rows() * 3);
530 MatrixXd t_G_Whitened_Roi(t_G_Whitened.rows(), idcs.rows() * 3);
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);
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);
538 qint32 nSens = t_G.rows();
539 qint32 nSources = t_G.cols() / 3;
546 t_sensG.
nClusters =
static_cast<int>(ceil(
static_cast<double>(nSources) /
static_cast<double>(p_iClusterSize)));
550 qInfo(
"%d Cluster(s)...", t_sensG.
nClusters);
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);
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) {
564 t_sensG.
matRoiGWhitened.block(k, j * 3, 1, 3) = t_G_Whitened_Roi.block(j, k * 3, 1, 3);
572 regionDataIn.push_back(std::move(t_sensG));
576 qWarning(
"failed! FsLabel contains no sources.");
584 qInfo(
"Clustering...");
585 QFuture<RegionDataOut> res;
587 res.waitForFinished();
592 MatrixXd t_G_partial;
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);
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);
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];
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);
629 clusterIdcs.conservativeResize(nClusterIdcs);
630 clusterSource_rr.conservativeResize(nClusterIdcs, 3);
631 clusterDistance.conservativeResize(nClusterIdcs);
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)];
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;
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;
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());
659 if (sqec < sqec_min) {
666 qint32 sel_idx = itIn->idcs[j_min];
683 p_fwdOut.
src[h].vertno.conservativeResize(count);
691 qint32 totalNumOfClust = 0;
692 for (qint32 h = 0; h < 2; ++h)
696 p_D = MatrixXd::Zero(this->
sol->data.cols(), totalNumOfClust);
698 p_D = MatrixXd::Zero(this->
sol->data.cols(), totalNumOfClust * 3);
700 QList<VectorXi> t_vertnos = this->
src.get_vertno();
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) {
709 idx_sel.array() += hemiOffset;
711 double selectWeight = 1.0 / idx_sel.size();
713 for (qint32 j = 0; j < idx_sel.size(); ++j)
714 p_D.col(currentCluster)[idx_sel(j)] = selectWeight;
716 qint32 clustOffset = currentCluster * 3;
717 for (qint32 j = 0; j < idx_sel.size(); ++j) {
718 qint32 idx_sel_Offset = idx_sel(j) * 3;
720 p_D(idx_sel_Offset, clustOffset) = selectWeight;
722 p_D(idx_sel_Offset + 1, clustOffset + 1) = selectWeight;
724 p_D(idx_sel_Offset + 2, clustOffset + 2) = selectWeight;
734 p_fwdOut.
sol->data = t_G_new;
735 p_fwdOut.
sol->ncol = t_G_new.cols();
749 qint32 np = isFixed ? p_fwdOut.
sol->data.cols() : p_fwdOut.
sol->data.cols() / 3;
751 if (p_iNumDipoles > np)
754 VectorXi sel(p_iNumDipoles);
756 float t_fStep =
static_cast<float>(np) /
static_cast<float>(p_iNumDipoles);
758 for (qint32 i = 0; i < p_iNumDipoles; ++i) {
759 float t_fCurrent =
static_cast<float>(i) * t_fStep;
760 sel[i] =
static_cast<quint32
>(floor(t_fCurrent));
764 p_D = MatrixXd::Zero(p_fwdOut.
sol->data.cols(), p_iNumDipoles);
765 for (qint32 i = 0; i < p_iNumDipoles; ++i)
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;
775 p_fwdOut.
sol->data = this->
sol->data * p_D;
777 MatrixX3f rr(p_iNumDipoles, 3);
779 MatrixX3f nn(p_iNumDipoles, 3);
781 for (qint32 i = 0; i < p_iNumDipoles; ++i) {
789 p_fwdOut.
sol->ncol = p_fwdOut.
sol->data.cols();
791 p_fwdOut.
nsource = p_iNumDipoles;
800 qInfo(
"\tCreating the depth weighting matrix...");
810 d = G.array().square().colwise().sum().transpose();
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());
815 qint32 n_pos = G.cols() / 3;
816 d = VectorXd::Zero(n_pos);
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();
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");
831 VectorXd w = d.cwiseInverse();
835 double weight_limit = pow(limit, 2);
836 if (!limit_depth_chs) {
841 limit = ws[ind] * weight_limit;
844 limit = ws[ws.size() - 1];
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) {
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);
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);
871 depth_prior.
data = wpp;
873 depth_prior.
data.resize(wpp.rows() * 3, 1);
876 for (qint32 i = 0; i < wpp.rows(); ++i) {
879 depth_prior.
data(idx, 0) = v;
880 depth_prior.
data(idx + 1, 0) = v;
881 depth_prior.
data(idx + 2, 0) = v;
886 depth_prior.
diag =
true;
887 depth_prior.
dim = depth_prior.
data.rows();
888 depth_prior.
nfree = 1;
898 qint32 n_sources = this->
sol->data.cols();
900 if (0 <= loose && loose <= 1) {
902 qWarning(
"\tForward operator is not oriented in surface coordinates. loose parameter should be None not %f.", loose);
904 qInfo(
"\tSetting loose to %f.", loose);
908 qInfo(
"\tIgnoring loose parameter with forward operator with fixed orientation.");
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);
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;
927 orient_prior.
diag =
true;
928 orient_prior.
dim = orient_prior.
data.size();
929 orient_prior.
nfree = 1;
937 const QStringList& exclude)
const
941 if (include.size() == 0 && exclude.size() == 0)
947 quint32 nuse = sel.size();
950 qInfo(
"Nothing remains after picking. Returning original forward solution.");
953 qInfo(
"\t%d out of %d channels remain after picking", nuse, fwd.
nchan);
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]);
960 fwd.
sol->data = newData;
961 fwd.
sol->nrow = nuse;
963 QStringList ch_names;
964 for (qint32 i = 0; i < sel.cols(); ++i)
965 ch_names << fwd.
sol->row_names[sel(i)];
967 fwd.
sol->row_names = ch_names;
969 QList<FiffChInfo> chs;
970 for (qint32 i = 0; i < sel.cols(); ++i)
971 chs.append(fwd.
info.
chs[sel(i)]);
976 for (qint32 i = 0; i < fwd.
info.
bads.size(); ++i)
977 if (ch_names.contains(fwd.
info.
bads[i]))
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]);
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;
1000 VectorXi selVertices;
1003 for (qint32 i = 0; i < p_qListLabels.size(); ++i) {
1004 VectorXi currentSelection;
1005 this->
src.label_src_vertno_sel(p_qListLabels[i], currentSelection);
1007 selVertices.conservativeResize(iSize + currentSelection.size());
1008 selVertices.block(iSize, 0, currentSelection.size(), 1) = currentSelection;
1009 iSize = selVertices.size();
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]);
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]);
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());
1046 RowVectorXi sel =
info.pick_types(meg, eeg,
false, include, exclude);
1048 QStringList include_ch_names;
1049 for (qint32 i = 0; i < sel.cols(); ++i)
1050 include_ch_names <<
info.ch_names[sel[i]];
1063 MatrixXd& p_outWhitener,
1064 qint32& p_outNumNonZero)
const
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;
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;
1075 qint32 n_chan = ch_names.size();
1076 qInfo(
"Computing inverse operator with %d channels.", n_chan);
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;
1092 if (p_outNumNonZero > 0)
1093 t_vecNonZero.conservativeResize(p_outNumNonZero);
1095 if (p_outNumNonZero > 0) {
1098 p_outWhitener = MatrixXd(p_outNumNonZero, n_chan);
1099 for (qint32 i = 0; i < p_outNumNonZero; ++i)
1100 p_outWhitener.row(i) = p_outNoiseCov.
eigvec.row(t_vecNonZero[i]) / sqrt(p_outNoiseCov.
eig(t_vecNonZero[i]));
1101 qInfo(
"\tReducing data rank to %d.", p_outNumNonZero);
1103 qInfo(
"Creating non pca whitener.");
1104 p_outWhitener = MatrixXd::Zero(n_chan, n_chan);
1105 for (qint32 i = 0; i < p_outNumNonZero; ++i)
1106 p_outWhitener(t_vecNonZero[i], t_vecNonZero[i]) = 1.0 / sqrt(p_outNoiseCov.
eig(t_vecNonZero[i]));
1108 p_outWhitener *= p_outNoiseCov.
eigvec;
1112 VectorXi fwd_idx = VectorXi::Zero(ch_names.size());
1113 VectorXi info_idx = VectorXi::Zero(ch_names.size());
1115 qint32 count_fwd_idx = 0;
1116 qint32 count_info_idx = 0;
1117 for (qint32 i = 0; i < ch_names.size(); ++i) {
1118 idx = fwd_ch_names.indexOf(ch_names[i]);
1120 fwd_idx[count_fwd_idx] = idx;
1123 idx = p_info.
ch_names.indexOf(ch_names[i]);
1125 info_idx[count_info_idx] = idx;
1129 fwd_idx.conservativeResize(count_fwd_idx);
1130 info_idx.conservativeResize(count_info_idx);
1132 gain.resize(count_fwd_idx, this->
sol->data.cols());
1133 for (qint32 i = 0; i < count_fwd_idx; ++i)
1134 gain.row(i) = this->
sol->data.row(fwd_idx[i]);
1136 p_outFwdInfo = p_info.
pick_info(info_idx);
1138 qInfo(
"\tTotal rank is %d", p_outNumNonZero);
1147 const QStringList& include,
1148 const QStringList& exclude,
1153 qInfo(
"Reading forward solution from %s...", t_pStream->streamName().toUtf8().constData());
1154 if (!t_pStream->open())
1161 if (fwds.size() == 0) {
1163 qWarning(
"No forward solutions in %s", t_pStream->streamName().toUtf8().constData());
1170 if (parent_mri.size() == 0) {
1172 qWarning(
"No parent MRI information in %s", t_pStream->streamName().toUtf8().constData());
1179 qWarning(
"Could not read the source spaces");
1184 for (qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1185 t_SourceSpace[k].
id = t_SourceSpace[k].find_source_space_hemi();
1192 bads = t_pStream->read_bad_channels(t_pStream->dirtree());
1193 if (bads.size() > 0) {
1194 qInfo(
"\t%lld bad channels ( ",
static_cast<long long>(bads.size()));
1195 for (qint32 i = 0; i < bads.size(); ++i)
1196 qInfo(
"\"%s\" ", bads[i].toUtf8().constData());
1207 for (qint32 k = 0; k < fwds.size(); ++k) {
1210 qWarning(
"Methods not listed for one of the forward solutions");
1214 qInfo(
"MEG solution found");
1217 qInfo(
"EEG solution found");
1224 if (read_one(t_pStream, megnode, megfwd)) {
1226 ori = QString(
"fixed");
1228 ori = QString(
"free");
1229 qInfo(
"\tRead MEG forward solution (%d sources, %d channels, %s orientations)", megfwd.
nsource, megfwd.
nchan, ori.toUtf8().constData());
1232 if (read_one(t_pStream, eegnode, eegfwd)) {
1234 ori = QString(
"fixed");
1236 ori = QString(
"free");
1237 qInfo(
"\tRead EEG forward solution (%d sources, %d channels, %s orientations)", eegfwd.
nsource, eegfwd.
nchan, ori.toUtf8().constData());
1246 if (megfwd.
sol->data.cols() != eegfwd.
sol->data.cols() ||
1251 qWarning(
"The MEG and EEG forward solutions do not match");
1256 fwd.
sol->data = MatrixXd(megfwd.
sol->nrow + eegfwd.
sol->nrow, megfwd.
sol->ncol);
1258 fwd.
sol->data.block(0, 0, megfwd.
sol->nrow, megfwd.
sol->ncol) = megfwd.
sol->data;
1259 fwd.
sol->data.block(megfwd.
sol->nrow, 0, eegfwd.
sol->nrow, eegfwd.
sol->ncol) = eegfwd.
sol->data;
1260 fwd.
sol->nrow = megfwd.
sol->nrow + eegfwd.
sol->nrow;
1261 fwd.
sol->row_names.append(eegfwd.
sol->row_names);
1273 qInfo(
"\tMEG and EEG forward solutions combined");
1275 fwd = std::move(megfwd);
1277 fwd = std::move(eegfwd);
1284 qWarning(
"MRI/head coordinate transformation not found");
1293 qWarning(
"MRI/head coordinate transformation not found");
1302 t_pStream->read_meas_info_base(t_pStream->dirtree(), fwd.
info);
1311 qWarning(
"Only forward solutions computed in MRI or head coordinates are acceptable");
1318 for (qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1319 nuse += t_SourceSpace[k].nuse;
1322 qWarning(
"Source spaces do not match the forward solution.");
1326 qInfo(
"\tSource spaces transformed to the forward solution coordinate frame");
1327 fwd.
src = t_SourceSpace;
1337 for (qint32 k = 0; k < t_SourceSpace.
size(); ++k) {
1338 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q) {
1339 fwd.
source_rr.row(nuse + q) = t_SourceSpace[k].rr.row(t_SourceSpace[k].vertno(q));
1342 RowVector3f nn = RowVector3f::Zero();
1343 for (
int v : hemi->pinfo[hemi->patch_inds[q]])
1344 nn += t_SourceSpace[k].nn.row(v);
1345 fwd.
source_nn.row(nuse + q) = nn.normalized();
1347 fwd.
source_nn.row(nuse + q) = t_SourceSpace[k].nn.row(t_SourceSpace[k].vertno(q));
1350 nuse += t_SourceSpace[k].nuse;
1356 qInfo(
"\tChanging to fixed-orientation forward solution...");
1358 MatrixXd tmp = fwd.
source_nn.transpose().cast<
double>();
1360 fwd.
sol->data *= fix_rot;
1365 SparseMatrix<double> t_matKron;
1366 SparseMatrix<double> t_eye(3, 3);
1367 for (qint32 i = 0; i < 3; ++i)
1368 t_eye.insert(i, i) = 1.0f;
1369 t_matKron = kroneckerProduct(fix_rot, t_eye);
1379 qInfo(
"\tCartesian source orientations...");
1382 for (qint32 k = 0; k < t_SourceSpace.
size(); ++k) {
1383 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1384 fwd.
source_rr.block(q + nuse, 0, 1, 3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q), 0, 1, 3);
1386 nuse += t_SourceSpace[k].nuse;
1389 MatrixXf t_ones = MatrixXf::Ones(fwd.
nsource, 1);
1390 Matrix3f t_eye = Matrix3f::Identity();
1391 fwd.
source_nn = kroneckerProduct(t_ones, t_eye);
1399 QStringList exclude_bads = exclude;
1400 if (bads.size() > 0) {
1401 for (qint32 k = 0; k < bads.size(); ++k)
1402 if (!exclude_bads.contains(bads[k], Qt::CaseInsensitive))
1403 exclude_bads << bads[k];
1432 qWarning(
"Source orientation tag not found.");
1440 qWarning(
"Coordinate frame tag not found.");
1448 qWarning(
"Number of sources not found.");
1452 one.
nsource = *t_pTag->toInt();
1454 if (!p_Node->find_tag(p_pStream,
FIFF_NCHAN, t_pTag)) {
1456 qWarning(
"Number of channels not found.");
1460 one.
nchan = *t_pTag->toInt();
1463 one.
sol->transpose_named_matrix();
1466 qWarning(
"Forward solution data not found.");
1472 one.
sol_grad->transpose_named_matrix();
1476 if (one.
sol->data.rows() != one.
nchan ||
1479 qWarning(
"Forward solution matrix has wrong dimensions.");
1487 qWarning(
"Forward solution gradient matrix has wrong dimensions.");
1499 if (
info.chs.size() != G.rows()) {
1500 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()));
1504 RowVectorXi sel =
info.pick_types(QString(
"grad"));
1505 if (sel.size() > 0) {
1506 for (qint32 i = 0; i < sel.size(); ++i)
1507 G.row(i) = G.row(sel[i]);
1508 G.conservativeResize(sel.size(), G.cols());
1509 qInfo(
"\t%ld planar channels", sel.size());
1511 sel =
info.pick_types(QString(
"mag"));
1512 if (sel.size() > 0) {
1513 for (qint32 i = 0; i < sel.size(); ++i)
1514 G.row(i) = G.row(sel[i]);
1515 G.conservativeResize(sel.size(), G.cols());
1516 qInfo(
"\t%ld magnetometer or axial gradiometer channels", sel.size());
1518 sel =
info.pick_types(
false,
true);
1519 if (sel.size() > 0) {
1520 for (qint32 i = 0; i < sel.size(); ++i)
1521 G.row(i) = G.row(sel[i]);
1522 G.conservativeResize(sel.size(), G.cols());
1523 qInfo(
"\t%ld EEG channels", sel.size());
1525 qWarning(
"Could not find MEG or EEG channels");
1540 qInfo(
"\tConverting to surface-based source orientations...");
1542 bool use_ave_nn =
false;
1543 auto* hemi0 =
src.hemisphereAt(0);
1544 if (hemi0 && hemi0->patch_inds.size() > 0) {
1546 qInfo(
"\tAverage patch normals will be employed in the rotation to the local surface coordinates...");
1553 for (qint32 k = 0; k <
src.size(); ++k) {
1554 for (qint32 q = 0; q <
src[k].nuse; ++q)
1555 this->
source_rr.block(q + nuse, 0, 1, 3) =
src[k].rr.block(
src[k].vertno(q), 0, 1, 3);
1557 for (qint32 p = 0; p <
src[k].nuse; ++p) {
1563 auto* hemiK =
src.hemisphereAt(k);
1564 VectorXi t_vIdx = hemiK->pinfo[hemiK->patch_inds[p]];
1565 Matrix3Xf t_nn(3, t_vIdx.size());
1566 for (qint32 i = 0; i < t_vIdx.size(); ++i)
1567 t_nn.col(i) =
src[k].nn.block(t_vIdx[i], 0, 1, 3).transpose();
1568 nn = t_nn.rowwise().sum();
1569 nn.array() /= nn.norm();
1571 nn =
src[k].nn.block(
src[k].vertno(p), 0, 1, 3).transpose();
1573 Matrix3f tmp = Matrix3f::Identity(nn.rows(), nn.rows()) - nn * nn.transpose();
1575 JacobiSVD<MatrixXf> t_svd(tmp, Eigen::ComputeThinU);
1577 VectorXf t_s = t_svd.singularValues();
1578 MatrixXf U = t_svd.matrixU();
1584 if ((nn.transpose() * U.block(0, 2, 3, 1))(0, 0) < 0)
1586 this->
source_nn.block(pp, 0, 3, 3) = U.transpose();
1589 nuse +=
src[k].nuse;
1591 MatrixXd tmp = this->
source_nn.transpose().cast<
double>();
1594 this->
sol->data *= surf_rot;
1597 SparseMatrix<double> t_matKron;
1598 SparseMatrix<double> t_eye(3, 3);
1599 for (qint32 i = 0; i < 3; ++i)
1600 t_eye.insert(i, i) = 1.0f;
1601 t_matKron = kroneckerProduct(surf_rot, t_eye);
1613 qWarning(
"Cannot convert to fixed orientation: requires surface-oriented, free-orientation forward solution");
1617 for (qint32 i = 2; i < this->
sol->data.cols(); i += 3) {
1618 this->
sol->data.col(count) = this->
sol->data.col(i);
1619 if (this->
source_nn.rows() == this->sol->data.cols())
1623 this->
sol->data.conservativeResize(this->
sol->data.rows(), count);
1624 if (this->
source_nn.rows() == 3 * count)
1625 this->
source_nn.conservativeResize(count, Eigen::NoChange);
1626 this->
sol->ncol = this->
sol->ncol / 3;
1628 qInfo(
"\tConverted the forward solution into the fixed-orientation mode.");
1635 auto* hemi =
src.hemisphereAt(0);
1636 return hemi && hemi->isClustered();
1643 MatrixX3f matSourceVertLeft, matSourceVertRight, matSourcePositions;
1645 if (lPickedLabels.isEmpty()) {
1646 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - picked label list is empty. Returning.";
1647 return matSourcePositions;
1650 if (tSurfSetInflated.
isEmpty()) {
1651 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - tSurfSetInflated is empty. Returning.";
1652 return matSourcePositions;
1656 for (
int j = 0; j < this->
src[0].vertno.rows(); ++j) {
1657 for (
int k = 0; k < lPickedLabels.size(); k++) {
1658 if (this->
src[0].vertno(j) == lPickedLabels.at(k).label_id) {
1659 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows() + 1, 3);
1660 matSourceVertLeft.row(matSourceVertLeft.rows() - 1) = tSurfSetInflated[0].rr().row(this->
src.hemisphereAt(0)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[0].offset().transpose();
1666 for (
int j = 0; j < this->
src[1].vertno.rows(); ++j) {
1667 for (
int k = 0; k < lPickedLabels.size(); k++) {
1668 if (this->
src[1].vertno(j) == lPickedLabels.at(k).label_id) {
1669 matSourceVertRight.conservativeResize(matSourceVertRight.rows() + 1, 3);
1670 matSourceVertRight.row(matSourceVertRight.rows() - 1) = tSurfSetInflated[1].rr().row(this->
src.hemisphereAt(1)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[1].offset().transpose();
1676 for (
int j = 0; j < this->
src[0].vertno.rows(); ++j) {
1677 for (
int k = 0; k < lPickedLabels.size(); k++) {
1678 for (
int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1679 if (this->
src[0].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 0) {
1680 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows() + 1, 3);
1681 matSourceVertLeft.row(matSourceVertLeft.rows() - 1) = tSurfSetInflated[0].rr().row(this->
src[0].vertno(j)) - tSurfSetInflated[0].offset().transpose();
1688 for (
int j = 0; j < this->
src[1].vertno.rows(); ++j) {
1689 for (
int k = 0; k < lPickedLabels.size(); k++) {
1690 for (
int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1691 if (this->
src[1].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 1) {
1692 matSourceVertRight.conservativeResize(matSourceVertRight.rows() + 1, 3);
1693 matSourceVertRight.row(matSourceVertRight.rows() - 1) = tSurfSetInflated[1].rr().row(this->
src[1].vertno(j)) - tSurfSetInflated[1].offset().transpose();
1701 matSourcePositions.resize(matSourceVertLeft.rows() + matSourceVertRight.rows(), 3);
1702 matSourcePositions << matSourceVertLeft, matSourceVertRight;
1704 return matSourcePositions;
Static MATLAB-style FIFF facade: thin wrapper functions kept for parity with the historical mne-matla...
constexpr auto FIFFB_MNE_PARENT_MEAS_FILE
constexpr auto FIFF_MNE_SOURCE_SPACE_NPOINTS
constexpr auto FIFFV_MNE_ORIENT_PRIOR_COV
constexpr auto FIFFV_COORD_HEAD
constexpr auto FIFF_MNE_FILE_NAME
constexpr auto FIFFV_MNE_MEG
constexpr auto FIFF_MNE_COORD_FRAME
constexpr auto FIFF_MNE_FORWARD_SOLUTION_GRAD
constexpr auto FIFFV_MNE_DEPTH_PRIOR_COV
constexpr auto FIFF_MNE_SOURCE_ORIENTATION
constexpr auto FIFFB_MNE_PARENT_MRI_FILE
constexpr auto FIFFV_MNE_EEG
constexpr auto FIFFV_COORD_MRI
constexpr auto FIFF_MNE_FORWARD_SOLUTION
constexpr auto FIFFB_MNE_FORWARD_SOLUTION
constexpr auto FIFF_MNE_INCLUDED_METHODS
constexpr auto FIFFV_MNE_FREE_ORI
constexpr auto FIFFV_MNE_FIXED_ORI
constexpr auto FIFFV_REF_MEG_CH
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
constexpr auto FIFF_DIR_POINTER
constexpr auto FIFF_NCHAN
constexpr auto FIFF_COORD_TRANS
constexpr auto FIFF_PARENT_BLOCK_ID
constexpr auto FIFF_PARENT_FILE_ID
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
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.
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 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)
void convert_to_surf_ori()
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
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
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)