32uint32_t withAlpha(uint32_t color, uint32_t alpha)
34 return (color & 0x00FFFFFFu) | ((alpha & 0xFFu) << 24);
37uint32_t curvatureGray(
const QVector<float>& curvature,
int index)
39 return (index >= 0 && index < curvature.size() && curvature[index] > 0.0f)
45uint32_t overCortex(uint32_t overlay, uint32_t gray)
47 const uint32_t a = overlay >> 24;
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;
53 return withAlpha(rgb, gray);
69void BrainSurface::markGeometryDirty()
71 ++m_geometryGeneration;
79 return {m_vertexData.constData(), quint32(m_vertexData.size() *
sizeof(
VertexData)), m_geometryGeneration};
84 return {m_colors.constData(), quint32(m_colors.size() *
sizeof(uint32_t)), m_colorGeneration};
89 return {m_annotationColors.constData(), quint32(m_annotationColors.size() *
sizeof(uint32_t)), m_annotationGeneration};
94 return {m_indexData.constData(), quint32(m_indexData.size() *
sizeof(uint32_t)), m_indexGeneration};
99void BrainSurface::setGeometry(QVector<VertexData> vertices, QVector<uint32_t> indices,
const QColor& color)
101 m_vertexData = std::move(vertices);
102 m_originalVertexData = m_vertexData;
103 m_indexData = std::move(indices);
104 m_indexCount = m_indexData.size();
107 m_defaultColor = 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;
115 updateVertexColors();
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];
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);
146 const int nVerts = surf.
rr.rows();
147 Eigen::MatrixX3f nn = surf.
nn;
148 if (nn.rows() != nVerts) {
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);
166void BrainSurface::createFromData(
const Eigen::MatrixX3f& vertices,
const Eigen::MatrixX3f& normals,
const Eigen::MatrixX3i& triangles,
const QColor& color)
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);
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();
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();
212 qWarning() <<
"BrainSurface: Failed to load annotation from" << path;
221 m_annotation = annotation;
222 m_hasAnnotation =
true;
225 const Eigen::VectorXi& vertices = m_annotation.
getVertices();
226 const Eigen::VectorXi& labelIds = m_annotation.getLabelIds();
228 QHash<int, uint32_t> labelColors;
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())
237 m_vertexLabelIds[v] = labelIds(i);
238 m_annotationColors[v] = labelColors.value(labelIds(i), 0u);
240 ++m_annotationGeneration;
247 if (verticesPerElement <= 0)
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);
254 ++m_annotationGeneration;
277 m_stcColors = colors;
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));
294 updateVertexColors();
301 m_baseColor = useDefault ? m_defaultColor : Qt::white;
302 updateVertexColors();
309 m_defaultColor = color;
311 updateVertexColors();
314void BrainSurface::updateVertexColors()
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);
323 m_colors.fill(
packABGR(m_baseColor.red(), m_baseColor.green(), m_baseColor.blue(), m_baseColor.alpha()));
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));
336 float minVal = std::numeric_limits<float>::max();
337 for (
const auto& v : m_vertexData) {
338 if (v.pos.x() < minVal)
346 float maxVal = std::numeric_limits<float>::lowest();
347 for (
const auto& v : m_vertexData) {
348 if (v.pos.x() > maxVal)
356 for (
auto& v : m_vertexData) {
357 v.pos.setX(v.pos.x() + offset);
368 QMatrix3x3 normalMat = m.normalMatrix();
370 for (
auto& v : m_vertexData) {
372 v.pos = m.map(v.pos);
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();
398 m_vertexData = m_originalVertexData;
399 if (!m.isIdentity()) {
411 std::vector<std::set<int>> tempNeighbors(m_vertexData.size());
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];
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);
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;
435 neighbors[k][idx++] = val;
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();
466 if (m_vertexData.isEmpty()) {
467 min = QVector3D(0, 0, 0);
468 max = QVector3D(0, 0, 0);
472 min = m_vertexData[0].pos;
473 max = m_vertexData[0].pos;
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()));
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()));
487 m_bAABBDirty =
false;
508constexpr int kBvhLeafSize = 4;
510constexpr float kBvhPad = 1e-6f;
512void growBox(
float* lo,
float* hi,
const QVector3D& p)
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]);
521float enterBox(
const float* lo,
const float* hi,
const float* origin,
const float* invDir,
float 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];
531 tNear = t0 > tNear ? t0 : tNear;
532 tFar = t1 < tFar ? t1 : tFar;
534 return std::numeric_limits<float>::infinity();
544 if (m_bvh && m_bvh->geometryGeneration == m_geometryGeneration && m_bvh->indexGeneration == m_indexGeneration)
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);
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)];
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();
568 for (
int k = 0; k < 3; ++k)
569 growBox(item.lo, item.hi, m_vertexData[m_indexData[3 * t + k]].pos);
571 tree->nodes.reserve(
size_t(std::max(1, 2 * triangleCount / kBvhLeafSize + 1)));
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();
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();
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);
603 for (
int a = 0; a < 3; ++a) {
604 node.min[a] -= kBvhPad;
605 node.max[a] += kBvhPad;
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;
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);
625 tree->nodes[size_t(task.node)] = node;
626 tasks.push_back({right, mid, task.end});
627 tasks.push_back({left, task.begin, mid});
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;
634 m_bvh = std::move(tree);
643 if (m_vertexData.isEmpty() || m_indexData.size() < 3)
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();
652 double closestDist = std::numeric_limits<double>::max();
653 int closestTriangle = -1;
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())
665 if (node.
count == 0) {
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();
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;
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;
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;
692 const double hx = dirY * edge2z - dirZ * edge2y;
693 const double hy = dirZ * edge2x - dirX * edge2z;
694 const double hz = dirX * edge2y - dirY * edge2x;
696 const double a = edge1x * hx + edge1y * hy + edge1z * hz;
697 if (std::abs(a) < 1e-18)
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)
706 const double qx = sy * edge1z - sz * edge1y;
707 const double qy = sz * edge1x - sx * edge1z;
708 const double qz = sx * edge1y - sy * edge1x;
710 const double v = f * (dirX * qx + dirY * qy + dirZ * qz);
711 if (v < -1e-7 || u + v > 1.0000001)
714 const double hitT = f * (edge2x * qx + edge2y * qy + edge2z * qz);
715 if (hitT > 1e-7 && hitT < closestDist) {
722 if (closestTriangle < 0)
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();
736 dist = float(closestDist);
744 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexLabelIds.size()) {
747 const int labelId = m_vertexLabelIds[vertexIdx];
753 if (ct.
table(i, 4) == labelId) {
756 while (!name.isEmpty() && (name.endsWith(
'\0') || name.endsWith(
' '))) {
768 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexLabelIds.size())
770 return m_vertexLabelIds[vertexIdx];
775 m_selectedRegionId = regionId;
780 m_selected = selected;
785 m_selectedVertexStart = start;
786 m_selectedVertexCount = count;
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) {
796 if (ct.
table(c, 4) == m_selectedRegionId)
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)
CPU-side contents of one GPU buffer of a renderable.
std::vector< Node > nodes
std::vector< int > triangles
quint64 geometryGeneration
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)
void clearSourceEstimateColors()
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.
In-memory FreeSurfer triangular cortical surface for one hemisphere.
const Eigen::MatrixX3f & nn() const
const Eigen::MatrixX3i & tris() const
const Eigen::MatrixX3f & rr() const
const Eigen::VectorXf & curv() const
static Eigen::MatrixX3f compute_normals(const Eigen::MatrixX3f &rr, const Eigen::MatrixX3i &tris)
BEM surface provides geometry information.