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] = (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) {
1097 qWarning(
"Warning in MNEForwardSolution::prepare_forward: if (p_pca) havent been debugged.");
1098 p_outWhitener = MatrixXd::Zero(n_chan, p_outNumNonZero);
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);
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]));
1109 p_outWhitener *= p_outNoiseCov.
eigvec;
1113 VectorXi fwd_idx = VectorXi::Zero(ch_names.size());
1114 VectorXi info_idx = VectorXi::Zero(ch_names.size());
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]);
1121 fwd_idx[count_fwd_idx] = idx;
1124 idx = p_info.
ch_names.indexOf(ch_names[i]);
1126 info_idx[count_info_idx] = idx;
1130 fwd_idx.conservativeResize(count_fwd_idx);
1131 info_idx.conservativeResize(count_info_idx);
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]);
1137 p_outFwdInfo = p_info.
pick_info(info_idx);
1139 qInfo(
"\tTotal rank is %d", p_outNumNonZero);
1148 const QStringList& include,
1149 const QStringList& exclude,
1154 qInfo(
"Reading forward solution from %s...", t_pStream->streamName().toUtf8().constData());
1155 if (!t_pStream->open())
1162 if (fwds.size() == 0) {
1164 qWarning(
"No forward solutions in %s", t_pStream->streamName().toUtf8().constData());
1171 if (parent_mri.size() == 0) {
1173 qWarning(
"No parent MRI information in %s", t_pStream->streamName().toUtf8().constData());
1180 qWarning(
"Could not read the source spaces");
1185 for (qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1186 t_SourceSpace[k].
id = t_SourceSpace[k].find_source_space_hemi();
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());
1208 for (qint32 k = 0; k < fwds.size(); ++k) {
1211 qWarning(
"Methods not listed for one of the forward solutions");
1215 qInfo(
"MEG solution found");
1218 qInfo(
"EEG solution found");
1225 if (read_one(t_pStream, megnode, megfwd)) {
1227 ori = QString(
"fixed");
1229 ori = QString(
"free");
1230 qInfo(
"\tRead MEG forward solution (%d sources, %d channels, %s orientations)", megfwd.
nsource, megfwd.
nchan, ori.toUtf8().constData());
1233 if (read_one(t_pStream, eegnode, eegfwd)) {
1235 ori = QString(
"fixed");
1237 ori = QString(
"free");
1238 qInfo(
"\tRead EEG forward solution (%d sources, %d channels, %s orientations)", eegfwd.
nsource, eegfwd.
nchan, ori.toUtf8().constData());
1247 if (megfwd.
sol->data.cols() != eegfwd.
sol->data.cols() ||
1252 qWarning(
"The MEG and EEG forward solutions do not match");
1257 fwd.
sol->data = MatrixXd(megfwd.
sol->nrow + eegfwd.
sol->nrow, megfwd.
sol->ncol);
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);
1274 qInfo(
"\tMEG and EEG forward solutions combined");
1276 fwd = std::move(megfwd);
1278 fwd = std::move(eegfwd);
1285 qWarning(
"MRI/head coordinate transformation not found");
1294 qWarning(
"MRI/head coordinate transformation not found");
1303 t_pStream->read_meas_info_base(t_pStream->dirtree(), fwd.
info);
1312 qWarning(
"Only forward solutions computed in MRI or head coordinates are acceptable");
1319 for (qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1320 nuse += t_SourceSpace[k].nuse;
1323 qWarning(
"Source spaces do not match the forward solution.");
1327 qInfo(
"\tSource spaces transformed to the forward solution coordinate frame");
1328 fwd.
src = t_SourceSpace;
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));
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();
1348 fwd.
source_nn.row(nuse + q) = t_SourceSpace[k].nn.row(t_SourceSpace[k].vertno(q));
1351 nuse += t_SourceSpace[k].nuse;
1357 qInfo(
"\tChanging to fixed-orientation forward solution...");
1359 MatrixXd tmp = fwd.
source_nn.transpose().cast<
double>();
1361 fwd.
sol->data *= fix_rot;
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);
1380 qInfo(
"\tCartesian source orientations...");
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);
1387 nuse += t_SourceSpace[k].nuse;
1390 MatrixXf t_ones = MatrixXf::Ones(fwd.
nsource, 1);
1391 Matrix3f t_eye = Matrix3f::Identity();
1392 fwd.
source_nn = kroneckerProduct(t_ones, t_eye);
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];
1433 qWarning(
"Source orientation tag not found.");
1441 qWarning(
"Coordinate frame tag not found.");
1449 qWarning(
"Number of sources not found.");
1453 one.
nsource = *t_pTag->toInt();
1455 if (!p_Node->find_tag(p_pStream,
FIFF_NCHAN, t_pTag)) {
1457 qWarning(
"Number of channels not found.");
1461 one.
nchan = *t_pTag->toInt();
1464 one.
sol->transpose_named_matrix();
1467 qWarning(
"Forward solution data not found.");
1473 one.
sol_grad->transpose_named_matrix();
1477 if (one.
sol->data.rows() != one.
nchan ||
1480 qWarning(
"Forward solution matrix has wrong dimensions.");
1488 qWarning(
"Forward solution gradient matrix has wrong dimensions.");
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()));
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());
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());
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());
1526 qWarning(
"Could not find MEG or EEG channels");
1541 qInfo(
"\tConverting to surface-based source orientations...");
1543 bool use_ave_nn =
false;
1544 auto* hemi0 =
src.hemisphereAt(0);
1545 if (hemi0 && hemi0->patch_inds.size() > 0) {
1547 qInfo(
"\tAverage patch normals will be employed in the rotation to the local surface coordinates...");
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);
1558 for (qint32 p = 0; p <
src[k].nuse; ++p) {
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();
1572 nn =
src[k].nn.block(
src[k].vertno(p), 0, 1, 3).transpose();
1574 Matrix3f tmp = Matrix3f::Identity(nn.rows(), nn.rows()) - nn * nn.transpose();
1576 JacobiSVD<MatrixXf> t_svd(tmp, Eigen::ComputeThinU);
1578 VectorXf t_s = t_svd.singularValues();
1579 MatrixXf U = t_svd.matrixU();
1585 if ((nn.transpose() * U.block(0, 2, 3, 1))(0, 0) < 0)
1587 this->
source_nn.block(pp, 0, 3, 3) = U.transpose();
1590 nuse +=
src[k].nuse;
1592 MatrixXd tmp = this->
source_nn.transpose().cast<
double>();
1595 this->
sol->data *= surf_rot;
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);
1614 qWarning(
"Cannot convert to fixed orientation: requires surface-oriented, free-orientation forward solution");
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())
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.");
1636 auto* hemi =
src.hemisphereAt(0);
1637 return hemi && hemi->isClustered();
1644 MatrixX3f matSourceVertLeft, matSourceVertRight, matSourcePositions;
1646 if (lPickedLabels.isEmpty()) {
1647 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - picked label list is empty. Returning.";
1648 return matSourcePositions;
1651 if (tSurfSetInflated.
isEmpty()) {
1652 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - tSurfSetInflated is empty. Returning.";
1653 return matSourcePositions;
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();
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();
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();
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();
1702 matSourcePositions.resize(matSourceVertLeft.rows() + matSourceVertRight.rows(), 3);
1703 matSourcePositions << matSourceVertLeft, matSourceVertRight;
1705 return matSourcePositions;
Static MATLAB-style FIFF facade: thin wrapper functions kept for parity with the historical mne-matla...
#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
4x4 affine FIFF coordinate transform (FIFF_COORD_TRANS) annotated with source/destination coordinate-...
#define FIFF_PARENT_BLOCK_ID
#define 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)