v2.0.0
Loading...
Searching...
No Matches
geometryinfo.cpp
Go to the documentation of this file.
1//=============================================================================================================
35
36//=============================================================================================================
37// INCLUDES
38//=============================================================================================================
39
40#include "geometryinfo.h"
41
42#include <fiff/fiff_info.h>
43
44//=============================================================================================================
45// STL INCLUDES
46//=============================================================================================================
47
48#include <cmath>
49#include <fstream>
50#include <set>
51
52//=============================================================================================================
53// QT INCLUDES
54//=============================================================================================================
55
56#include <QtConcurrent/QtConcurrent>
57
58//=============================================================================================================
59// USED NAMESPACES
60//=============================================================================================================
61
62using namespace DISP3DLIB;
63using namespace Eigen;
64using namespace FIFFLIB;
65
66//=============================================================================================================
67// DEFINE MEMBER METHODS
68//=============================================================================================================
69
70QSharedPointer<MatrixXd> GeometryInfo::scdc(const MatrixX3f &matVertices,
71 const std::vector<VectorXi> &vecNeighborVertices,
72 VectorXi &vecVertSubset,
73 double dCancelDist)
74{
75 // create matrix and check for empty subset:
76 qint32 iCols = static_cast<qint32>(vecVertSubset.size());
77 if(vecVertSubset.size() == 0) {
78 qDebug() << "[WARNING] SCDC received empty subset, calculating full distance table, make sure you have enough memory !";
79 vecVertSubset = VectorXi::LinSpaced(matVertices.rows(), 0, static_cast<int>(matVertices.rows()) - 1);
80 iCols = static_cast<qint32>(matVertices.rows());
81 }
82
83 QSharedPointer<MatrixXd> returnMat = QSharedPointer<MatrixXd>::create(matVertices.rows(), iCols);
84
85 // distribute calculation on cores
86 int iCores = QThread::idealThreadCount();
87 if (iCores <= 0) {
88 iCores = 2;
89 }
90#ifdef __EMSCRIPTEN__
91 // Cap parallel threads to avoid exhausting the Emscripten pthread pool.
92 iCores = qMin(iCores, 2);
93#endif
94
95 qint32 iSubArraySize = int(double(vecVertSubset.size()) / double(iCores));
96 QVector<QFuture<void> > vecThreads(iCores);
97 qint32 iBegin = 0;
98 qint32 iEnd = iSubArraySize;
99
100 for (int i = 0; i < vecThreads.size(); ++i) {
101 if(i == vecThreads.size()-1)
102 {
103 vecThreads[i] = QtConcurrent::run(std::bind(iterativeDijkstra,
104 returnMat,
105 std::cref(matVertices),
106 std::cref(vecNeighborVertices),
107 std::cref(vecVertSubset),
108 iBegin,
109 static_cast<qint32>(vecVertSubset.size()),
110 dCancelDist));
111 break;
112 }
113 else
114 {
115 vecThreads[i] = QtConcurrent::run(std::bind(iterativeDijkstra,
116 returnMat,
117 std::cref(matVertices),
118 std::cref(vecNeighborVertices),
119 std::cref(vecVertSubset),
120 iBegin,
121 iEnd,
122 dCancelDist));
123 iBegin += iSubArraySize;
124 iEnd += iSubArraySize;
125 }
126 }
127
128 for (QFuture<void>& f : vecThreads) {
129 f.waitForFinished();
130 }
131
132 return returnMat;
133}
134
135//=============================================================================================================
136
137QSharedPointer<SparseMatrix<float>> GeometryInfo::scdcInterpolationMat(
138 const MatrixX3f &matVertices,
139 const std::vector<VectorXi> &vecNeighborVertices,
140 const VectorXi &vecVertSubset,
141 double (*interpolationFunction)(double),
142 double dCancelDist,
143 std::function<void(int, int)> progressCallback)
144{
145 const qint32 nVerts = static_cast<qint32>(matVertices.rows());
146 const qint32 nSources = static_cast<qint32>(vecVertSubset.size());
147 const qint32 nAdj = static_cast<qint32>(vecNeighborVertices.size());
148
149 // Build a set of source vertex indices for O(1) lookup
150 // (vertices that ARE source vertices get weight=1 on themselves)
151 QSet<qint32> sourceVertexSet;
152 QHash<qint32, qint32> vertexToSourceIdx;
153 for (qint32 s = 0; s < nSources; ++s) {
154 sourceVertexSet.insert(vecVertSubset[s]);
155 vertexToSourceIdx.insert(vecVertSubset[s], s);
156 }
157
158 // For each source vertex, run Dijkstra from that source.
159 // Collect distances to all reachable mesh vertices within cancelDist.
160 // Store as: perVertex[meshVertexIdx] -> list of (sourceIdx, geodesicDist)
161 // We process source-by-source since each Dijkstra only touches a small neighborhood.
162
163 // Thread-local storage: each chunk produces triplets
164 struct VertexWeight {
165 qint32 sourceIdx;
166 float dist;
167 };
168
169 // We'll collect per-vertex sparse distance info
170 // Using a vector of vectors: for each mesh vertex, store (sourceIdx, dist) pairs
171 std::vector<std::vector<VertexWeight>> perVertexWeights(nVerts);
172
173 // Process sources sequentially (each Dijkstra is already fast with cancelDist)
174 QVector<double> vecMinDists(nAdj);
175 std::set<std::pair<double, qint32>> vertexQ;
176 const double INF = FLOAT_INFINITY;
177
178 for (qint32 s = 0; s < nSources; ++s) {
179 if (progressCallback && s > 0 && s % 100 == 0) {
180 progressCallback(s, nSources);
181 }
182
183 qint32 iRoot = vecVertSubset[s];
184 vertexQ.clear();
185 vecMinDists.fill(INF);
186 vecMinDists[iRoot] = 0.0;
187 vertexQ.insert(std::make_pair(0.0, iRoot));
188
189 while (!vertexQ.empty()) {
190 const double dDist = vertexQ.begin()->first;
191 const qint32 u = vertexQ.begin()->second;
192 vertexQ.erase(vertexQ.begin());
193
194 if (dDist <= dCancelDist) {
195 const VectorXi& vecNeighbours = vecNeighborVertices[u];
196
197 for (Eigen::Index ne = 0; ne < vecNeighbours.size(); ++ne) {
198 qint32 v = vecNeighbours[ne];
199
200 const double dDistX = matVertices(u, 0) - matVertices(v, 0);
201 const double dDistY = matVertices(u, 1) - matVertices(v, 1);
202 const double dDistZ = matVertices(u, 2) - matVertices(v, 2);
203 const double dDistWithU = dDist + sqrt(dDistX * dDistX + dDistY * dDistY + dDistZ * dDistZ);
204
205 if (dDistWithU < vecMinDists[v]) {
206 vertexQ.erase(std::make_pair(vecMinDists[v], v));
207 vecMinDists[v] = dDistWithU;
208 vertexQ.insert(std::make_pair(vecMinDists[v], v));
209 }
210 }
211 }
212 }
213
214 // Collect all vertices reachable from this source within cancelDist
215 for (qint32 m = 0; m < nAdj; ++m) {
216 if (vecMinDists[m] < dCancelDist) {
217 perVertexWeights[m].push_back({s, static_cast<float>(vecMinDists[m])});
218 }
219 }
220 }
221
222 // Build the sparse interpolation matrix from the collected distances
223
224 QVector<Eigen::Triplet<float>> vecTriplets;
225 // Estimate ~10-20 sources per vertex on average
226 vecTriplets.reserve(nVerts * 10);
227
228 for (qint32 r = 0; r < nVerts; ++r) {
229 if (sourceVertexSet.contains(r)) {
230 // Source vertex: identity mapping (weight = 1)
231 vecTriplets.push_back(Eigen::Triplet<float>(r, vertexToSourceIdx[r], 1.0f));
232 } else {
233 const auto& weights = perVertexWeights[r];
234 if (weights.empty()) continue;
235
236 // Apply interpolation function and normalize weights
237 float dWeightsSum = 0.0f;
238 QVector<QPair<qint32, float>> vecBelowThresh;
239 vecBelowThresh.reserve(static_cast<int>(weights.size()));
240
241 for (const auto& w : weights) {
242 const float dValueWeight = std::fabs(1.0f / static_cast<float>(interpolationFunction(w.dist)));
243 dWeightsSum += dValueWeight;
244 vecBelowThresh.push_back(qMakePair(w.sourceIdx, dValueWeight));
245 }
246
247 for (const auto& qp : vecBelowThresh) {
248 vecTriplets.push_back(Eigen::Triplet<float>(r, qp.first, qp.second / dWeightsSum));
249 }
250 }
251 }
252
253 auto interpMat = QSharedPointer<SparseMatrix<float>>::create(nVerts, nSources);
254 interpMat->setFromTriplets(vecTriplets.begin(), vecTriplets.end());
255
256 return interpMat;
257}
258
259//=============================================================================================================
260
261VectorXi GeometryInfo::projectSensors(const MatrixX3f &matVertices,
262 const MatrixX3f &matSensorPositions)
263{
264 const qint32 iNumSensors = static_cast<qint32>(matSensorPositions.rows());
265
266 qint32 iCores = QThread::idealThreadCount();
267 if (iCores <= 0)
268 {
269 iCores = 2;
270 }
271#ifdef __EMSCRIPTEN__
272 iCores = qMin(iCores, (qint32)2);
273#endif
274
275 const qint32 iSubArraySize = int(double(iNumSensors) / double(iCores));
276
277 if(iSubArraySize <= 1)
278 {
279 return nearestNeighbor(matVertices, matSensorPositions, 0, iNumSensors);
280 }
281
282 QVector<QFuture<VectorXi> > vecThreads(iCores);
283 qint32 iBeginOffset = 0;
284 qint32 iEndOffset = iBeginOffset + iSubArraySize;
285 for(qint32 i = 0; i < vecThreads.size(); ++i)
286 {
287 if(i == vecThreads.size()-1)
288 {
289 vecThreads[i] = QtConcurrent::run(nearestNeighbor,
290 matVertices,
291 matSensorPositions,
292 iBeginOffset,
293 iNumSensors);
294 break;
295 }
296 else
297 {
298 vecThreads[i] = QtConcurrent::run(nearestNeighbor,
299 matVertices,
300 matSensorPositions,
301 iBeginOffset,
302 iEndOffset);
303 iBeginOffset = iEndOffset;
304 iEndOffset += iSubArraySize;
305 }
306 }
307
308 for (QFuture<VectorXi>& f : vecThreads) {
309 f.waitForFinished();
310 }
311
312 // concatenate partial results
313 VectorXi vecOutputArray(iNumSensors);
314 qint32 iOffset = 0;
315 for(qint32 i = 0; i < vecThreads.size(); ++i)
316 {
317 const VectorXi& partial = vecThreads[i].result();
318 vecOutputArray.segment(iOffset, partial.size()) = partial;
319 iOffset += static_cast<qint32>(partial.size());
320 }
321
322 return vecOutputArray;
323}
324
325//=============================================================================================================
326
327VectorXi GeometryInfo::nearestNeighbor(const MatrixX3f &matVertices,
328 const MatrixX3f &matSensorPositions,
329 qint32 iBegin,
330 qint32 iEnd)
331{
332 VectorXi vecMappedSensors(iEnd - iBegin);
333
334 for(qint32 s = iBegin; s < iEnd; ++s)
335 {
336 qint32 iChampionId = 0;
337 double iChampDist = std::numeric_limits<double>::max();
338 for(qint32 i = 0; i < matVertices.rows(); ++i)
339 {
340 double dDist = sqrt(squared(matVertices(i, 0) - matSensorPositions(s, 0))
341 + squared(matVertices(i, 1) - matSensorPositions(s, 1))
342 + squared(matVertices(i, 2) - matSensorPositions(s, 2)));
343 if(dDist < iChampDist)
344 {
345 iChampionId = i;
346 iChampDist = dDist;
347 }
348 }
349 vecMappedSensors[s - iBegin] = iChampionId;
350 }
351
352 return vecMappedSensors;
353}
354
355//=============================================================================================================
356
357void GeometryInfo::iterativeDijkstra(QSharedPointer<MatrixXd> matOutputDistMatrix,
358 const MatrixX3f &matVertices,
359 const std::vector<VectorXi> &vecNeighborVertices,
360 const VectorXi &vecVertSubset,
361 qint32 iBegin,
362 qint32 iEnd,
363 double dCancelDistance) {
364 const std::vector<VectorXi> &vecAdjacency = vecNeighborVertices;
365 qint32 n = static_cast<qint32>(vecAdjacency.size());
366 QVector<double> vecMinDists(n);
367 std::set< std::pair< double, qint32> > vertexQ;
368 const double INF = FLOAT_INFINITY;
369
370 for (qint32 i = iBegin; i < iEnd; ++i) {
371 if ((i - iBegin) > 0 && (i - iBegin) % 100 == 0) {
372 qDebug() << "GeometryInfo::iterativeDijkstra progress:" << (i - iBegin) << "/" << (iEnd - iBegin) << " (Thread range:" << iBegin << "-" << iEnd << ")";
373 }
374
375 qint32 iRoot = vecVertSubset[i];
376 vertexQ.clear();
377 vecMinDists.fill(INF);
378 vecMinDists[iRoot] = 0.0;
379 vertexQ.insert(std::make_pair(vecMinDists[iRoot], iRoot));
380
381 while (vertexQ.empty() == false) {
382 const double dDist = vertexQ.begin()->first;
383 const qint32 u = vertexQ.begin()->second;
384 vertexQ.erase(vertexQ.begin());
385
386 if (dDist <= dCancelDistance) {
387 const VectorXi& vecNeighbours = vecAdjacency[u];
388
389 for (Eigen::Index ne = 0; ne < vecNeighbours.size(); ++ne) {
390 qint32 v = vecNeighbours[ne];
391
392 const double dDistX = matVertices(u, 0) - matVertices(v, 0);
393 const double dDistY = matVertices(u, 1) - matVertices(v, 1);
394 const double dDistZ = matVertices(u, 2) - matVertices(v, 2);
395 const double dDistWithU = dDist + sqrt(dDistX * dDistX + dDistY * dDistY + dDistZ * dDistZ);
396
397 if (dDistWithU < vecMinDists[v]) {
398 vertexQ.erase(std::make_pair(vecMinDists[v], v));
399 vecMinDists[v] = dDistWithU;
400 vertexQ.insert(std::make_pair(vecMinDists[v], v));
401 }
402 }
403 }
404 }
405
406 for (qint32 m = 0; m < vecMinDists.size(); ++m) {
407 matOutputDistMatrix->coeffRef(m , i) = vecMinDists[m];
408 }
409 }
410}
411
412//=============================================================================================================
413
414VectorXi GeometryInfo::filterBadChannels(QSharedPointer<Eigen::MatrixXd> matDistanceTable,
415 const FIFFLIB::FiffInfo& fiffInfo,
416 qint32 iSensorType) {
417 std::vector<int> vecBadColumns;
418 QVector<const FiffChInfo*> vecSensors;
419 for(const FiffChInfo& s : fiffInfo.chs){
420 if(s.kind == iSensorType && (s.unit == FIFF_UNIT_T || s.unit == FIFF_UNIT_V)){
421 vecSensors.push_back(&s);
422 }
423 }
424
425 for(const QString& b : fiffInfo.bads){
426 for(int col = 0; col < vecSensors.size(); ++col){
427 if(vecSensors[col]->ch_name == b){
428 vecBadColumns.push_back(col);
429 for(int row = 0; row < matDistanceTable->rows(); ++row){
430 matDistanceTable->coeffRef(row, col) = FLOAT_INFINITY;
431 }
432 break;
433 }
434 }
435 }
436
437 return Eigen::Map<VectorXi>(vecBadColumns.data(), static_cast<Eigen::Index>(vecBadColumns.size()));
438}
GeometryInfo class declaration.
#define FLOAT_INFINITY
FiffInfo class declaration.
#define FIFF_UNIT_V
#define FIFF_UNIT_T
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
3-D brain visualisation using the Qt RHI rendering backend.
static Eigen::VectorXi nearestNeighbor(const Eigen::MatrixX3f &matVertices, const Eigen::MatrixX3f &matSensorPositions, qint32 iBegin, qint32 iEnd)
static QSharedPointer< Eigen::SparseMatrix< float > > scdcInterpolationMat(const Eigen::MatrixX3f &matVertices, const std::vector< Eigen::VectorXi > &vecNeighborVertices, const Eigen::VectorXi &vecVertSubset, double(*interpolationFunction)(double), double dCancelDist, std::function< void(int, int)> progressCallback=nullptr)
scdcInterpolationMat Computes geodesic distances (SCDC) and builds the sparse interpolation matrix in...
static Eigen::VectorXi filterBadChannels(QSharedPointer< Eigen::MatrixXd > matDistanceTable, const FIFFLIB::FiffInfo &fiffInfo, qint32 iSensorType)
filterBadChannels Filters bad channels from distance table.
static void iterativeDijkstra(QSharedPointer< Eigen::MatrixXd > matOutputDistMatrix, const Eigen::MatrixX3f &matVertices, const std::vector< Eigen::VectorXi > &vecNeighborVertices, const Eigen::VectorXi &vecVertSubset, qint32 iBegin, qint32 iEnd, double dCancelDistance)
static double squared(double dBase)
static Eigen::VectorXi projectSensors(const Eigen::MatrixX3f &matVertices, const Eigen::MatrixX3f &matSensorPositions)
projectSensors Calculates the nearest neighbor vertex to each sensor.
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.
Channel info descriptor.
FIFF measurement file information.
Definition fiff_info.h:86
QList< FiffChInfo > chs