28uint32_t withAlpha(uint32_t color, uint32_t alpha)
30 return (color & 0x00FFFFFFu) | ((alpha & 0xFFu) << 24);
33uint32_t curvatureGray(
const QVector<float>& curvature,
int index)
35 return (index >= 0 && index < curvature.size() && curvature[index] > 0.0f)
41uint32_t overCortex(uint32_t overlay, uint32_t gray)
43 const uint32_t a = overlay >> 24;
45 for (
int shift = 0; shift < 24; shift += 8) {
46 const uint32_t c = (overlay >> shift) & 0xFFu;
47 rgb |= ((c * a + gray * (255u - a) + 127u) / 255u) << shift;
49 return withAlpha(rgb, gray);
67void BrainSurface::markVertexDirty()
94 return m_gpu->vertexBuffer.get();
98 return m_gpu->indexBuffer.get();
105 m_vertexData.clear();
108 const Eigen::MatrixXf& rr = surf.
rr();
109 const Eigen::MatrixXf& nn = surf.
nn();
110 const Eigen::MatrixXi& tris = surf.
tris();
111 const Eigen::VectorXf& curv = surf.
curv();
112 m_curvature.resize(curv.size());
113 for (
int i = 0; i < curv.size(); ++i)
114 m_curvature[i] = curv[i];
117 m_vertexData.reserve(rr.rows());
119 for (
int i = 0; i < rr.rows(); ++i) {
121 v.
pos = QVector3D(rr(i, 0), rr(i, 1), rr(i, 2));
122 v.
norm = QVector3D(nn(i, 0), nn(i, 1), nn(i, 2));
123 v.
color = 0xFFFFFFFF;
125 m_vertexData.append(v);
128 m_indexData.reserve(tris.rows() * 3);
129 for (
int i = 0; i < tris.rows(); ++i) {
130 m_indexData.append(tris(i, 0));
131 m_indexData.append(tris(i, 1));
132 m_indexData.append(tris(i, 2));
134 m_indexCount = m_indexData.size();
140 updateVertexColors();
142 m_originalVertexData = m_vertexData;
151 m_vertexData.clear();
155 int nVerts = surf.
rr.rows();
156 m_vertexData.reserve(nVerts);
159 Eigen::MatrixX3f nn = surf.
nn;
160 if (nn.rows() != nVerts) {
164 m_defaultColor = color;
166 uint32_t colorVal =
packABGR(color.red(), color.green(), color.blue(), color.alpha());
168 for (
int i = 0; i < nVerts; ++i) {
170 v.
pos = QVector3D(surf.
rr(i, 0), surf.
rr(i, 1), surf.
rr(i, 2));
171 v.
norm = QVector3D(nn(i, 0), nn(i, 1), nn(i, 2));
174 m_vertexData.append(v);
177 int nTris = surf.
itris.rows();
178 m_indexData.reserve(nTris * 3);
179 for (
int i = 0; i < nTris; ++i) {
180 m_indexData.append(surf.
itris(i, 0));
181 m_indexData.append(surf.
itris(i, 1));
182 m_indexData.append(surf.
itris(i, 2));
184 m_indexCount = m_indexData.size();
186 m_originalVertexData = m_vertexData;
195void BrainSurface::createFromData(
const Eigen::MatrixX3f& vertices,
const Eigen::MatrixX3f& normals,
const Eigen::MatrixX3i& triangles,
const QColor& color)
197 m_vertexData.clear();
201 int nVerts = vertices.rows();
202 m_vertexData.reserve(nVerts);
204 m_defaultColor = color;
206 uint32_t colorVal =
packABGR(color.red(), color.green(), color.blue(), color.alpha());
208 for (
int i = 0; i < nVerts; ++i) {
210 v.
pos = QVector3D(vertices(i, 0), vertices(i, 1), vertices(i, 2));
211 v.
norm = QVector3D(normals(i, 0), normals(i, 1), normals(i, 2));
214 m_vertexData.append(v);
217 int nTris = triangles.rows();
218 m_indexData.reserve(nTris * 3);
219 for (
int i = 0; i < nTris; ++i) {
220 m_indexData.append(triangles(i, 0));
221 m_indexData.append(triangles(i, 1));
222 m_indexData.append(triangles(i, 2));
224 m_indexCount = m_indexData.size();
226 m_originalVertexData = m_vertexData;
234 Eigen::MatrixX3f rr(m_vertexData.size(), 3);
235 for (
int i = 0; i < m_vertexData.size(); ++i) {
236 rr(i, 0) = m_vertexData[i].pos.x();
237 rr(i, 1) = m_vertexData[i].pos.y();
238 rr(i, 2) = m_vertexData[i].pos.z();
247 Eigen::MatrixX3f nn(m_vertexData.size(), 3);
248 for (
int i = 0; i < m_vertexData.size(); ++i) {
249 nn(i, 0) = m_vertexData[i].norm.x();
250 nn(i, 1) = m_vertexData[i].norm.y();
251 nn(i, 2) = m_vertexData[i].norm.z();
261 qWarning() <<
"BrainSurface: Failed to load annotation from" << path;
264 m_hasAnnotation =
true;
265 updateVertexColors();
272 m_annotation = annotation;
273 m_hasAnnotation =
true;
274 updateVertexColors();
290 if (m_visMode == mode)
299 updateVertexColors();
308 m_stcColors = colors;
313 for (
int i = 0; i < qMin(colors.size(), m_vertexData.size()); ++i) {
314 m_vertexData[i].color = overCortex(colors[i], curvatureGray(m_curvature, i));
326 updateVertexColors();
334 m_baseColor = useDefault ? m_defaultColor : Qt::white;
335 updateVertexColors();
342 m_defaultColor = color;
344 updateVertexColors();
347void BrainSurface::updateVertexColors()
354 if (!m_curvature.isEmpty() && m_curvature.size() == m_vertexData.size()) {
355 for (
int i = 0; i < m_vertexData.size(); ++i) {
356 const uint32_t val = curvatureGray(m_curvature, i);
357 m_vertexData[i].color =
packABGR(val, val, val, val);
362 const uint32_t baseVal =
packABGR(m_baseColor.red(), m_baseColor.green(),
363 m_baseColor.blue(), m_baseColor.alpha());
364 for (
int i = 0; i < m_vertexData.size(); ++i) {
365 m_vertexData[i].color = baseVal;
375 if (!m_stcColors.isEmpty()) {
376 for (
int i = 0; i < qMin(m_stcColors.size(), m_vertexData.size()); ++i) {
377 m_vertexData[i].color = overCortex(m_stcColors[i], curvatureGray(m_curvature, i));
382 for (
auto& v : m_vertexData) {
383 v.colorAnnotation = 0x00000000;
386 if (m_hasAnnotation && !m_vertexData.isEmpty()) {
387 const Eigen::VectorXi& vertices = m_annotation.getVertices();
388 const Eigen::VectorXi& labelIds = m_annotation.getLabelIds();
389 const FSLIB::FsColortable& ct = m_annotation.getColortable();
391 for (
int i = 0; i < labelIds.rows(); ++i) {
392 int vertexIdx = vertices(i);
393 if (vertexIdx >= 0 && vertexIdx < m_vertexData.size()) {
396 if (ct.
table(c, 4) == labelIds(i)) {
402 uint32_t r = ct.
table(colorIdx, 0);
403 uint32_t g = ct.
table(colorIdx, 1);
404 uint32_t b = ct.
table(colorIdx, 2);
405 m_vertexData[vertexIdx].colorAnnotation =
418 const uint32_t gold =
packABGR(255, 200, 60);
419 if (m_selectedRegionId != -1 && m_hasAnnotation) {
421 const Eigen::VectorXi& vertices = m_annotation.getVertices();
422 const Eigen::VectorXi& labelIds = m_annotation.getLabelIds();
423 for (
int i = 0; i < labelIds.rows(); ++i) {
424 if (labelIds(i) == m_selectedRegionId) {
425 int idx = vertices(i);
426 if (idx >= 0 && idx < m_vertexData.size()) {
427 m_vertexData[idx].color = gold;
428 m_vertexData[idx].colorAnnotation = gold;
432 }
else if (m_selectedVertexStart >= 0 && m_selectedVertexCount > 0) {
434 const int end = qMin(m_selectedVertexStart + m_selectedVertexCount,
435 m_vertexData.size());
436 for (
int i = m_selectedVertexStart; i < end; ++i) {
437 m_vertexData[i].color = gold;
449 float minVal = std::numeric_limits<float>::max();
450 for (
const auto& v : m_vertexData) {
451 if (v.pos.x() < minVal)
459 float maxVal = std::numeric_limits<float>::lowest();
460 for (
const auto& v : m_vertexData) {
461 if (v.pos.x() > maxVal)
469 for (
auto& v : m_vertexData) {
470 v.pos.setX(v.pos.x() + offset);
482 QMatrix3x3 normalMat = m.normalMatrix();
484 for (
auto& v : m_vertexData) {
486 v.pos = m.map(v.pos);
499 const float* d = normalMat.constData();
500 float nx = d[0] * v.norm.x() + d[3] * v.norm.y() + d[6] * v.norm.z();
501 float ny = d[1] * v.norm.x() + d[4] * v.norm.y() + d[7] * v.norm.z();
502 float nz = d[2] * v.norm.x() + d[5] * v.norm.y() + d[8] * v.norm.z();
503 v.norm = QVector3D(nx, ny, nz).normalized();
514 for (
int i = 0; i < qMin(m_vertexData.size(), m_originalVertexData.size()); ++i) {
515 m_vertexData[i].pos = m_originalVertexData[i].pos;
516 m_vertexData[i].norm = m_originalVertexData[i].norm;
518 if (!m.isIdentity()) {
530 const bool needsCreate = !m_gpu->vertexBuffer || !m_gpu->indexBuffer;
538 if (!needsCreate && !m_gpu->dirty) {
540 u->uploadStaticBuffer(m_gpu->vertexBuffer.get(), m_vertexData.constData());
541 u->uploadStaticBuffer(m_gpu->indexBuffer.get(), m_indexData.constData());
551 if (!m_gpu->dirty && !needsCreate)
555 const quint32 vbufSize =
static_cast<quint32
>(m_vertexData.size() *
sizeof(
VertexData));
556 const quint32 ibufSize =
static_cast<quint32
>(m_indexData.size() *
sizeof(uint32_t));
559 if (!m_gpu->vertexBuffer) {
560 m_gpu->vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, vbufSize));
561 m_gpu->vertexBuffer->create();
562 m_gpu->indexDirty =
true;
576 if (!m_gpu->vertexBuffer || m_gpu->dirty) {
577 m_gpu->vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, vbufSize));
578 m_gpu->vertexBuffer->create();
579 m_gpu->indexDirty =
true;
582 if (!m_gpu->indexBuffer) {
583 m_gpu->indexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, ibufSize));
584 m_gpu->indexBuffer->create();
585 m_gpu->indexDirty =
true;
588 u->uploadStaticBuffer(m_gpu->vertexBuffer.get(), m_vertexData.constData());
589 if (m_gpu->indexDirty) {
590 u->uploadStaticBuffer(m_gpu->indexBuffer.get(), m_indexData.constData());
591 m_gpu->indexDirty =
false;
593 m_gpu->dirty =
false;
601 std::vector<std::set<int>> tempNeighbors(m_vertexData.size());
604 for (
int i = 0; i + 2 < m_indexData.size(); i += 3) {
605 int v0 = m_indexData[i];
606 int v1 = m_indexData[i + 1];
607 int v2 = m_indexData[i + 2];
610 tempNeighbors[v0].insert(v1);
611 tempNeighbors[v0].insert(v2);
612 tempNeighbors[v1].insert(v0);
613 tempNeighbors[v1].insert(v2);
614 tempNeighbors[v2].insert(v0);
615 tempNeighbors[v2].insert(v1);
619 std::vector<Eigen::VectorXi> neighbors(tempNeighbors.size());
620 for (
size_t k = 0; k < tempNeighbors.size(); ++k) {
621 const auto& s = tempNeighbors[k];
622 neighbors[k].resize(
static_cast<Eigen::Index
>(s.size()));
623 Eigen::Index idx = 0;
625 neighbors[k][idx++] = val;
636 Eigen::MatrixX3f mat(m_vertexData.size(), 3);
637 for (
int i = 0; i < m_vertexData.size(); ++i) {
638 mat(i, 0) = m_vertexData[i].pos.x();
639 mat(i, 1) = m_vertexData[i].pos.y();
640 mat(i, 2) = m_vertexData[i].pos.z();
656 if (m_vertexData.isEmpty()) {
657 min = QVector3D(0, 0, 0);
658 max = QVector3D(0, 0, 0);
662 min = m_vertexData[0].pos;
663 max = m_vertexData[0].pos;
665 for (
const auto& v : m_vertexData) {
666 min.setX(std::min(min.x(), v.pos.x()));
667 min.setY(std::min(min.y(), v.pos.y()));
668 min.setZ(std::min(min.z(), v.pos.z()));
670 max.setX(std::max(max.x(), v.pos.x()));
671 max.setY(std::max(max.y(), v.pos.y()));
672 max.setZ(std::max(max.z(), v.pos.z()));
677 m_bAABBDirty =
false;
683 if (m_vertexData.isEmpty())
692 double origin[3] = {rayOrigin.x(), rayOrigin.y(), rayOrigin.z()};
693 double dir[3] = {rayDir.x(), rayDir.y(), rayDir.z()};
694 double minB[3] = {min.x() - eps, min.y() - eps, min.z() - eps};
695 double maxB[3] = {max.x() + eps, max.y() + eps, max.z() + eps};
698 double originX = origin[0], originY = origin[1], originZ = origin[2];
699 double dirX = dir[0], dirY = dir[1], dirZ = dir[2];
701 double tmin = -std::numeric_limits<double>::max();
702 double tmax = std::numeric_limits<double>::max();
704 for (
int i = 0; i < 3; ++i) {
705 if (std::abs(dir[i]) < 1e-15) {
706 if (origin[i] < minB[i] || origin[i] > maxB[i])
709 double t1 = (minB[i] - origin[i]) / dir[i];
710 double t2 = (maxB[i] - origin[i]) / dir[i];
726 double closestDist = std::numeric_limits<double>::max();
728 int closestVert = -1;
733 for (
int i = 0; i < m_indexData.size(); i += 3) {
734 int i0 = m_indexData[i];
735 int i1 = m_indexData[i + 1];
736 int i2 = m_indexData[i + 2];
737 const QVector3D& v0q = m_vertexData[i0].pos;
738 const QVector3D& v1q = m_vertexData[i1].pos;
739 const QVector3D& v2q = m_vertexData[i2].pos;
741 double v0x = v0q.x(), v0y = v0q.y(), v0z = v0q.z();
742 double v1x = v1q.x(), v1y = v1q.y(), v1z = v1q.z();
743 double v2x = v2q.x(), v2y = v2q.y(), v2z = v2q.z();
745 double edge1x = v1x - v0x, edge1y = v1y - v0y, edge1z = v1z - v0z;
746 double edge2x = v2x - v0x, edge2y = v2y - v0y, edge2z = v2z - v0z;
748 double hx = dirY * edge2z - dirZ * edge2y;
749 double hy = dirZ * edge2x - dirX * edge2z;
750 double hz = dirX * edge2y - dirY * edge2x;
752 double a = edge1x * hx + edge1y * hy + edge1z * hz;
753 if (std::abs(a) < 1e-18)
757 double sx = originX - v0x, sy = originY - v0y, sz = originZ - v0z;
758 double u = f * (sx * hx + sy * hy + sz * hz);
759 if (u < -1e-7 || u > 1.0000001)
762 double qx = sy * edge1z - sz * edge1y;
763 double qy = sz * edge1x - sx * edge1z;
764 double qz = sx * edge1y - sy * edge1x;
766 double v = f * (dirX * qx + dirY * qy + dirZ * qz);
767 if (v < -1e-7 || u + v > 1.0000001)
770 double t = f * (edge2x * qx + edge2y * qy + edge2z * qz);
771 if (t > 1e-7 && t < closestDist) {
777 constexpr double tol = 0.25;
779 if (u >= -tol && v >= -tol && u + v <= 1.0 + tol) {
785 double hitX = originX + t * dirX;
786 double hitY = originY + t * dirY;
787 double hitZ = originZ + t * dirZ;
789 double d0 = (v0x - hitX) * (v0x - hitX) + (v0y - hitY) * (v0y - hitY) + (v0z - hitZ) * (v0z - hitZ);
790 double d1 = (v1x - hitX) * (v1x - hitX) + (v1y - hitY) * (v1y - hitY) + (v1z - hitZ) * (v1z - hitZ);
791 double d2 = (v2x - hitX) * (v2x - hitX) + (v2y - hitY) * (v2y - hitY) + (v2z - hitZ) * (v2z - hitZ);
793 if (d0 < d1 && d0 < d2)
804 dist =
static_cast<float>(closestDist);
805 vertexIdx = closestVert;
816 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexData.size()) {
820 const Eigen::VectorXi& vertices = m_annotation.getVertices();
821 const Eigen::VectorXi& labelIds = m_annotation.getLabelIds();
828 for (
int i = 0; i < vertices.rows(); ++i) {
829 if (vertices(i) == vertexIdx) {
830 labelId = labelIds(i);
840 if (ct.
table(i, 4) == labelId) {
843 while (!name.isEmpty() && (name.endsWith(
'\0') || name.endsWith(
' '))) {
855 if (!m_hasAnnotation || vertexIdx < 0)
858 const Eigen::VectorXi& vertices = m_annotation.getVertices();
859 const Eigen::VectorXi& labelIds = m_annotation.
getLabelIds();
861 for (
int i = 0; i < vertices.rows(); ++i) {
862 if (vertices(i) == vertexIdx) {
872 m_selectedRegionId = regionId;
873 updateVertexColors();
878 m_selected = selected;
879 updateVertexColors();
884 m_selectedVertexStart = start;
885 m_selectedVertexCount = count;
886 updateVertexColors();
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
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)
std::unique_ptr< QRhiBuffer > vertexBuffer
std::unique_ptr< QRhiBuffer > indexBuffer
Interleaved vertex attributes (position, normal, color, curvature) for brain surface GPU upload.
int getAnnotationLabelId(int vertexIdx) const
void setVisible(bool visible)
Eigen::MatrixX3f vertexPositions() const
Eigen::MatrixX3f verticesAsMatrix() const
void setSelectedVertexRange(int start, int count)
QRhiBuffer * indexBuffer() const
void fromSurface(const FSLIB::FsSurface &surf)
Eigen::MatrixX3f vertexNormals() const
void applyTransform(const QMatrix4x4 &m)
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
DISP3DLIB::VisualizationMode VisualizationMode
bool loadAnnotation(const QString &path)
void boundingBox(QVector3D &min, QVector3D &max) const
void transform(const QMatrix4x4 &m)
void fromBemSurface(const MNELIB::MNEBemSurface &surf, const QColor &color=Qt::white)
void setUseDefaultColor(bool useDefault)
void setSelectedRegion(int regionId)
void addAnnotation(const FSLIB::FsAnnotation &annotation)
void applySourceEstimateColors(const QVector< uint32_t > &colors)
QRhiBuffer * vertexBuffer() const
void clearSourceEstimateColors()
static constexpr VisualizationMode ModeSourceEstimate
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 updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
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)
FreeSurfer colour lookup table: region name + RGBA + packed label, indexed by entry.
Eigen::VectorXi getLabelIds() const
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.