24uint32_t withAlpha(uint32_t color, uint32_t alpha)
26 return (color & 0x00FFFFFFu) | ((alpha & 0xFFu) << 24);
29uint32_t curvatureGray(
const QVector<float> &curvature,
int index)
31 return (index >= 0 && index < curvature.size() && curvature[index] > 0.0f)
51void BrainSurface::markVertexDirty()
86 const Eigen::MatrixXf &rr = surf.
rr();
87 const Eigen::MatrixXf &nn = surf.
nn();
88 const Eigen::MatrixXi &tris = surf.
tris();
89 const Eigen::VectorXf &curv = surf.
curv();
90 m_curvature.resize(curv.size());
91 for(
int i=0; i<curv.size(); ++i) m_curvature[i] = curv[i];
94 m_vertexData.reserve(rr.rows());
96 for (
int i = 0; i < rr.rows(); ++i) {
98 v.
pos = QVector3D(rr(i, 0), rr(i, 1), rr(i, 2));
99 v.
norm = QVector3D(nn(i, 0), nn(i, 1), nn(i, 2));
100 v.
color = 0xFFFFFFFF;
102 m_vertexData.append(v);
105 m_indexData.reserve(tris.rows() * 3);
106 for (
int i = 0; i < tris.rows(); ++i) {
107 m_indexData.append(tris(i, 0));
108 m_indexData.append(tris(i, 1));
109 m_indexData.append(tris(i, 2));
111 m_indexCount = m_indexData.size();
117 updateVertexColors();
119 m_originalVertexData = m_vertexData;
128 m_vertexData.clear();
132 int nVerts = surf.
rr.rows();
133 m_vertexData.reserve(nVerts);
136 Eigen::MatrixX3f nn = surf.
nn;
137 if (nn.rows() != nVerts) {
141 m_defaultColor = color;
143 uint32_t colorVal =
packABGR(color.red(), color.green(), color.blue(), color.alpha());
145 for (
int i = 0; i < nVerts; ++i) {
147 v.
pos = QVector3D(surf.
rr(i, 0), surf.
rr(i, 1), surf.
rr(i, 2));
148 v.
norm = QVector3D(nn(i, 0), nn(i, 1), nn(i, 2));
151 m_vertexData.append(v);
154 int nTris = surf.
itris.rows();
155 m_indexData.reserve(nTris * 3);
156 for (
int i = 0; i < nTris; ++i) {
157 m_indexData.append(surf.
itris(i, 0));
158 m_indexData.append(surf.
itris(i, 1));
159 m_indexData.append(surf.
itris(i, 2));
161 m_indexCount = m_indexData.size();
163 m_originalVertexData = m_vertexData;
171 Eigen::MatrixX3f normals(vertices.rows(), 3);
172 for(
int i=0; i<vertices.rows(); ++i) {
173 QVector3D p(vertices(i, 0), vertices(i, 1), vertices(i, 2));
174 QVector3D n = p.normalized();
175 normals(i, 0) = n.x();
176 normals(i, 1) = n.y();
177 normals(i, 2) = n.z();
182void BrainSurface::createFromData(
const Eigen::MatrixX3f &vertices,
const Eigen::MatrixX3f &normals,
const Eigen::MatrixX3i &triangles,
const QColor &color)
184 m_vertexData.clear();
188 int nVerts = vertices.rows();
189 m_vertexData.reserve(nVerts);
191 m_defaultColor = color;
193 uint32_t colorVal =
packABGR(color.red(), color.green(), color.blue(), color.alpha());
195 for (
int i = 0; i < nVerts; ++i) {
197 v.
pos = QVector3D(vertices(i, 0), vertices(i, 1), vertices(i, 2));
198 v.
norm = QVector3D(normals(i, 0), normals(i, 1), normals(i, 2));
201 m_vertexData.append(v);
204 int nTris = triangles.rows();
205 m_indexData.reserve(nTris * 3);
206 for (
int i = 0; i < nTris; ++i) {
207 m_indexData.append(triangles(i, 0));
208 m_indexData.append(triangles(i, 1));
209 m_indexData.append(triangles(i, 2));
211 m_indexCount = m_indexData.size();
213 m_originalVertexData = m_vertexData;
221 Eigen::MatrixX3f rr(m_vertexData.size(), 3);
222 for (
int i = 0; i < m_vertexData.size(); ++i) {
223 rr(i, 0) = m_vertexData[i].pos.x();
224 rr(i, 1) = m_vertexData[i].pos.y();
225 rr(i, 2) = m_vertexData[i].pos.z();
234 Eigen::MatrixX3f nn(m_vertexData.size(), 3);
235 for (
int i = 0; i < m_vertexData.size(); ++i) {
236 nn(i, 0) = m_vertexData[i].norm.x();
237 nn(i, 1) = m_vertexData[i].norm.y();
238 nn(i, 2) = m_vertexData[i].norm.z();
248 qWarning() <<
"BrainSurface: Failed to load annotation from" << path;
251 m_hasAnnotation =
true;
252 updateVertexColors();
259 m_annotation = annotation;
260 m_hasAnnotation =
true;
261 updateVertexColors();
277 if (m_visMode == mode)
286 updateVertexColors();
295 m_stcColors = colors;
300 for (
int i = 0; i < qMin(colors.size(), m_vertexData.size()); ++i) {
301 m_vertexData[i].color = withAlpha(colors[i], curvatureGray(m_curvature, i));
313 updateVertexColors();
321 m_baseColor = useDefault ? m_defaultColor : Qt::white;
322 updateVertexColors();
325void BrainSurface::updateVertexColors()
332 if (!m_curvature.isEmpty() && m_curvature.size() == m_vertexData.size()) {
333 for (
int i = 0; i < m_vertexData.size(); ++i) {
334 const uint32_t val = curvatureGray(m_curvature, i);
335 m_vertexData[i].color =
packABGR(val, val, val, val);
340 const uint32_t baseVal =
packABGR(m_baseColor.red(), m_baseColor.green(),
341 m_baseColor.blue(), m_baseColor.alpha());
342 for (
int i = 0; i < m_vertexData.size(); ++i) {
343 m_vertexData[i].color = baseVal;
353 if (!m_stcColors.isEmpty()) {
354 for (
int i = 0; i < qMin(m_stcColors.size(), m_vertexData.size()); ++i) {
355 m_vertexData[i].color = withAlpha(m_stcColors[i], curvatureGray(m_curvature, i));
360 for (
auto &v : m_vertexData) {
361 v.colorAnnotation = 0x00000000;
364 if (m_hasAnnotation && !m_vertexData.isEmpty()) {
365 const Eigen::VectorXi &vertices = m_annotation.getVertices();
366 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
367 const FSLIB::FsColortable &ct = m_annotation.getColortable();
369 for (
int i = 0; i < labelIds.rows(); ++i) {
370 int vertexIdx = vertices(i);
371 if (vertexIdx >= 0 && vertexIdx < m_vertexData.size()) {
374 if (ct.
table(c, 4) == labelIds(i)) {
380 uint32_t r = ct.
table(colorIdx, 0);
381 uint32_t g = ct.
table(colorIdx, 1);
382 uint32_t b = ct.
table(colorIdx, 2);
383 m_vertexData[vertexIdx].colorAnnotation =
396 const uint32_t gold =
packABGR(255, 200, 60);
397 if (m_selectedRegionId != -1 && m_hasAnnotation) {
399 const Eigen::VectorXi &vertices = m_annotation.getVertices();
400 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
401 for (
int i = 0; i < labelIds.rows(); ++i) {
402 if (labelIds(i) == m_selectedRegionId) {
403 int idx = vertices(i);
404 if (idx >= 0 && idx < m_vertexData.size()) {
405 m_vertexData[idx].color = gold;
406 m_vertexData[idx].colorAnnotation = gold;
410 }
else if (m_selectedVertexStart >= 0 && m_selectedVertexCount > 0) {
412 const int end = qMin(m_selectedVertexStart + m_selectedVertexCount,
413 m_vertexData.size());
414 for (
int i = m_selectedVertexStart; i < end; ++i) {
415 m_vertexData[i].color = gold;
427 float minVal = std::numeric_limits<float>::max();
428 for (
const auto &v : m_vertexData) {
429 if (v.pos.x() < minVal) minVal = v.pos.x();
436 float maxVal = std::numeric_limits<float>::lowest();
437 for (
const auto &v : m_vertexData) {
438 if (v.pos.x() > maxVal) maxVal = v.pos.x();
445 for (
auto &v : m_vertexData) {
446 v.pos.setX(v.pos.x() + offset);
458 QMatrix3x3 normalMat = m.normalMatrix();
460 for (
auto &v : m_vertexData) {
462 v.pos = m.map(v.pos);
475 const float *d = normalMat.constData();
476 float nx = d[0]*v.norm.x() + d[3]*v.norm.y() + d[6]*v.norm.z();
477 float ny = d[1]*v.norm.x() + d[4]*v.norm.y() + d[7]*v.norm.z();
478 float nz = d[2]*v.norm.x() + d[5]*v.norm.y() + d[8]*v.norm.z();
479 v.norm = QVector3D(nx, ny, nz).normalized();
489 m_vertexData = m_originalVertexData;
490 if (!m.isIdentity()) {
502 const bool needsCreate = !m_gpu->vertexBuffer || !m_gpu->indexBuffer;
510 if (!needsCreate && !m_gpu->dirty) {
512 u->uploadStaticBuffer(m_gpu->vertexBuffer.get(), m_vertexData.constData());
513 u->uploadStaticBuffer(m_gpu->indexBuffer.get(), m_indexData.constData());
523 if (!m_gpu->dirty && !needsCreate)
return;
526 const quint32 vbufSize = m_vertexData.size() *
sizeof(
VertexData);
527 const quint32 ibufSize = m_indexData.size() *
sizeof(uint32_t);
530 if (!m_gpu->vertexBuffer) {
531 m_gpu->vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, vbufSize));
532 m_gpu->vertexBuffer->create();
533 m_gpu->indexDirty =
true;
547 if (!m_gpu->vertexBuffer || m_gpu->dirty) {
548 m_gpu->vertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, vbufSize));
549 m_gpu->vertexBuffer->create();
550 m_gpu->indexDirty =
true;
553 if (!m_gpu->indexBuffer) {
554 m_gpu->indexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, ibufSize));
555 m_gpu->indexBuffer->create();
556 m_gpu->indexDirty =
true;
559 u->uploadStaticBuffer(m_gpu->vertexBuffer.get(), m_vertexData.constData());
560 if (m_gpu->indexDirty) {
561 u->uploadStaticBuffer(m_gpu->indexBuffer.get(), m_indexData.constData());
562 m_gpu->indexDirty =
false;
564 m_gpu->dirty =
false;
572 std::vector<std::set<int>> tempNeighbors(m_vertexData.size());
575 for (
int i = 0; i + 2 < m_indexData.size(); i += 3) {
576 int v0 = m_indexData[i];
577 int v1 = m_indexData[i + 1];
578 int v2 = m_indexData[i + 2];
581 tempNeighbors[v0].insert(v1);
582 tempNeighbors[v0].insert(v2);
583 tempNeighbors[v1].insert(v0);
584 tempNeighbors[v1].insert(v2);
585 tempNeighbors[v2].insert(v0);
586 tempNeighbors[v2].insert(v1);
590 std::vector<Eigen::VectorXi> neighbors(tempNeighbors.size());
591 for (
size_t k = 0; k < tempNeighbors.size(); ++k) {
592 const auto& s = tempNeighbors[k];
593 neighbors[k].resize(
static_cast<Eigen::Index
>(s.size()));
594 Eigen::Index idx = 0;
596 neighbors[k][idx++] = val;
607 Eigen::MatrixX3f mat(m_vertexData.size(), 3);
608 for (
int i = 0; i < m_vertexData.size(); ++i) {
609 mat(i, 0) = m_vertexData[i].pos.x();
610 mat(i, 1) = m_vertexData[i].pos.y();
611 mat(i, 2) = m_vertexData[i].pos.z();
627 if (m_vertexData.isEmpty()) {
628 min = QVector3D(0,0,0);
629 max = QVector3D(0,0,0);
633 min = m_vertexData[0].pos;
634 max = m_vertexData[0].pos;
636 for (
const auto &v : m_vertexData) {
637 min.setX(std::min(min.x(), v.pos.x()));
638 min.setY(std::min(min.y(), v.pos.y()));
639 min.setZ(std::min(min.z(), v.pos.z()));
641 max.setX(std::max(max.x(), v.pos.x()));
642 max.setY(std::max(max.y(), v.pos.y()));
643 max.setZ(std::max(max.z(), v.pos.z()));
648 m_bAABBDirty =
false;
654 if (m_vertexData.isEmpty())
return false;
662 double origin[3] = {rayOrigin.x(), rayOrigin.y(), rayOrigin.z()};
663 double dir[3] = {rayDir.x(), rayDir.y(), rayDir.z()};
664 double minB[3] = {min.x() - eps, min.y() - eps, min.z() - eps};
665 double maxB[3] = {max.x() + eps, max.y() + eps, max.z() + eps};
668 double originX = origin[0], originY = origin[1], originZ = origin[2];
669 double dirX = dir[0], dirY = dir[1], dirZ = dir[2];
671 double tmin = -std::numeric_limits<double>::max();
672 double tmax = std::numeric_limits<double>::max();
674 for (
int i = 0; i < 3; ++i) {
675 if (std::abs(dir[i]) < 1e-15) {
676 if (origin[i] < minB[i] || origin[i] > maxB[i])
return false;
678 double t1 = (minB[i] - origin[i]) / dir[i];
679 double t2 = (maxB[i] - origin[i]) / dir[i];
680 if (t1 > t2) std::swap(t1, t2);
681 if (t1 > tmin) tmin = t1;
682 if (t2 < tmax) tmax = t2;
683 if (tmin > tmax)
return false;
687 if (tmax < 1e-7)
return false;
690 double closestDist = std::numeric_limits<double>::max();
692 int closestVert = -1;
697 for (
int i = 0; i < m_indexData.size(); i += 3) {
698 int i0 = m_indexData[i];
699 int i1 = m_indexData[i+1];
700 int i2 = m_indexData[i+2];
701 const QVector3D &v0q = m_vertexData[i0].pos;
702 const QVector3D &v1q = m_vertexData[i1].pos;
703 const QVector3D &v2q = m_vertexData[i2].pos;
705 double v0x = v0q.x(), v0y = v0q.y(), v0z = v0q.z();
706 double v1x = v1q.x(), v1y = v1q.y(), v1z = v1q.z();
707 double v2x = v2q.x(), v2y = v2q.y(), v2z = v2q.z();
709 double edge1x = v1x - v0x, edge1y = v1y - v0y, edge1z = v1z - v0z;
710 double edge2x = v2x - v0x, edge2y = v2y - v0y, edge2z = v2z - v0z;
712 double hx = dirY * edge2z - dirZ * edge2y;
713 double hy = dirZ * edge2x - dirX * edge2z;
714 double hz = dirX * edge2y - dirY * edge2x;
716 double a = edge1x * hx + edge1y * hy + edge1z * hz;
717 if (std::abs(a) < 1e-18)
continue;
720 double sx = originX - v0x, sy = originY - v0y, sz = originZ - v0z;
721 double u = f * (sx * hx + sy * hy + sz * hz);
722 if (u < -1e-7 || u > 1.0000001)
continue;
724 double qx = sy * edge1z - sz * edge1y;
725 double qy = sz * edge1x - sx * edge1z;
726 double qz = sx * edge1y - sy * edge1x;
728 double v = f * (dirX * qx + dirY * qy + dirZ * qz);
729 if (v < -1e-7 || u + v > 1.0000001)
continue;
731 double t = f * (edge2x * qx + edge2y * qy + edge2z * qz);
732 if (t > 1e-7 && t < closestDist) {
738 constexpr double tol = 0.25;
740 if (u >= -tol && v >= -tol && u + v <= 1.0 + tol) {
746 double hitX = originX + t * dirX;
747 double hitY = originY + t * dirY;
748 double hitZ = originZ + t * dirZ;
750 double d0 = (v0x - hitX)*(v0x - hitX) + (v0y - hitY)*(v0y - hitY) + (v0z - hitZ)*(v0z - hitZ);
751 double d1 = (v1x - hitX)*(v1x - hitX) + (v1y - hitY)*(v1y - hitY) + (v1z - hitZ)*(v1z - hitZ);
752 double d2 = (v2x - hitX)*(v2x - hitX) + (v2y - hitY)*(v2y - hitY) + (v2z - hitZ)*(v2z - hitZ);
754 if (d0 < d1 && d0 < d2) closestVert = i0;
755 else if (d1 < d2) closestVert = i1;
756 else closestVert = i2;
762 dist =
static_cast<float>(closestDist);
763 vertexIdx = closestVert;
774 if (!m_hasAnnotation || vertexIdx < 0 || vertexIdx >= m_vertexData.size()) {
778 const Eigen::VectorXi &vertices = m_annotation.getVertices();
779 const Eigen::VectorXi &labelIds = m_annotation.getLabelIds();
786 for (
int i = 0; i < vertices.rows(); ++i) {
787 if (vertices(i) == vertexIdx) {
788 labelId = labelIds(i);
793 if (labelId == -1)
return "Unknown";
797 if (ct.
table(i, 4) == labelId) {
800 while (!name.isEmpty() && (name.endsWith(
'\0') || name.endsWith(
' '))) {
812 if (!m_hasAnnotation || vertexIdx < 0)
return -1;
814 const Eigen::VectorXi &vertices = m_annotation.getVertices();
815 const Eigen::VectorXi &labelIds = m_annotation.
getLabelIds();
817 for (
int i = 0; i < vertices.rows(); ++i) {
818 if (vertices(i) == vertexIdx) {
828 m_selectedRegionId = regionId;
829 updateVertexColors();
834 m_selected = selected;
835 updateVertexColors();
840 m_selectedVertexStart = start;
841 m_selectedVertexCount = count;
842 updateVertexColors();
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
uint32_t packABGR(uint32_t r, uint32_t g, uint32_t b, uint32_t a=0xFF)
std::unique_ptr< QRhiBuffer > indexBuffer
std::unique_ptr< QRhiBuffer > vertexBuffer
Interleaved vertex attributes (position, normal, color, curvature) for brain surface GPU upload.
void translateX(float offset)
void fromSurface(const FSLIB::FsSurface &surf)
void setVisualizationMode(VisualizationMode mode)
Eigen::MatrixX3f vertexNormals() const
void setVisible(bool visible)
void boundingBox(QVector3D &min, QVector3D &max) const
QRhiBuffer * vertexBuffer() const
bool loadAnnotation(const QString &path)
void setSelectedRegion(int regionId)
void transform(const QMatrix4x4 &m)
::VisualizationMode VisualizationMode
void setSelectedVertexRange(int start, int count)
static constexpr VisualizationMode ModeSourceEstimate
QRhiBuffer * indexBuffer() const
bool intersects(const QVector3D &rayOrigin, const QVector3D &rayDir, float &dist, int &vertexIdx) const
void applySourceEstimateColors(const QVector< uint32_t > &colors)
void setUseDefaultColor(bool useDefault)
QString getAnnotationLabel(int vertexIdx) const
void clearSourceEstimateColors()
void updateBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
Eigen::MatrixX3f vertexPositions() const
int getAnnotationLabelId(int vertexIdx) const
void addAnnotation(const FSLIB::FsAnnotation &annotation)
void applyTransform(const QMatrix4x4 &m)
void setSelected(bool selected)
static constexpr VisualizationMode ModeSurface
Eigen::MatrixX3f verticesAsMatrix() const
void fromBemSurface(const MNELIB::MNEBemSurface &surf, const QColor &color=Qt::white)
void createFromData(const Eigen::MatrixX3f &vertices, const Eigen::MatrixX3i &triangles, const QColor &color)
std::vector< Eigen::VectorXi > computeNeighbors() const
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.