v2.0.0
Loading...
Searching...
No Matches
brainsurface.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "brainsurface.h"
18
19#include <QHash>
20#include <QMatrix3x3>
21#include <QMatrix4x4>
22
23#include <algorithm>
24#include <limits>
25#include <set>
26
27namespace DISP3DLIB
28{
29
30namespace
31{
32uint32_t withAlpha(uint32_t color, uint32_t alpha)
33{
34 return (color & 0x00FFFFFFu) | ((alpha & 0xFFu) << 24);
35}
36
37uint32_t curvatureGray(const QVector<float>& curvature, int index)
38{
39 return (index >= 0 && index < curvature.size() && curvature[index] > 0.0f)
40 ? 0x40u
41 : 0xAAu;
42}
43
44// Overlay alpha is blended into RGB here because the shader keeps the curvature grey in the alpha channel.
45uint32_t overCortex(uint32_t overlay, uint32_t gray)
46{
47 const uint32_t a = overlay >> 24;
48 uint32_t rgb = 0;
49 for (int shift = 0; shift < 24; shift += 8) {
50 const uint32_t c = (overlay >> shift) & 0xFFu;
51 rgb |= ((c * a + gray * (255u - a) + 127u) / 255u) << shift;
52 }
53 return withAlpha(rgb, gray);
54}
55} // namespace
56
57//=============================================================================================================
58// DEFINE MEMBER METHODS
59//=============================================================================================================
60
62
63//=============================================================================================================
64
66
67//=============================================================================================================
68
69void BrainSurface::markGeometryDirty()
70{
71 ++m_geometryGeneration;
72 m_bAABBDirty = true;
73}
74
75//=============================================================================================================
76
78{
79 return {m_vertexData.constData(), quint32(m_vertexData.size() * sizeof(VertexData)), m_geometryGeneration};
80}
81
83{
84 return {m_colors.constData(), quint32(m_colors.size() * sizeof(uint32_t)), m_colorGeneration};
85}
86
88{
89 return {m_annotationColors.constData(), quint32(m_annotationColors.size() * sizeof(uint32_t)), m_annotationGeneration};
90}
91
93{
94 return {m_indexData.constData(), quint32(m_indexData.size() * sizeof(uint32_t)), m_indexGeneration};
95}
96
97//=============================================================================================================
98
99void BrainSurface::setGeometry(QVector<VertexData> vertices, QVector<uint32_t> indices, const QColor& color)
100{
101 m_vertexData = std::move(vertices);
102 m_originalVertexData = m_vertexData;
103 m_indexData = std::move(indices);
104 m_indexCount = m_indexData.size();
105 ++m_indexGeneration;
106 markGeometryDirty();
107 m_defaultColor = color;
108 m_baseColor = color;
109 m_colors.resize(m_vertexData.size());
110 m_annotationColors.fill(0u, m_vertexData.size());
111 m_vertexLabelIds.fill(-1, m_vertexData.size());
112 ++m_annotationGeneration;
113 if (m_hasAnnotation)
114 addAnnotation(m_annotation);
115 updateVertexColors();
116}
117
118//=============================================================================================================
119
121{
122 const Eigen::MatrixXf& rr = surf.rr();
123 const Eigen::MatrixXf& nn = surf.nn();
124 const Eigen::MatrixXi& tris = surf.tris();
125 const Eigen::VectorXf& curv = surf.curv();
126 m_curvature.resize(curv.size());
127 for (int i = 0; i < curv.size(); ++i)
128 m_curvature[i] = curv[i];
129
130 QVector<VertexData> vertices(rr.rows());
131 for (int i = 0; i < rr.rows(); ++i)
132 vertices[i] = {QVector3D(rr(i, 0), rr(i, 1), rr(i, 2)), QVector3D(nn(i, 0), nn(i, 1), nn(i, 2))};
133 QVector<uint32_t> indices(tris.rows() * 3);
134 for (int i = 0; i < tris.rows(); ++i)
135 for (int j = 0; j < 3; ++j)
136 indices[3 * i + j] = tris(i, j);
137 setGeometry(std::move(vertices), std::move(indices), Qt::white);
138}
139
140//=============================================================================================================
141
142void BrainSurface::fromBemSurface(const MNELIB::MNEBemSurface& surf, const QColor& color)
143{
144 m_curvature.clear();
145
146 const int nVerts = surf.rr.rows();
147 Eigen::MatrixX3f nn = surf.nn;
148 if (nn.rows() != nVerts) {
149 nn = FSLIB::FsSurface::compute_normals(Eigen::MatrixX3f(surf.rr), Eigen::MatrixX3i(surf.itris));
150 }
151 QVector<VertexData> vertices(nVerts);
152 for (int i = 0; i < nVerts; ++i)
153 vertices[i] = {QVector3D(surf.rr(i, 0), surf.rr(i, 1), surf.rr(i, 2)), QVector3D(nn(i, 0), nn(i, 1), nn(i, 2))};
154 QVector<uint32_t> indices(surf.itris.rows() * 3);
155 for (int i = 0; i < surf.itris.rows(); ++i)
156 for (int j = 0; j < 3; ++j)
157 indices[3 * i + j] = surf.itris(i, j);
158 setGeometry(std::move(vertices), std::move(indices), color);
159}
160
161void BrainSurface::createFromData(const Eigen::MatrixX3f& vertices, const Eigen::MatrixX3i& triangles, const QColor& color)
162{
163 createFromData(vertices, FSLIB::FsSurface::compute_normals(vertices, triangles), triangles, color);
164}
165
166void BrainSurface::createFromData(const Eigen::MatrixX3f& vertices, const Eigen::MatrixX3f& normals, const Eigen::MatrixX3i& triangles, const QColor& color)
167{
168 m_curvature.clear();
169
170 QVector<VertexData> verts(vertices.rows());
171 for (int i = 0; i < vertices.rows(); ++i)
172 verts[i] = {QVector3D(vertices(i, 0), vertices(i, 1), vertices(i, 2)), QVector3D(normals(i, 0), normals(i, 1), normals(i, 2))};
173 QVector<uint32_t> indices(triangles.rows() * 3);
174 for (int i = 0; i < triangles.rows(); ++i)
175 for (int j = 0; j < 3; ++j)
176 indices[3 * i + j] = triangles(i, j);
177 setGeometry(std::move(verts), std::move(indices), color);
178}
179
180//=============================================================================================================
181
182Eigen::MatrixX3f BrainSurface::vertexPositions() const
183{
184 Eigen::MatrixX3f rr(m_vertexData.size(), 3);
185 for (int i = 0; i < m_vertexData.size(); ++i) {
186 rr(i, 0) = m_vertexData[i].pos.x();
187 rr(i, 1) = m_vertexData[i].pos.y();
188 rr(i, 2) = m_vertexData[i].pos.z();
189 }
190 return rr;
191}
192
193//=============================================================================================================
194
195Eigen::MatrixX3f BrainSurface::vertexNormals() const
196{
197 Eigen::MatrixX3f nn(m_vertexData.size(), 3);
198 for (int i = 0; i < m_vertexData.size(); ++i) {
199 nn(i, 0) = m_vertexData[i].norm.x();
200 nn(i, 1) = m_vertexData[i].norm.y();
201 nn(i, 2) = m_vertexData[i].norm.z();
202 }
203 return nn;
204}
205
206//=============================================================================================================
207
208bool BrainSurface::loadAnnotation(const QString& path)
209{
210 FSLIB::FsAnnotation annotation;
211 if (!FSLIB::FsAnnotation::read(path, annotation)) {
212 qWarning() << "BrainSurface: Failed to load annotation from" << path;
213 return false;
214 }
215 addAnnotation(annotation);
216 return true;
217}
218
220{
221 m_annotation = annotation;
222 m_hasAnnotation = true;
223
224 // Per-vertex label ids and colours, so hover lookups and drawing need no search
225 const Eigen::VectorXi& vertices = m_annotation.getVertices();
226 const Eigen::VectorXi& labelIds = m_annotation.getLabelIds();
227 const FSLIB::FsColortable& ct = m_annotation.getColortable();
228 QHash<int, uint32_t> labelColors;
229 for (int c = 0; c < ct.numEntries; ++c)
230 labelColors.insert(ct.table(c, 4), packABGR(ct.table(c, 0), ct.table(c, 1), ct.table(c, 2)));
231 m_vertexLabelIds.fill(-1, m_vertexData.size());
232 m_annotationColors.fill(0u, m_vertexData.size());
233 for (int i = 0; i < labelIds.rows(); ++i) {
234 const int v = vertices(i);
235 if (v < 0 || v >= m_vertexData.size())
236 continue;
237 m_vertexLabelIds[v] = labelIds(i);
238 m_annotationColors[v] = labelColors.value(labelIds(i), 0u);
239 }
240 ++m_annotationGeneration;
241}
242
243//=============================================================================================================
244
245void BrainSurface::labelElements(int verticesPerElement)
246{
247 if (verticesPerElement <= 0)
248 return;
249 // Alpha 0 keeps element keys apart from atlas colours, which are opaque
250 for (int v = 0; v < m_annotationColors.size(); ++v) {
251 const uint32_t key = uint32_t(v / verticesPerElement + 1);
252 m_annotationColors[v] = packABGR(key & 0xFFu, (key >> 8) & 0xFFu, (key >> 16) & 0xFFu, 0u);
253 }
254 ++m_annotationGeneration;
255}
256
257//=============================================================================================================
258
259void BrainSurface::setVisible(bool visible)
260{
261 m_visible = visible;
262}
263
264//=============================================================================================================
265
267{
268 // The colour stream holds the same data in every mode; each view picks the channel by uniform
269 m_visMode = mode;
270}
271
272//=============================================================================================================
273
274void BrainSurface::applySourceEstimateColors(const QVector<uint32_t>& colors)
275{
276 m_visMode = ModeSourceEstimate;
277 m_stcColors = colors;
278
279 // Preserve the neutral curvature grey in alpha so Surface mode can still render the light/dark
280 // cortex while RGB carries the source-estimate colours.
281 const int n = qMin(colors.size(), m_colors.size());
282 for (int i = 0; i < n; ++i) {
283 m_colors[i] = overCortex(colors[i], curvatureGray(m_curvature, i));
284 }
285 ++m_colorGeneration;
286}
287
288//=============================================================================================================
289
291{
292 m_stcColors.clear();
293 m_visMode = ModeSurface;
294 updateVertexColors();
295}
296
297//=============================================================================================================
298
300{
301 m_baseColor = useDefault ? m_defaultColor : Qt::white;
302 updateVertexColors();
303}
304
305//=============================================================================================================
306
307void BrainSurface::setColor(const QColor& color)
308{
309 m_defaultColor = color;
310 m_baseColor = color;
311 updateVertexColors();
312}
313
314void BrainSurface::updateVertexColors()
315{
316 // Cortex: curvature grey; other surfaces (BEM, sensors, ...): the base colour
317 if (!m_curvature.isEmpty() && m_curvature.size() == m_colors.size()) {
318 for (int i = 0; i < m_colors.size(); ++i) {
319 const uint32_t val = curvatureGray(m_curvature, i);
320 m_colors[i] = packABGR(val, val, val, val);
321 }
322 } else {
323 m_colors.fill(packABGR(m_baseColor.red(), m_baseColor.green(), m_baseColor.blue(), m_baseColor.alpha()));
324 }
325
326 // Source-estimate colours stay in the colour stream: each view selects its overlay by uniform
327 const int n = qMin(m_stcColors.size(), m_colors.size());
328 for (int i = 0; i < n; ++i) {
329 m_colors[i] = overCortex(m_stcColors[i], curvatureGray(m_curvature, i));
330 }
331 ++m_colorGeneration;
332}
333
335{
336 float minVal = std::numeric_limits<float>::max();
337 for (const auto& v : m_vertexData) {
338 if (v.pos.x() < minVal)
339 minVal = v.pos.x();
340 }
341 return minVal;
342}
343
345{
346 float maxVal = std::numeric_limits<float>::lowest();
347 for (const auto& v : m_vertexData) {
348 if (v.pos.x() > maxVal)
349 maxVal = v.pos.x();
350 }
351 return maxVal;
352}
353
354void BrainSurface::translateX(float offset)
355{
356 for (auto& v : m_vertexData) {
357 v.pos.setX(v.pos.x() + offset);
358 }
359 markGeometryDirty();
360}
361
362//=============================================================================================================
363
364void BrainSurface::transform(const QMatrix4x4& m)
365{
366 // Extract 3x3 normal matrix (inverse transpose of upper-left 3x3)
367 // QMatrix4x4::normalMatrix() returns QMatrix3x3.
368 QMatrix3x3 normalMat = m.normalMatrix();
369
370 for (auto& v : m_vertexData) {
371 // Transform position
372 v.pos = m.map(v.pos);
373
374 // Transform normal
375 // Note: QMatrix3x3 * QVector3D isn't directly supported by some Qt versions conveniently,
376 // but let's assume standard multiplication works or do manually.
377 // Actually QVector3D operator*(QMatrix4x4) exists but is row-vector mul.
378 // QMatrix4x4 operator*(QVector3D) is standard column-vector mul.
379
380 // Use generic map method or just manual multiply if needed.
381 // Easier: mapVector for vectors (ignores translation) but needs to be normal matrix for non-uniform scales.
382 // If scale is uniform, mapVector is fine.
383
384 // Let's do it manually to be safe with QMatrix3x3
385 const float* d = normalMat.constData();
386 float nx = d[0] * v.norm.x() + d[3] * v.norm.y() + d[6] * v.norm.z();
387 float ny = d[1] * v.norm.x() + d[4] * v.norm.y() + d[7] * v.norm.z();
388 float nz = d[2] * v.norm.x() + d[5] * v.norm.y() + d[8] * v.norm.z();
389 v.norm = QVector3D(nx, ny, nz).normalized();
390 }
391 markGeometryDirty();
392}
393
394//=============================================================================================================
395
396void BrainSurface::applyTransform(const QMatrix4x4& m)
397{
398 m_vertexData = m_originalVertexData;
399 if (!m.isIdentity()) {
400 transform(m);
401 } else {
402 markGeometryDirty();
403 }
404}
405
406//=============================================================================================================
407
408std::vector<Eigen::VectorXi> BrainSurface::computeNeighbors() const
409{
410 // Use temporary std::vector<std::set<int>> for dedup during construction
411 std::vector<std::set<int>> tempNeighbors(m_vertexData.size());
412
413 // Triangles are stored in m_indexData as triplets
414 for (int i = 0; i + 2 < m_indexData.size(); i += 3) {
415 int v0 = m_indexData[i];
416 int v1 = m_indexData[i + 1];
417 int v2 = m_indexData[i + 2];
418
419 // Add bidirectional edges (set handles dedup)
420 tempNeighbors[v0].insert(v1);
421 tempNeighbors[v0].insert(v2);
422 tempNeighbors[v1].insert(v0);
423 tempNeighbors[v1].insert(v2);
424 tempNeighbors[v2].insert(v0);
425 tempNeighbors[v2].insert(v1);
426 }
427
428 // Convert to std::vector<VectorXi>
429 std::vector<Eigen::VectorXi> neighbors(tempNeighbors.size());
430 for (size_t k = 0; k < tempNeighbors.size(); ++k) {
431 const auto& s = tempNeighbors[k];
432 neighbors[k].resize(static_cast<Eigen::Index>(s.size()));
433 Eigen::Index idx = 0;
434 for (int val : s) {
435 neighbors[k][idx++] = val;
436 }
437 }
438
439 return neighbors;
440}
441
442//=============================================================================================================
443
444Eigen::MatrixX3f BrainSurface::verticesAsMatrix() const
445{
446 Eigen::MatrixX3f mat(m_vertexData.size(), 3);
447 for (int i = 0; i < m_vertexData.size(); ++i) {
448 mat(i, 0) = m_vertexData[i].pos.x();
449 mat(i, 1) = m_vertexData[i].pos.y();
450 mat(i, 2) = m_vertexData[i].pos.z();
451 }
452 return mat;
453}
454
455
456//=============================================================================================================
457
458void BrainSurface::boundingBox(QVector3D& min, QVector3D& max) const
459{
460 if (!m_bAABBDirty) {
461 min = m_aabbMin;
462 max = m_aabbMax;
463 return;
464 }
465
466 if (m_vertexData.isEmpty()) {
467 min = QVector3D(0, 0, 0);
468 max = QVector3D(0, 0, 0);
469 return;
470 }
471
472 min = m_vertexData[0].pos;
473 max = m_vertexData[0].pos;
474
475 for (const auto& v : m_vertexData) {
476 min.setX(std::min(min.x(), v.pos.x()));
477 min.setY(std::min(min.y(), v.pos.y()));
478 min.setZ(std::min(min.z(), v.pos.z()));
479
480 max.setX(std::max(max.x(), v.pos.x()));
481 max.setY(std::max(max.y(), v.pos.y()));
482 max.setZ(std::max(max.z(), v.pos.z()));
483 }
484
485 m_aabbMin = min;
486 m_aabbMax = max;
487 m_bAABBDirty = false;
488}
489
491{
492 struct Node
493 {
494 float min[3];
495 float max[3];
496 int first; // first triangle (leaf) or left child (inner node)
497 int count; // triangles in a leaf, 0 for an inner node
498 int right; // right child of an inner node
499 };
500 std::vector<Node> nodes;
501 std::vector<int> triangles; // triangle indices, grouped by leaf
503 quint64 indexGeneration = 0;
504};
505
506namespace
507{
508constexpr int kBvhLeafSize = 4;
509// Pads node bounds so a ray grazing a flat, axis-aligned box still enters it
510constexpr float kBvhPad = 1e-6f;
511
512void growBox(float* lo, float* hi, const QVector3D& p)
513{
514 for (int a = 0; a < 3; ++a) {
515 lo[a] = std::min(lo[a], p[a]);
516 hi[a] = std::max(hi[a], p[a]);
517 }
518}
519
520// Ray parameter where the ray enters the box, or +inf when it misses it before maxT
521float enterBox(const float* lo, const float* hi, const float* origin, const float* invDir, float maxT)
522{
523 float tNear = 0.0f;
524 float tFar = maxT;
525 for (int a = 0; a < 3; ++a) {
526 float t0 = (lo[a] - origin[a]) * invDir[a];
527 float t1 = (hi[a] - origin[a]) * invDir[a];
528 if (t0 > t1)
529 std::swap(t0, t1);
530 // NaN (origin on a slab plane of a ray parallel to it) keeps the previous bound
531 tNear = t0 > tNear ? t0 : tNear;
532 tFar = t1 < tFar ? t1 : tFar;
533 if (tNear > tFar)
534 return std::numeric_limits<float>::infinity();
535 }
536 return tNear;
537}
538} // namespace
539
540//=============================================================================================================
541
542const BrainSurface::Bvh& BrainSurface::bvh() const
543{
544 if (m_bvh && m_bvh->geometryGeneration == m_geometryGeneration && m_bvh->indexGeneration == m_indexGeneration)
545 return *m_bvh;
546
547 auto tree = std::make_shared<Bvh>();
548 tree->geometryGeneration = m_geometryGeneration;
549 tree->indexGeneration = m_indexGeneration;
550 const int triangleCount = int(m_indexData.size() / 3);
551
552 // Triangle boxes in one contiguous array that the build partitions in place
553 struct Item
554 {
555 float lo[3];
556 int triangle;
557 float hi[3];
558 float pad;
559 };
560 std::vector<Item> items(static_cast<size_t>(triangleCount));
561 for (int t = 0; t < triangleCount; ++t) {
562 Item& item = items[size_t(t)];
563 item.triangle = t;
564 for (int a = 0; a < 3; ++a) {
565 item.lo[a] = std::numeric_limits<float>::max();
566 item.hi[a] = std::numeric_limits<float>::lowest();
567 }
568 for (int k = 0; k < 3; ++k)
569 growBox(item.lo, item.hi, m_vertexData[m_indexData[3 * t + k]].pos);
570 }
571 tree->nodes.reserve(size_t(std::max(1, 2 * triangleCount / kBvhLeafSize + 1)));
572
573 // Top-down build: split each node at the median box centre along its widest centre axis
574 struct Task
575 {
576 int node;
577 int begin;
578 int end;
579 };
580 std::vector<Task> tasks;
581 tree->nodes.push_back({});
582 tasks.push_back({0, 0, triangleCount});
583 while (!tasks.empty()) {
584 const Task task = tasks.back();
585 tasks.pop_back();
586 Bvh::Node node{};
587 float cLo[3];
588 float cHi[3];
589 for (int a = 0; a < 3; ++a) {
590 node.min[a] = cLo[a] = std::numeric_limits<float>::max();
591 node.max[a] = cHi[a] = std::numeric_limits<float>::lowest();
592 }
593 for (int i = task.begin; i < task.end; ++i) {
594 const Item& item = items[size_t(i)];
595 for (int a = 0; a < 3; ++a) {
596 node.min[a] = std::min(node.min[a], item.lo[a]);
597 node.max[a] = std::max(node.max[a], item.hi[a]);
598 const float c = item.lo[a] + item.hi[a];
599 cLo[a] = std::min(cLo[a], c);
600 cHi[a] = std::max(cHi[a], c);
601 }
602 }
603 for (int a = 0; a < 3; ++a) {
604 node.min[a] -= kBvhPad;
605 node.max[a] += kBvhPad;
606 }
607
608 const int axis = (cHi[1] - cLo[1] > cHi[0] - cLo[0]) ? ((cHi[2] - cLo[2] > cHi[1] - cLo[1]) ? 2 : 1)
609 : ((cHi[2] - cLo[2] > cHi[0] - cLo[0]) ? 2 : 0);
610 if (task.end - task.begin <= kBvhLeafSize || cHi[axis] <= cLo[axis]) {
611 node.first = task.begin;
612 node.count = task.end - task.begin;
613 tree->nodes[size_t(task.node)] = node;
614 continue;
615 }
616 const int mid = (task.begin + task.end) / 2;
617 std::nth_element(items.begin() + task.begin, items.begin() + mid, items.begin() + task.end,
618 [axis](const Item& l, const Item& r) { return l.lo[axis] + l.hi[axis] < r.lo[axis] + r.hi[axis]; });
619 const int left = int(tree->nodes.size());
620 const int right = left + 1;
621 tree->nodes.resize(tree->nodes.size() + 2);
622 node.first = left;
623 node.count = 0;
624 node.right = right;
625 tree->nodes[size_t(task.node)] = node;
626 tasks.push_back({right, mid, task.end});
627 tasks.push_back({left, task.begin, mid});
628 }
629
630 tree->triangles.resize(size_t(triangleCount));
631 for (int i = 0; i < triangleCount; ++i)
632 tree->triangles[size_t(i)] = items[size_t(i)].triangle;
633
634 m_bvh = std::move(tree);
635 return *m_bvh;
636}
637
638//=============================================================================================================
639
640bool BrainSurface::intersects(const QVector3D& rayOrigin, const QVector3D& rayDir, float& dist, int& vertexIdx) const
641{
642 vertexIdx = -1;
643 if (m_vertexData.isEmpty() || m_indexData.size() < 3)
644 return false;
645
646 const Bvh& tree = bvh();
647 const float origin[3] = {rayOrigin.x(), rayOrigin.y(), rayOrigin.z()};
648 const float invDir[3] = {1.0f / rayDir.x(), 1.0f / rayDir.y(), 1.0f / rayDir.z()};
649 const double originX = rayOrigin.x(), originY = rayOrigin.y(), originZ = rayOrigin.z();
650 const double dirX = rayDir.x(), dirY = rayDir.y(), dirZ = rayDir.z();
651
652 double closestDist = std::numeric_limits<double>::max();
653 int closestTriangle = -1;
654
655 // Möller–Trumbore in double precision on the triangles of the leaves the ray enters, nearest
656 // first. Median splits keep the depth near log2(triangles / leaf size), far below the stack size.
657 int stack[64];
658 int top = 0;
659 stack[top++] = 0;
660 while (top > 0) {
661 const Bvh::Node& node = tree.nodes[size_t(stack[--top])];
662 const float limit = closestDist < double(std::numeric_limits<float>::max()) ? float(closestDist) : std::numeric_limits<float>::max();
663 if (enterBox(node.min, node.max, origin, invDir, limit) == std::numeric_limits<float>::infinity())
664 continue;
665 if (node.count == 0) {
666 const Bvh::Node& l = tree.nodes[size_t(node.first)];
667 const Bvh::Node& r = tree.nodes[size_t(node.right)];
668 const float tl = enterBox(l.min, l.max, origin, invDir, limit);
669 const float tr = enterBox(r.min, r.max, origin, invDir, limit);
670 constexpr float miss = std::numeric_limits<float>::infinity();
671 // Push the farther child first so the nearer one is visited next
672 const bool leftFirst = tl <= tr;
673 const int nearChild = leftFirst ? node.first : node.right;
674 const int farChild = leftFirst ? node.right : node.first;
675 if ((leftFirst ? tr : tl) != miss)
676 stack[top++] = farChild;
677 if ((leftFirst ? tl : tr) != miss)
678 stack[top++] = nearChild;
679 continue;
680 }
681
682 for (int i = node.first; i < node.first + node.count; ++i) {
683 const int t = tree.triangles[size_t(i)];
684 const QVector3D& v0q = m_vertexData[m_indexData[3 * t]].pos;
685 const QVector3D& v1q = m_vertexData[m_indexData[3 * t + 1]].pos;
686 const QVector3D& v2q = m_vertexData[m_indexData[3 * t + 2]].pos;
687
688 const double v0x = v0q.x(), v0y = v0q.y(), v0z = v0q.z();
689 const double edge1x = v1q.x() - v0x, edge1y = v1q.y() - v0y, edge1z = v1q.z() - v0z;
690 const double edge2x = v2q.x() - v0x, edge2y = v2q.y() - v0y, edge2z = v2q.z() - v0z;
691
692 const double hx = dirY * edge2z - dirZ * edge2y;
693 const double hy = dirZ * edge2x - dirX * edge2z;
694 const double hz = dirX * edge2y - dirY * edge2x;
695
696 const double a = edge1x * hx + edge1y * hy + edge1z * hz;
697 if (std::abs(a) < 1e-18)
698 continue; // Parallel to the triangle
699
700 const double f = 1.0 / a;
701 const double sx = originX - v0x, sy = originY - v0y, sz = originZ - v0z;
702 const double u = f * (sx * hx + sy * hy + sz * hz);
703 if (u < -1e-7 || u > 1.0000001)
704 continue;
705
706 const double qx = sy * edge1z - sz * edge1y;
707 const double qy = sz * edge1x - sx * edge1z;
708 const double qz = sx * edge1y - sy * edge1x;
709
710 const double v = f * (dirX * qx + dirY * qy + dirZ * qz);
711 if (v < -1e-7 || u + v > 1.0000001)
712 continue;
713
714 const double hitT = f * (edge2x * qx + edge2y * qy + edge2z * qz);
715 if (hitT > 1e-7 && hitT < closestDist) {
716 closestDist = hitT;
717 closestTriangle = t;
718 }
719 }
720 }
721
722 if (closestTriangle < 0)
723 return false;
724
725 // The vertex of the hit triangle nearest to the hit point, used for region lookup
726 const QVector3D hit = rayOrigin + float(closestDist) * rayDir;
727 float best = std::numeric_limits<float>::max();
728 for (int k = 0; k < 3; ++k) {
729 const int v = int(m_indexData[3 * closestTriangle + k]);
730 const float d = (m_vertexData[v].pos - hit).lengthSquared();
731 if (d < best) {
732 best = d;
733 vertexIdx = v;
734 }
735 }
736 dist = float(closestDist);
737 return true;
738}
739
740//=============================================================================================================
741
742QString BrainSurface::getAnnotationLabel(int vertexIdx) const
743{
744 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexLabelIds.size()) {
745 return "";
746 }
747 const int labelId = m_vertexLabelIds[vertexIdx];
748 if (labelId == -1)
749 return "Unknown";
750
751 const FSLIB::FsColortable& ct = m_annotation.getColortable();
752 for (int i = 0; i < ct.numEntries; ++i) {
753 if (ct.table(i, 4) == labelId) {
754 QString name = ct.struct_names[i];
755 // Remove null characters and trailing whitespace that might cause "strange signs"
756 while (!name.isEmpty() && (name.endsWith('\0') || name.endsWith(' '))) {
757 name.chop(1);
758 }
759 return name;
760 }
761 }
762
763 return "Unknown";
764}
765
767{
768 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexLabelIds.size())
769 return -1;
770 return m_vertexLabelIds[vertexIdx];
771}
772
774{
775 m_selectedRegionId = regionId;
776}
777
778void BrainSurface::setSelected(bool selected)
779{
780 m_selected = selected;
781}
782
783void BrainSurface::setSelectedVertexRange(int start, int count)
784{
785 m_selectedVertexStart = start;
786 m_selectedVertexCount = count;
787}
788
789std::optional<uint32_t> BrainSurface::selectedLabel() const
790{
791 if (m_selectedVertexStart >= 0 && m_selectedVertexCount > 0 && m_selectedVertexStart < m_annotationColors.size())
792 return m_annotationColors[m_selectedVertexStart];
793 if (m_selected && m_selectedRegionId != -1 && m_hasAnnotation) {
794 const FSLIB::FsColortable& ct = m_annotation.getColortable();
795 for (int c = 0; c < ct.numEntries; ++c) {
796 if (ct.table(c, 4) == m_selectedRegionId)
797 return packABGR(ct.table(c, 0), ct.table(c, 1), ct.table(c, 2));
798 }
799 }
800 return std::nullopt;
801}
802
803} // namespace DISP3DLIB
Renderable cortical / BEM mesh: CPU geometry, colour and annotation streams for the renderer.
3-D brain visualisation using the Qt RHI rendering backend.
uint32_t packABGR(uint32_t r, uint32_t g, uint32_t b, uint32_t a=0xFF)
Definition rendertypes.h:54
CPU-side contents of one GPU buffer of a renderable.
Definition rendertypes.h:99
Geometry of one brain surface vertex: position and normal.
int getAnnotationLabelId(int vertexIdx) const
void setVisible(bool visible)
Eigen::MatrixX3f vertexPositions() const
Eigen::MatrixX3f verticesAsMatrix() const
void setSelectedVertexRange(int start, int count)
void fromSurface(const FSLIB::FsSurface &surf)
Eigen::MatrixX3f vertexNormals() const
void applyTransform(const QMatrix4x4 &m)
GpuStream geometryStream() const
bool intersects(const QVector3D &rayOrigin, const QVector3D &rayDir, float &dist, int &vertexIdx) const
void setColor(const QColor &color)
QString getAnnotationLabel(int vertexIdx) const
static constexpr VisualizationMode ModeSurface
void labelElements(int verticesPerElement)
DISP3DLIB::VisualizationMode VisualizationMode
bool loadAnnotation(const QString &path)
void boundingBox(QVector3D &min, QVector3D &max) const
GpuStream indexStream() const
void transform(const QMatrix4x4 &m)
void fromBemSurface(const MNELIB::MNEBemSurface &surf, const QColor &color=Qt::white)
void setUseDefaultColor(bool useDefault)
std::optional< uint32_t > selectedLabel() const
void setSelectedRegion(int regionId)
GpuStream colorStream() const
void addAnnotation(const FSLIB::FsAnnotation &annotation)
void applySourceEstimateColors(const QVector< uint32_t > &colors)
static constexpr VisualizationMode ModeSourceEstimate
GpuStream annotationStream() const
void translateX(float offset)
std::vector< Eigen::VectorXi > computeNeighbors() const
void setSelected(bool selected)
void createFromData(const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3i &triangles, const QColor &color)
void setVisualizationMode(VisualizationMode mode)
Single-hemisphere FreeSurfer parcellation: vertex → region label plus embedded colortable.
static bool read(const QString &subject_id, qint32 hemi, const QString &atlas, const QString &subjects_dir, FsAnnotation &p_Annotation)
Eigen::VectorXi & getVertices()
FreeSurfer colour lookup table: region name + RGBA + packed label, indexed by entry.
QStringList struct_names
Eigen::MatrixXi table
In-memory FreeSurfer triangular cortical surface for one hemisphere.
Definition fs_surface.h:94
const Eigen::MatrixX3f & nn() const
Definition fs_surface.h:392
const Eigen::MatrixX3i & tris() const
Definition fs_surface.h:385
const Eigen::MatrixX3f & rr() const
Definition fs_surface.h:378
const Eigen::VectorXf & curv() const
Definition fs_surface.h:399
static Eigen::MatrixX3f compute_normals(const Eigen::MatrixX3f &rr, const Eigen::MatrixX3i &tris)
BEM surface provides geometry information.