440 qint32 p_iClusterSize,
444 QString p_sMethod)
const
446 qInfo(
"Cluster forward solution using %s.", p_sMethod.toUtf8().constData());
451 if(!check_matching_chnames_conventions(p_pNoise_cov.
names, p_pInfo.
ch_names) && !p_pNoise_cov.
names.isEmpty() && !p_pInfo.
ch_names.isEmpty()) {
452 if(check_matching_chnames_conventions(p_pNoise_cov.
names, p_pInfo.
ch_names,
true)) {
453 qWarning(
"MNEForwardSolution::cluster_forward_solution - Cov names do match with info channel names but have a different naming convention.");
456 qWarning(
"MNEForwardSolution::cluster_forward_solution - Cov channel names do not match with info channel names.");
466 qWarning(
"Error: Fixed orientation not implemented yet!");
470 MatrixXd t_G_Whitened(0,0);
471 bool t_bUseWhitened =
false;
479 MatrixXd p_outWhitener;
480 qint32 p_outNumNonZero;
482 this->
prepare_forward(p_pInfo, p_pNoise_cov,
false, p_outFwdInfo, t_G_Whitened, p_outNoiseCov, p_outWhitener, p_outNumNonZero);
483 qInfo(
"\tWhitening the forward solution.");
485 t_G_Whitened = p_outWhitener*t_G_Whitened;
486 t_bUseWhitened =
true;
497 for(qint32 h = 0; h < this->
src.size(); ++h )
505 for(qint32 j = 0; j < h; ++j)
506 offset += this->
src[j].nuse;
509 qInfo(
"Cluster Left Hemisphere");
511 qInfo(
"Cluster Right Hemisphere");
515 VectorXi label_ids = t_CurrentColorTable.
getLabelIds();
518 VectorXi vertno_labeled = VectorXi::Zero(this->
src[h].vertno.rows());
521 for(qint32 i = 0; i < vertno_labeled.rows(); ++i)
522 vertno_labeled[i] = p_AnnotationSet[h].getLabelIds()[this->
src[h].vertno[i]];
524 std::vector<RegionData> regionDataIn;
529 for (qint32 i = 0; i < label_ids.rows(); ++i)
531 if (label_ids[i] != 0)
533 QString curr_name = t_CurrentColorTable.
struct_names[i];
534 qInfo(
"\tCluster %d / %ld %s...", i+1, label_ids.rows(), curr_name.toUtf8().constData());
539 VectorXi idcs = VectorXi::Zero(vertno_labeled.rows());
543 for(qint32 j = 0; j < vertno_labeled.rows(); ++j)
545 if(vertno_labeled[j] == label_ids[i])
551 idcs.conservativeResize(c);
554 MatrixXd t_G(this->
sol->data.rows(), idcs.rows()*3);
555 MatrixXd t_G_Whitened_Roi(t_G_Whitened.rows(), idcs.rows()*3);
557 for(qint32 j = 0; j < idcs.rows(); ++j)
559 t_G.block(0, j*3, t_G.rows(), 3) = this->
sol->data.block(0, (idcs[j]+offset)*3, t_G.rows(), 3);
561 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);
564 qint32 nSens = t_G.rows();
565 qint32 nSources = t_G.cols()/3;
573 t_sensG.
nClusters =
static_cast<int>(ceil(
static_cast<double>(nSources) /
static_cast<double>(p_iClusterSize)));
577 qInfo(
"%d Cluster(s)...", t_sensG.
nClusters);
580 t_sensG.
matRoiG = MatrixXd(t_G.cols()/3, 3*nSens);
582 t_sensG.
matRoiGWhitened = MatrixXd(t_G_Whitened_Roi.cols()/3, 3*nSens);
584 for(qint32 j = 0; j < nSens; ++j)
586 for(qint32 k = 0; k < t_sensG.
matRoiG.rows(); ++k)
587 t_sensG.
matRoiG.block(k,j*3,1,3) = t_G.block(j,k*3,1,3);
590 t_sensG.
matRoiGWhitened.block(k,j*3,1,3) = t_G_Whitened_Roi.block(j,k*3,1,3);
597 regionDataIn.push_back(std::move(t_sensG));
603 qWarning(
"failed! FsLabel contains no sources.");
611 qInfo(
"Clustering...");
612 QFuture< RegionDataOut > res;
614 res.waitForFinished();
619 MatrixXd t_G_partial;
623 auto itIn = regionDataIn.cbegin();
624 QFuture<RegionDataOut>::const_iterator itOut;
625 for (itOut = res.constBegin(); itOut != res.constEnd(); ++itOut)
627 nClusters = itOut->ctrs.rows();
628 nSens = itOut->ctrs.cols()/3;
629 t_G_partial = MatrixXd::Zero(nSens, nClusters*3);
635 for(qint32 j = 0; j < nSens; ++j)
636 for(qint32 k = 0; k < nClusters; ++k)
637 t_G_partial.block(j, k*3, 1, 3) = itOut->ctrs.block(k,j*3,1,3);
642 for(qint32 j = 0; j < nClusters; ++j)
644 VectorXi clusterIdcs = VectorXi::Zero(itOut->roiIdx.rows());
645 VectorXd clusterDistance = VectorXd::Zero(itOut->roiIdx.rows());
646 MatrixX3f clusterSource_rr = MatrixX3f::Zero(itOut->roiIdx.rows(), 3);
647 qint32 nClusterIdcs = 0;
648 for(qint32 k = 0; k < itOut->roiIdx.rows(); ++k)
650 if(itOut->roiIdx[k] == j)
652 clusterIdcs[nClusterIdcs] = itIn->idcs[k];
654 qint32 offset = h == 0 ? 0 : this->
src[0].nuse;
655 clusterSource_rr.row(nClusterIdcs) = this->
source_rr.row(offset + itIn->idcs[k]);
656 clusterDistance[nClusterIdcs] = itOut->D(k,j);
660 clusterIdcs.conservativeResize(nClusterIdcs);
661 clusterSource_rr.conservativeResize(nClusterIdcs,3);
662 clusterDistance.conservativeResize(nClusterIdcs);
664 VectorXi clusterVertnos = VectorXi::Zero(clusterIdcs.size());
665 for(qint32 k = 0; k < clusterVertnos.size(); ++k)
666 clusterVertnos(k) = this->
src[h].vertno[clusterIdcs(k)];
678 if(t_G_partial.rows() > 0 && t_G_partial.cols() > 0)
680 t_G_new.conservativeResize(t_G_partial.rows(), t_G_new.cols() + t_G_partial.cols());
681 t_G_new.block(0, t_G_new.cols() - t_G_partial.cols(), t_G_new.rows(), t_G_partial.cols()) = t_G_partial;
684 for(qint32 k = 0; k < nClusters; ++k)
688 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());
689 double sqec_min = sqec;
691 for(qint32 j = 1; j < itIn->idcs.rows(); ++j)
693 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());
703 qint32 sel_idx = itIn->idcs[j_min];
720 p_fwdOut.
src[h].vertno.conservativeResize(count);
728 qint32 totalNumOfClust = 0;
729 for (qint32 h = 0; h < 2; ++h)
733 p_D = MatrixXd::Zero(this->
sol->data.cols(), totalNumOfClust);
735 p_D = MatrixXd::Zero(this->
sol->data.cols(), totalNumOfClust*3);
737 QList<VectorXi> t_vertnos = this->
src.get_vertno();
739 qint32 currentCluster = 0;
740 for (qint32 h = 0; h < 2; ++h)
742 int hemiOffset = h == 0 ? 0 : t_vertnos[0].size();
743 for(qint32 i = 0; i < p_fwdOut.
src.
hemisphereAt(h)->cluster_info.clusterVertnos.size(); ++i)
748 idx_sel.array() += hemiOffset;
750 double selectWeight = 1.0/idx_sel.size();
753 for(qint32 j = 0; j < idx_sel.size(); ++j)
754 p_D.col(currentCluster)[idx_sel(j)] = selectWeight;
758 qint32 clustOffset = currentCluster*3;
759 for(qint32 j = 0; j < idx_sel.size(); ++j)
761 qint32 idx_sel_Offset = idx_sel(j)*3;
763 p_D(idx_sel_Offset,clustOffset) = selectWeight;
765 p_D(idx_sel_Offset+1, clustOffset+1) = selectWeight;
767 p_D(idx_sel_Offset+2, clustOffset+2) = selectWeight;
777 p_fwdOut.
sol->data = t_G_new;
778 p_fwdOut.
sol->ncol = t_G_new.cols();
1135 MatrixXd &p_outWhitener,
1136 qint32 &p_outNumNonZero)
const
1138 QStringList fwd_ch_names, ch_names;
1139 for(qint32 i = 0; i < this->
info.chs.size(); ++i)
1140 fwd_ch_names << this->
info.chs[i].ch_name;
1143 for(qint32 i = 0; i < p_info.
chs.size(); ++i)
1144 if(!p_info.
bads.contains(p_info.
chs[i].ch_name)
1145 && !p_noise_cov.
bads.contains(p_info.
chs[i].ch_name)
1146 && p_noise_cov.
names.contains(p_info.
chs[i].ch_name)
1147 && fwd_ch_names.contains(p_info.
chs[i].ch_name))
1148 ch_names << p_info.
chs[i].ch_name;
1150 qint32 n_chan = ch_names.size();
1151 qInfo(
"Computing inverse operator with %d channels.", n_chan);
1159 p_outNumNonZero = 0;
1160 VectorXi t_vecNonZero = VectorXi::Zero(n_chan);
1161 for(qint32 i = 0; i < p_outNoiseCov.
eig.rows(); ++i)
1163 if(p_outNoiseCov.
eig[i] > 0)
1165 t_vecNonZero[p_outNumNonZero] = i;
1169 if(p_outNumNonZero > 0)
1170 t_vecNonZero.conservativeResize(p_outNumNonZero);
1172 if(p_outNumNonZero > 0)
1176 qWarning(
"Warning in MNEForwardSolution::prepare_forward: if (p_pca) havent been debugged.");
1177 p_outWhitener = MatrixXd::Zero(n_chan, p_outNumNonZero);
1179 for(qint32 i = 0; i < p_outNumNonZero; ++i)
1180 p_outWhitener.col(t_vecNonZero[i]) = p_outNoiseCov.
eigvec.col(t_vecNonZero[i]).array() / sqrt(p_outNoiseCov.
eig(t_vecNonZero[i]));
1181 qInfo(
"\tReducing data rank to %d.", p_outNumNonZero);
1185 qInfo(
"Creating non pca whitener.");
1186 p_outWhitener = MatrixXd::Zero(n_chan, n_chan);
1187 for(qint32 i = 0; i < p_outNumNonZero; ++i)
1188 p_outWhitener(t_vecNonZero[i],t_vecNonZero[i]) = 1.0 / sqrt(p_outNoiseCov.
eig(t_vecNonZero[i]));
1190 p_outWhitener *= p_outNoiseCov.
eigvec;
1194 VectorXi fwd_idx = VectorXi::Zero(ch_names.size());
1195 VectorXi info_idx = VectorXi::Zero(ch_names.size());
1197 qint32 count_fwd_idx = 0;
1198 qint32 count_info_idx = 0;
1199 for(qint32 i = 0; i < ch_names.size(); ++i)
1201 idx = fwd_ch_names.indexOf(ch_names[i]);
1204 fwd_idx[count_fwd_idx] = idx;
1207 idx = p_info.
ch_names.indexOf(ch_names[i]);
1210 info_idx[count_info_idx] = idx;
1214 fwd_idx.conservativeResize(count_fwd_idx);
1215 info_idx.conservativeResize(count_info_idx);
1217 gain.resize(count_fwd_idx, this->
sol->data.cols());
1218 for(qint32 i = 0; i < count_fwd_idx; ++i)
1219 gain.row(i) = this->
sol->data.row(fwd_idx[i]);
1221 p_outFwdInfo = p_info.
pick_info(info_idx);
1223 qInfo(
"\tTotal rank is %d", p_outNumNonZero);
1232 const QStringList& include,
1233 const QStringList& exclude,
1238 qInfo(
"Reading forward solution from %s...", t_pStream->streamName().toUtf8().constData());
1239 if(!t_pStream->open())
1246 if (fwds.size() == 0)
1249 qWarning(
"No forward solutions in %s", t_pStream->streamName().toUtf8().constData());
1256 if (parent_mri.size() == 0)
1259 qWarning(
"No parent MRI information in %s", t_pStream->streamName().toUtf8().constData());
1267 qWarning(
"Could not read the source spaces");
1272 for(qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1273 t_SourceSpace[k].
id = t_SourceSpace[k].find_source_space_hemi();
1281 bads = t_pStream->read_bad_channels(t_pStream->dirtree());
1284 qInfo(
"\t%lld bad channels ( ", bads.size());
1285 for(qint32 i = 0; i < bads.size(); ++i)
1286 qInfo(
"\"%s\" ", bads[i].toUtf8().constData());
1297 for(qint32 k = 0; k < fwds.size(); ++k)
1302 qWarning(
"Methods not listed for one of the forward solutions");
1307 qInfo(
"MEG solution found");
1312 qInfo(
"EEG solution found");
1319 if (read_one(t_pStream, megnode, megfwd))
1322 ori = QString(
"fixed");
1324 ori = QString(
"free");
1325 qInfo(
"\tRead MEG forward solution (%d sources, %d channels, %s orientations)", megfwd.
nsource,megfwd.
nchan,ori.toUtf8().constData());
1328 if (read_one(t_pStream, eegnode, eegfwd))
1331 ori = QString(
"fixed");
1333 ori = QString(
"free");
1334 qInfo(
"\tRead EEG forward solution (%d sources, %d channels, %s orientations)", eegfwd.
nsource,eegfwd.
nchan,ori.toUtf8().constData());
1344 if (megfwd.
sol->data.cols() != eegfwd.
sol->data.cols() ||
1350 qWarning(
"The MEG and EEG forward solutions do not match");
1355 fwd.
sol->data = MatrixXd(megfwd.
sol->nrow + eegfwd.
sol->nrow, megfwd.
sol->ncol);
1357 fwd.
sol->data.block(0,0,megfwd.
sol->nrow,megfwd.
sol->ncol) = megfwd.
sol->data;
1358 fwd.
sol->data.block(megfwd.
sol->nrow,0,eegfwd.
sol->nrow,eegfwd.
sol->ncol) = eegfwd.
sol->data;
1359 fwd.
sol->nrow = megfwd.
sol->nrow + eegfwd.
sol->nrow;
1360 fwd.
sol->row_names.append(eegfwd.
sol->row_names);
1373 qInfo(
"\tMEG and EEG forward solutions combined");
1376 fwd = std::move(megfwd);
1378 fwd = std::move(eegfwd);
1386 qWarning(
"MRI/head coordinate transformation not found");
1399 qWarning(
"MRI/head coordinate transformation not found");
1408 t_pStream->read_meas_info_base(t_pStream->dirtree(), fwd.
info);
1418 qWarning(
"Only forward solutions computed in MRI or head coordinates are acceptable");
1425 for(qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1426 nuse += t_SourceSpace[k].nuse;
1429 qDebug() <<
"Source spaces do not match the forward solution.\n";
1433 qInfo(
"\tSource spaces transformed to the forward solution coordinate frame");
1434 fwd.
src = t_SourceSpace;
1443 for(qint32 k = 0; k < t_SourceSpace.
size();++k)
1445 for(qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1447 fwd.
source_rr.block(q,0,1,3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q),0,1,3);
1448 fwd.
source_nn.block(q,0,1,3) = t_SourceSpace[k].nn.block(t_SourceSpace[k].vertno(q),0,1,3);
1450 nuse += t_SourceSpace[k].nuse;
1457 qInfo(
"\tChanging to fixed-orientation forward solution...");
1459 MatrixXd tmp = fwd.
source_nn.transpose().cast<
double>();
1461 fwd.
sol->data *= fix_rot;
1467 SparseMatrix<double> t_matKron;
1468 SparseMatrix<double> t_eye(3,3);
1469 for (qint32 i = 0; i < 3; ++i)
1470 t_eye.insert(i,i) = 1.0f;
1471 t_matKron = kroneckerProduct(fix_rot,t_eye);
1483 qInfo(
"\tConverting to surface-based source orientations...");
1485 bool use_ave_nn =
false;
1487 if(hemi0 && hemi0->patch_inds.size() > 0)
1490 qInfo(
"\tAverage patch normals will be employed in the rotation to the local surface coordinates...");
1498 qWarning(
"Warning source_ori: Rotating the source coordinate system haven't been verified --> Singular Vectors U are different from MATLAB!");
1500 for(qint32 k = 0; k < t_SourceSpace.
size();++k)
1503 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1504 fwd.
source_rr.block(q+nuse,0,1,3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q),0,1,3);
1506 for (qint32 p = 0; p < t_SourceSpace[k].nuse; ++p)
1515 VectorXi t_vIdx = hemiK->
pinfo[hemiK->patch_inds[p]];
1516 Matrix3Xf t_nn(3, t_vIdx.size());
1517 for(qint32 i = 0; i < t_vIdx.size(); ++i)
1518 t_nn.col(i) = t_SourceSpace[k].nn.block(t_vIdx[i],0,1,3).transpose();
1519 nn = t_nn.rowwise().sum();
1520 nn.array() /= nn.norm();
1523 nn = t_SourceSpace[k].nn.block(t_SourceSpace[k].vertno(p),0,1,3).transpose();
1525 Matrix3f tmp = Matrix3f::Identity(nn.rows(), nn.rows()) - nn*nn.transpose();
1527 JacobiSVD<MatrixXf> t_svd(tmp, Eigen::ComputeThinU);
1529 VectorXf t_s = t_svd.singularValues();
1530 MatrixXf U = t_svd.matrixU();
1536 if ((nn.transpose() * U.block(0,2,3,1))(0,0) < 0)
1538 fwd.
source_nn.block(pp, 0, 3, 3) = U.transpose();
1541 nuse += t_SourceSpace[k].nuse;
1543 MatrixXd tmp = fwd.
source_nn.transpose().cast<
double>();
1546 fwd.
sol->data *= surf_rot;
1550 SparseMatrix<double> t_matKron;
1551 SparseMatrix<double> t_eye(3,3);
1552 for (qint32 i = 0; i < 3; ++i)
1553 t_eye.insert(i,i) = 1.0f;
1554 t_matKron = kroneckerProduct(surf_rot,t_eye);
1561 qInfo(
"\tCartesian source orientations...");
1564 for(qint32 k = 0; k < t_SourceSpace.
size(); ++k)
1566 for (qint32 q = 0; q < t_SourceSpace[k].nuse; ++q)
1567 fwd.
source_rr.block(q+nuse,0,1,3) = t_SourceSpace[k].rr.block(t_SourceSpace[k].vertno(q),0,1,3);
1569 nuse += t_SourceSpace[k].nuse;
1572 MatrixXf t_ones = MatrixXf::Ones(fwd.
nsource,1);
1573 Matrix3f t_eye = Matrix3f::Identity();
1574 fwd.
source_nn = kroneckerProduct(t_ones,t_eye);
1582 QStringList exclude_bads = exclude;
1583 if (bads.size() > 0)
1585 for(qint32 k = 0; k < bads.size(); ++k)
1586 if(!exclude_bads.contains(bads[k],Qt::CaseInsensitive))
1587 exclude_bads << bads[k];
1761 MatrixX3f matSourceVertLeft, matSourceVertRight, matSourcePositions;
1763 if(lPickedLabels.isEmpty()) {
1764 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - picked label list is empty. Returning.";
1765 return matSourcePositions;
1768 if(tSurfSetInflated.
isEmpty()) {
1769 qWarning() <<
"MNEForwardSolution::getSourcePositionsByLabel - tSurfSetInflated is empty. Returning.";
1770 return matSourcePositions;
1774 for(
int j = 0; j < this->
src[0].vertno.rows(); ++j) {
1775 for(
int k = 0; k < lPickedLabels.size(); k++) {
1776 if(this->
src[0].vertno(j) == lPickedLabels.at(k).label_id) {
1777 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows()+1,3);
1778 matSourceVertLeft.row(matSourceVertLeft.rows()-1) = tSurfSetInflated[0].rr().row(this->
src.hemisphereAt(0)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[0].offset().transpose();
1784 for(
int j = 0; j < this->
src[1].vertno.rows(); ++j) {
1785 for(
int k = 0; k < lPickedLabels.size(); k++) {
1786 if(this->
src[1].vertno(j) == lPickedLabels.at(k).label_id) {
1787 matSourceVertRight.conservativeResize(matSourceVertRight.rows()+1,3);
1788 matSourceVertRight.row(matSourceVertRight.rows()-1) = tSurfSetInflated[1].rr().row(this->
src.hemisphereAt(1)->cluster_info.centroidVertno.at(j)) - tSurfSetInflated[1].offset().transpose();
1794 for(
int j = 0; j < this->
src[0].vertno.rows(); ++j) {
1795 for(
int k = 0; k < lPickedLabels.size(); k++) {
1796 for(
int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1797 if(this->
src[0].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 0) {
1798 matSourceVertLeft.conservativeResize(matSourceVertLeft.rows()+1,3);
1799 matSourceVertLeft.row(matSourceVertLeft.rows()-1) = tSurfSetInflated[0].rr().row(this->
src[0].vertno(j)) - tSurfSetInflated[0].offset().transpose();
1806 for(
int j = 0; j < this->
src[1].vertno.rows(); ++j) {
1807 for(
int k = 0; k < lPickedLabels.size(); k++) {
1808 for(
int l = 0; l < lPickedLabels.at(k).vertices.rows(); l++) {
1809 if(this->
src[1].vertno(j) == lPickedLabels.at(k).vertices(l) && lPickedLabels.at(k).hemi == 1) {
1810 matSourceVertRight.conservativeResize(matSourceVertRight.rows()+1,3);
1811 matSourceVertRight.row(matSourceVertRight.rows()-1) = tSurfSetInflated[1].rr().row(this->
src[1].vertno(j)) - tSurfSetInflated[1].offset().transpose();
1819 matSourcePositions.resize(matSourceVertLeft.rows()+matSourceVertRight.rows(),3);
1820 matSourcePositions << matSourceVertLeft, matSourceVertRight;
1822 return matSourcePositions;