v2.0.0
Loading...
Searching...
No Matches
sourceestimateoverlay.cpp
Go to the documentation of this file.
1//=============================================================================================================
34
35//=============================================================================================================
36// INCLUDES
37//=============================================================================================================
38
41#include "core/rendertypes.h"
42
46
47#include <QFile>
48#include <QDebug>
49#include <cmath>
50
51//=============================================================================================================
52// DEFINE MEMBER METHODS
53//=============================================================================================================
54
58
59//=============================================================================================================
60
64
65//=============================================================================================================
66
67bool SourceEstimateOverlay::loadStc(const QString &path, int hemi)
68{
69 QFile file(path);
70 // Note: MNESourceEstimate::read() opens the file internally, don't open it here
71
73 if (!MNELIB::MNESourceEstimate::read(file, stc)) {
74 qWarning() << "SourceEstimateOverlay::loadStc - Failed to read STC file:" << path;
75 return false;
76 }
77
78 if (hemi == 0) {
79 m_stcLh = stc;
80 m_hasLh = true;
81 qDebug() << "SourceEstimateOverlay: Loaded LH with" << stc.data.rows() << "vertices,"
82 << stc.data.cols() << "time points";
83 } else {
84 m_stcRh = stc;
85 m_hasRh = true;
86 qDebug() << "SourceEstimateOverlay: Loaded RH with" << stc.data.rows() << "vertices,"
87 << stc.data.cols() << "time points";
88 }
89
90 // Auto-set thresholds based on data range
91 if (m_hasLh || m_hasRh) {
92 double minVal, maxVal;
93 getDataRange(minVal, maxVal);
94 m_threshMin = minVal;
95 m_threshMax = maxVal;
96 m_threshMid = (minVal + maxVal) / 2.0;
97 qDebug() << "SourceEstimateOverlay: Auto thresholds set to" << m_threshMin << m_threshMid << m_threshMax;
98 }
99
100 return true;
101}
102
103//=============================================================================================================
104
106{
107 return m_hasLh || m_hasRh;
108}
109
110//=============================================================================================================
111
113{
114 if (!surface) return;
115
116 int hemi = surface->hemi();
117 const MNELIB::MNESourceEstimate *stc = nullptr;
118 QSharedPointer<Eigen::SparseMatrix<float>> interpMat;
119
120 if (hemi == 0 && m_hasLh) {
121 stc = &m_stcLh;
122 interpMat = m_interpolationMatLh;
123 } else if (hemi == 1 && m_hasRh) {
124 stc = &m_stcRh;
125 interpMat = m_interpolationMatRh;
126 } else {
127 return; // No data for this hemisphere
128 }
129
130 if (stc->isEmpty()) return;
131
132 // Clamp time index
133 int tIdx = qBound(0, timeIndex, static_cast<int>(stc->data.cols()) - 1);
134
135 // Get source data for this time point
136 Eigen::VectorXf sourceData = stc->data.col(tIdx).cwiseAbs().cast<float>();
137
138 // Create color array for all surface vertices
139 uint32_t vertexCount = surface->vertexCount();
140 QVector<uint32_t> colors(vertexCount, 0xFF808080); // Default gray
141
142 // Determine if we have an interpolation matrix
143 Eigen::VectorXf interpolatedData;
144
145 if (interpMat && interpMat->rows() == static_cast<int>(vertexCount) &&
146 interpMat->cols() == sourceData.size()) {
147 // Use interpolation to spread values to all vertices
148 // Note: interpolateSignal returns by value, not QSharedPointer, so assignment matches
149 interpolatedData = DISP3DRHILIB::Interpolation::interpolateSignal(interpMat, QSharedPointer<Eigen::VectorXf>::create(sourceData));
150 } else {
151 // Fall back to sparse visualization (direct mapping)
152 interpolatedData = Eigen::VectorXf::Zero(vertexCount);
153 const Eigen::VectorXi &srcVertices = stc->vertices;
154 for (int i = 0; i < srcVertices.size() && i < sourceData.size(); ++i) {
155 int vertIdx = srcVertices(i);
156 if (vertIdx >= 0 && vertIdx < static_cast<int>(vertexCount)) {
157 interpolatedData(vertIdx) = sourceData(i);
158 }
159 }
160 }
161
162 // Convert interpolated values to colors
163 for (int i = 0; i < static_cast<int>(vertexCount); ++i) {
164 float value = interpolatedData(i);
165
166 // Normalize based on thresholds
167 double normalized = 0.0;
168 if (m_threshMax > m_threshMin) {
169 normalized = (value - m_threshMin) / (m_threshMax - m_threshMin);
170 normalized = qBound(0.0, normalized, 1.0);
171 }
172
173 // Calculate alpha based on threshold
174 uint8_t alpha = 255;
175 if (value < m_threshMin) {
176 alpha = 0; // Fully transparent below minimum
177 } else if (value < m_threshMid) {
178 // Fade in from min to mid
179 float range = m_threshMid - m_threshMin;
180 if (range > 0) {
181 alpha = static_cast<uint8_t>(255.0f * (value - m_threshMin) / range);
182 }
183 }
184
185 colors[i] = valueToColor(normalized, alpha);
186 }
187
188 surface->applySourceEstimateColors(colors);
189}
190
191//=============================================================================================================
192
193void SourceEstimateOverlay::setColormap(const QString &name)
194{
195 m_colormap = name;
196}
197
198//=============================================================================================================
199
200void SourceEstimateOverlay::setThresholds(float min, float mid, float max)
201{
202 m_threshMin = min;
203 m_threshMid = mid;
204 m_threshMax = max;
205}
206
207//=============================================================================================================
208
210{
211 if (m_hasLh) return m_stcLh.data.cols();
212 if (m_hasRh) return m_stcRh.data.cols();
213 return 0;
214}
215
216//=============================================================================================================
217
219{
220 if (m_hasLh && idx < m_stcLh.times.size()) {
221 return m_stcLh.times(idx);
222 }
223 if (m_hasRh && idx < m_stcRh.times.size()) {
224 return m_stcRh.times(idx);
225 }
226 return 0.0f;
227}
228
229//=============================================================================================================
230
232{
233 if (m_hasLh) return m_stcLh.tmin;
234 if (m_hasRh) return m_stcRh.tmin;
235 return 0.0f;
236}
237
238//=============================================================================================================
239
241{
242 if (m_hasLh) return m_stcLh.tstep;
243 if (m_hasRh) return m_stcRh.tstep;
244 return 0.0f;
245}
246
247//=============================================================================================================
248
249void SourceEstimateOverlay::getDataRange(double &minVal, double &maxVal) const
250{
251 minVal = std::numeric_limits<double>::max();
252 maxVal = std::numeric_limits<double>::lowest();
253
254 if (m_hasLh) {
255 double lhMin = m_stcLh.data.minCoeff();
256 double lhMax = m_stcLh.data.maxCoeff();
257 minVal = qMin(minVal, std::abs(lhMin));
258 maxVal = qMax(maxVal, std::abs(lhMax));
259 }
260
261 if (m_hasRh) {
262 double rhMin = m_stcRh.data.minCoeff();
263 double rhMax = m_stcRh.data.maxCoeff();
264 minVal = qMin(minVal, std::abs(rhMin));
265 maxVal = qMax(maxVal, std::abs(rhMax));
266 }
267
268 // If no data, set defaults
269 if (minVal > maxVal) {
270 minVal = 0.0;
271 maxVal = 1.0;
272 }
273}
274
275//=============================================================================================================
276
277uint32_t SourceEstimateOverlay::valueToColor(double value, uint8_t alpha) const
278{
279 QRgb rgb = DISPLIB::ColorMap::valueToColor(value, m_colormap);
280
281 uint32_t r = qRed(rgb);
282 uint32_t g = qGreen(rgb);
283 uint32_t b = qBlue(rgb);
284
285 // Pack as ABGR (same format as BrainSurface uses)
286 return packABGR(r, g, b, static_cast<uint32_t>(alpha));
287}
288
289//=============================================================================================================
290
291void SourceEstimateOverlay::computeInterpolationMatrix(BrainSurface *surface, int hemi, double cancelDist)
292{
293 if (!surface) return;
294
295 const MNELIB::MNESourceEstimate *stc = nullptr;
296 QSharedPointer<Eigen::SparseMatrix<float>> *pMatPtr = nullptr;
297
298 if (hemi == 0 && m_hasLh) {
299 stc = &m_stcLh;
300 pMatPtr = &m_interpolationMatLh;
301 } else if (hemi == 1 && m_hasRh) {
302 stc = &m_stcRh;
303 pMatPtr = &m_interpolationMatRh;
304 } else {
305 return;
306 }
307
308 if (stc->isEmpty()) return;
309
310 qDebug() << "SourceEstimateOverlay: Computing interpolation matrix for hemi" << hemi;
311
312 // Get vertices and neighbor information needed for Dijkstra (SCDC)
313 Eigen::MatrixX3f matVertices = surface->verticesAsMatrix();
314 std::vector<Eigen::VectorXi> vecNeighbors = surface->computeNeighbors();
315
316 // Source vertex subset from STC (already a VectorXi)
317 Eigen::VectorXi vecSourceVertices = stc->vertices;
318
319 qDebug() << "SourceEstimateOverlay: Surface has" << matVertices.rows() << "vertices,"
320 << vecSourceVertices.size() << "sources";
321
322 if (vecSourceVertices.size() == 0) {
323 qWarning() << "SourceEstimateOverlay: No source vertices found";
324 return;
325 }
326
327 // 1. Calculate Distance Table (Geodesic distance on surface)
328 // This uses Dijkstra's algorithm via GeometryInfo::scdc
329 // Note: This can be slow for many sources!
330 qDebug() << "SourceEstimateOverlay: Computing distance table (SCDC)...";
331 QSharedPointer<Eigen::MatrixXd> distTable = DISP3DRHILIB::GeometryInfo::scdc(
332 matVertices,
333 vecNeighbors,
334 vecSourceVertices,
335 cancelDist
336 );
337
338 if (!distTable || distTable->rows() == 0) {
339 qWarning() << "SourceEstimateOverlay: Failed to compute distance table";
340 return;
341 }
342
343 // 2. Create Interpolation Matrix
344 qDebug() << "SourceEstimateOverlay: Creating interpolation matrix...";
346 vecSourceVertices,
347 distTable,
348 DISP3DRHILIB::Interpolation::cubic, // Use cubic interpolation function
349 cancelDist
350 );
351
352 if (*pMatPtr && (*pMatPtr)->rows() > 0) {
353 qDebug() << "SourceEstimateOverlay: Interpolation matrix created:"
354 << (*pMatPtr)->rows() << "x" << (*pMatPtr)->cols();
355 } else {
356 qWarning() << "SourceEstimateOverlay: Failed to compute interpolation matrix";
357 }
358}
359
360//=============================================================================================================
361
363{
364 if (hemi == 0) {
365 m_stcLh = stc;
366 m_hasLh = true;
367 } else {
368 m_stcRh = stc;
369 m_hasRh = true;
370 }
371}
372
373//=============================================================================================================
374
375void SourceEstimateOverlay::setInterpolationMatrix(QSharedPointer<Eigen::SparseMatrix<float>> mat, int hemi)
376{
377 if (hemi == 0) {
378 m_interpolationMatLh = mat;
379 } else {
380 m_interpolationMatRh = mat;
381 }
382}
383
384//=============================================================================================================
385
387{
388 if (m_hasLh || m_hasRh) {
389 double minVal, maxVal;
390 getDataRange(minVal, maxVal);
391 m_threshMin = minVal;
392 m_threshMax = maxVal;
393 m_threshMid = (minVal + maxVal) / 2.0;
394 qDebug() << "SourceEstimateOverlay: Auto thresholds set to" << m_threshMin << m_threshMid << m_threshMax;
395 }
396}
397
398//=============================================================================================================
399
400Eigen::VectorXd SourceEstimateOverlay::sourceDataColumn(int timeIndex) const
401{
402 int nLh = m_hasLh ? m_stcLh.data.rows() : 0;
403 int nRh = m_hasRh ? m_stcRh.data.rows() : 0;
404
405 if (nLh == 0 && nRh == 0) {
406 return Eigen::VectorXd();
407 }
408
409 Eigen::VectorXd result(nLh + nRh);
410
411 if (m_hasLh) {
412 int tIdx = qBound(0, timeIndex, static_cast<int>(m_stcLh.data.cols()) - 1);
413 result.head(nLh) = m_stcLh.data.col(tIdx);
414 }
415
416 if (m_hasRh) {
417 int tIdx = qBound(0, timeIndex, static_cast<int>(m_stcRh.data.cols()) - 1);
418 result.segment(nLh, nRh) = m_stcRh.data.col(tIdx);
419 }
420
421 return result;
422}
ColorMap class declaration.
BrainSurface class declaration.
SourceEstimateOverlay class declaration.
GeometryInfo class declaration.
Interpolation class declaration.
Lightweight render-related enums shared across the disp3D_rhi library.
uint32_t packABGR(uint32_t r, uint32_t g, uint32_t b, uint32_t a=0xFF)
Definition rendertypes.h:60
static QRgb valueToColor(double v, const QString &sMap)
Definition colormap.h:688
static QSharedPointer< Eigen::MatrixXd > scdc(const Eigen::MatrixX3f &matVertices, const std::vector< Eigen::VectorXi > &vecNeighborVertices, Eigen::VectorXi &vecVertSubset, double dCancelDist=FLOAT_INFINITY)
scdc Calculates surface constrained distances on a mesh.
static Eigen::VectorXf interpolateSignal(const QSharedPointer< Eigen::SparseMatrix< float > > matInterpolationMatrix, const QSharedPointer< Eigen::VectorXf > &vecMeasurementData)
interpolateSignal Interpolates sensor data using the weight matrix (shared pointer version).
static double cubic(const double dIn)
cubic Cubic hyperbola interpolation function.
static QSharedPointer< Eigen::SparseMatrix< float > > createInterpolationMat(const Eigen::VectorXi &vecProjectedSensors, const QSharedPointer< Eigen::MatrixXd > matDistanceTable, double(*interpolationFunction)(double), const double dCancelDist=FLOAT_INFINITY, const Eigen::VectorXi &vecExcludeIndex=Eigen::VectorXi())
createInterpolationMat Calculates the weight matrix for interpolation.
Renderable cortical surface mesh with per-vertex color, curvature data, and GPU buffer management.
uint32_t vertexCount() const
int hemi() const
void applySourceEstimateColors(const QVector< uint32_t > &colors)
Eigen::MatrixX3f verticesAsMatrix() const
std::vector< Eigen::VectorXi > computeNeighbors() const
Eigen::VectorXd sourceDataColumn(int timeIndex) const
void getDataRange(double &minVal, double &maxVal) const
void setThresholds(float min, float mid, float max)
void applyToSurface(BrainSurface *surface, int timeIndex)
void setStcData(const MNELIB::MNESourceEstimate &stc, int hemi)
float timeAtIndex(int idx) const
void computeInterpolationMatrix(BrainSurface *surface, int hemi, double cancelDist=0.05)
void setColormap(const QString &name)
void setInterpolationMatrix(QSharedPointer< Eigen::SparseMatrix< float > > mat, int hemi)
bool loadStc(const QString &path, int hemi)
static bool read(QIODevice &p_IODevice, MNESourceEstimate &p_stc)