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 }
141 else if (mode == "mean_flip") {
142 VectorXd signs = computeSignFlip(labelData);
143 MatrixXd flipped = labelData;
144 for (int i = 0; i < nVerts; ++i)
145 flipped.row(i) *= signs[i];
146 result.row(oi) = flipped.colwise().mean();
147 }
148 else if (mode == "pca_flip") {
149 // Demean across time
150 RowVectorXd meanTime = labelData.colwise().mean();
151 MatrixXd centered = labelData.rowwise() - meanTime;
152
153 JacobiSVD<MatrixXd> svd(centered, ComputeThinV);
154 // First PC time course
155 RowVectorXd pc1 = svd.matrixV().col(0).transpose();
156
157 // Scale by singular value and sign
158 double sv1 = svd.singularValues()[0];
159 pc1 *= sv1 / std::sqrt(static_cast<double>(nVerts));
160
161 // Flip sign to match the dominant sign of the data
162 // Check correlation with mean
163 double corr = (meanTime.array() * pc1.array()).sum();
164 if (corr < 0.0)
165 pc1 = -pc1;
166
167 result.row(oi) = pc1;
168 }
169 else if (mode == "max") {
170 // Maximum absolute value at each time point
171 for (int t = 0; t < nTimes; ++t) {
172 double maxVal = 0.0;
173 double maxAbsVal = 0.0;
174 for (int i = 0; i < nVerts; ++i) {
175 double absVal = std::abs(labelData(i, t));
176 if (absVal > maxAbsVal) {
177 maxAbsVal = absVal;
178 maxVal = labelData(i, t);
179 }
180 }
181 result(oi, t) = maxVal;
182 }
183 }
184 else {
185 qWarning() << "[InvLabelTimeCourse::extract] Unknown mode:" << mode
186 << "— using mean.";
187 result.row(oi) = labelData.colwise().mean();
188 }
189 }
190
191 return result;
192}
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:79
Eigen::VectorXi vertices
Definition fs_label.h:164
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,...