v2.0.0
Loading...
Searching...
No Matches
inv_label_time_course.cpp
Go to the documentation of this file.
1//=============================================================================================================
20
21//=============================================================================================================
22// INCLUDES
23//=============================================================================================================
24
26
27//=============================================================================================================
28// EIGEN INCLUDES
29//=============================================================================================================
30
31#include <Eigen/Core>
32#include <Eigen/SVD>
33
34//=============================================================================================================
35// STD INCLUDES
36//=============================================================================================================
37
38#include <cmath>
39#include <algorithm>
40
41//=============================================================================================================
42// QT INCLUDES
43//=============================================================================================================
44
45#include <QDebug>
46#include <QMap>
47
48//=============================================================================================================
49// USED NAMESPACES
50//=============================================================================================================
51
52using namespace INVLIB;
53using namespace FSLIB;
54using namespace Eigen;
55
56//=============================================================================================================
57// DEFINE MEMBER METHODS
58//=============================================================================================================
59
60VectorXd InvLabelTimeCourse::computeSignFlip(const MatrixXd& stcData)
61{
62 const int nVerts = static_cast<int>(stcData.rows());
63 if (nVerts == 0)
64 return VectorXd();
65
66 // Use SVD to find the dominant temporal pattern
67 // Then flip each vertex to align with it
68 JacobiSVD<MatrixXd> svd(stcData, ComputeThinU);
69 VectorXd u1 = svd.matrixU().col(0);
70
71 VectorXd signs(nVerts);
72 for (int i = 0; i < nVerts; ++i)
73 signs[i] = (u1[i] >= 0.0) ? 1.0 : -1.0;
74
75 return signs;
76}
77
78//=============================================================================================================
79
81 const QList<FsLabel>& labels,
82 const QString& sMode,
83 bool bAllowEmpty)
84{
85 if (stc.isEmpty() || labels.isEmpty()) {
86 qWarning() << "[InvLabelTimeCourse::extract] Empty stc or labels.";
87 return MatrixXd();
88 }
89
90 const int nTimes = static_cast<int>(stc.data.cols());
91 const int nLabels = labels.size();
92
93 // Resolve "auto" mode
94 QString mode = sMode;
95 if (mode == "auto")
96 mode = "mean_flip";
97
98 // Build a map from vertex index → row in stc.data
99 QMap<int, int> vertexToRow;
100 for (int i = 0; i < stc.vertices.size(); ++i)
101 vertexToRow.insert(stc.vertices[i], i);
102
103 // Count output labels (skip empty if !bAllowEmpty)
104 QList<int> outputLabelIndices;
105 for (int li = 0; li < nLabels; ++li) {
106 int count = 0;
107 for (int vi = 0; vi < labels[li].vertices.size(); ++vi) {
108 if (vertexToRow.contains(labels[li].vertices[vi]))
109 ++count;
110 }
111 if (count > 0 || bAllowEmpty)
112 outputLabelIndices.append(li);
113 }
114
115 MatrixXd result = MatrixXd::Zero(outputLabelIndices.size(), nTimes);
116
117 for (int oi = 0; oi < outputLabelIndices.size(); ++oi) {
118 const FsLabel& label = labels[outputLabelIndices[oi]];
119
120 // Find matching vertex rows
121 QList<int> rows;
122 for (int vi = 0; vi < label.vertices.size(); ++vi) {
123 auto it = vertexToRow.find(label.vertices[vi]);
124 if (it != vertexToRow.end())
125 rows.append(it.value());
126 }
127
128 if (rows.isEmpty())
129 continue;
130
131 const int nVerts = rows.size();
132
133 // Extract sub-matrix for this label
134 MatrixXd labelData(nVerts, nTimes);
135 for (int i = 0; i < nVerts; ++i)
136 labelData.row(i) = stc.data.row(rows[i]);
137
138 if (mode == "mean") {
139 result.row(oi) = labelData.colwise().mean();
140 } else if (mode == "mean_flip") {
141 VectorXd signs = computeSignFlip(labelData);
142 MatrixXd flipped = labelData;
143 for (int i = 0; i < nVerts; ++i)
144 flipped.row(i) *= signs[i];
145 result.row(oi) = flipped.colwise().mean();
146 } else if (mode == "pca_flip") {
147 // Demean across time
148 RowVectorXd meanTime = labelData.colwise().mean();
149 MatrixXd centered = labelData.rowwise() - meanTime;
150
151 JacobiSVD<MatrixXd> svd(centered, ComputeThinV);
152 // First PC time course
153 RowVectorXd pc1 = svd.matrixV().col(0).transpose();
154
155 // Scale by singular value and sign
156 double sv1 = svd.singularValues()[0];
157 pc1 *= sv1 / std::sqrt(static_cast<double>(nVerts));
158
159 // Flip sign to match the dominant sign of the data
160 // Check correlation with mean
161 double corr = (meanTime.array() * pc1.array()).sum();
162 if (corr < 0.0)
163 pc1 = -pc1;
164
165 result.row(oi) = pc1;
166 } else if (mode == "max") {
167 // Maximum absolute value at each time point
168 for (int t = 0; t < nTimes; ++t) {
169 double maxVal = 0.0;
170 double maxAbsVal = 0.0;
171 for (int i = 0; i < nVerts; ++i) {
172 double absVal = std::abs(labelData(i, t));
173 if (absVal > maxAbsVal) {
174 maxAbsVal = absVal;
175 maxVal = labelData(i, t);
176 }
177 }
178 result(oi, t) = maxVal;
179 }
180 } else {
181 qWarning() << "[InvLabelTimeCourse::extract] Unknown mode:" << mode
182 << "— using mean.";
183 result.row(oi) = labelData.colwise().mean();
184 }
185 }
186
187 return result;
188}
Eigen::JacobiSVD< Eigen::Matrix3f > svd(S, Eigen::ComputeFullU|Eigen::ComputeFullV)
ROI-level aggregation of vertex-wise source estimates — the C++ peer of MNE-Python's extract_label_ti...
FreeSurfer surface, annotation and parcellation I/O for mne-cpp.
Inverse source estimation (MNE, dSPM, sLORETA, dipole fitting).
A FreeSurfer/MNE surface label: per-vertex indices, Tk-RAS positions and scalar values for one hemisp...
Definition fs_label.h:81
Eigen::VectorXi vertices
Definition fs_label.h:166
static Eigen::VectorXd computeSignFlip(const Eigen::MatrixXd &stcData)
static Eigen::MatrixXd extract(const InvSourceEstimate &stc, const QList< FSLIB::FsLabel > &labels, const QString &sMode="mean_flip", bool bAllowEmpty=false)
Source-space inverse-solution container with dense grid plus optional focal-dipole,...