v2.0.0
Loading...
Searching...
No Matches
networkobject.cpp
Go to the documentation of this file.
1//=============================================================================================================
12
13//=============================================================================================================
14// INCLUDES
15//=============================================================================================================
16
17#include "networkobject.h"
18
19#include <rhi/qrhi.h>
20
24
25#include <QQuaternion>
26#include <QDebug>
27#include <cmath>
28
29using namespace CONNECTIVITYLIB;
30using namespace DISPLIB;
31using namespace Eigen;
32
33namespace DISP3DLIB
34{
35
36//=============================================================================================================
37// PIMPL
38//=============================================================================================================
39
41{
42 // Node buffers
43 std::unique_ptr<QRhiBuffer> nodeVertexBuffer;
44 std::unique_ptr<QRhiBuffer> nodeIndexBuffer;
45 std::unique_ptr<QRhiBuffer> nodeInstanceBuffer;
46 // Edge buffers
47 std::unique_ptr<QRhiBuffer> edgeVertexBuffer;
48 std::unique_ptr<QRhiBuffer> edgeIndexBuffer;
49 std::unique_ptr<QRhiBuffer> edgeInstanceBuffer;
50};
51
52//=============================================================================================================
53// DEFINE MEMBER METHODS
54//=============================================================================================================
55
57: m_gpu(std::make_unique<GpuBuffers>())
58{
59}
60
61//=============================================================================================================
62
64
65//=============================================================================================================
66
68{
69 return m_gpu->nodeVertexBuffer.get();
70}
72{
73 return m_gpu->nodeIndexBuffer.get();
74}
76{
77 return m_gpu->nodeInstanceBuffer.get();
78}
80{
81 return m_gpu->edgeVertexBuffer.get();
82}
84{
85 return m_gpu->edgeIndexBuffer.get();
86}
88{
89 return m_gpu->edgeInstanceBuffer.get();
90}
91
92//=============================================================================================================
93
94void NetworkObject::load(const Network& network, const QString& sColormap)
95{
96 m_network = network;
97 m_colormap = sColormap;
98
99 createNodeGeometry();
100 createEdgeGeometry();
101 buildNodeInstances();
102 buildEdgeInstances();
103}
104
105//=============================================================================================================
106
107void NetworkObject::setThreshold(double dThreshold)
108{
109 m_network.setThreshold(dThreshold);
110 buildNodeInstances();
111 buildEdgeInstances();
112}
113
114//=============================================================================================================
115
116void NetworkObject::setColormap(const QString& sColormap)
117{
118 m_colormap = sColormap;
119 buildNodeInstances();
120 buildEdgeInstances();
121}
122
123//=============================================================================================================
124
125void NetworkObject::createNodeGeometry()
126{
127 if (!m_nodeVertexData.isEmpty())
128 return;
129
130 // Create an icosphere (subdivision level 1) for nodes
131 const int subdivisions = 1;
132 const float radius = 1.0f; // Unit sphere, scaled per-instance
133
134 // Start with icosahedron
135 const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
136
137 std::vector<QVector3D> vertices = {
138 QVector3D(-1, t, 0).normalized() * radius,
139 QVector3D(1, t, 0).normalized() * radius,
140 QVector3D(-1, -t, 0).normalized() * radius,
141 QVector3D(1, -t, 0).normalized() * radius,
142 QVector3D(0, -1, t).normalized() * radius,
143 QVector3D(0, 1, t).normalized() * radius,
144 QVector3D(0, -1, -t).normalized() * radius,
145 QVector3D(0, 1, -t).normalized() * radius,
146 QVector3D(t, 0, -1).normalized() * radius,
147 QVector3D(t, 0, 1).normalized() * radius,
148 QVector3D(-t, 0, -1).normalized() * radius,
149 QVector3D(-t, 0, 1).normalized() * radius,
150 };
151
152 std::vector<uint32_t> indices = {
153 0,
154 11,
155 5,
156 0,
157 5,
158 1,
159 0,
160 1,
161 7,
162 0,
163 7,
164 10,
165 0,
166 10,
167 11,
168 1,
169 5,
170 9,
171 5,
172 11,
173 4,
174 11,
175 10,
176 2,
177 10,
178 7,
179 6,
180 7,
181 1,
182 8,
183 3,
184 9,
185 4,
186 3,
187 4,
188 2,
189 3,
190 2,
191 6,
192 3,
193 6,
194 8,
195 3,
196 8,
197 9,
198 4,
199 9,
200 5,
201 2,
202 4,
203 11,
204 6,
205 2,
206 10,
207 8,
208 6,
209 7,
210 9,
211 8,
212 1,
213 };
214
215 // Subdivide
216 for (int s = 0; s < subdivisions; ++s) {
217 std::vector<uint32_t> newIndices;
218 std::map<uint64_t, uint32_t> midpointCache;
219
220 auto getMidpoint = [&](uint32_t i0, uint32_t i1) -> uint32_t {
221 uint64_t key = (uint64_t)std::min(i0, i1) << 32 | std::max(i0, i1);
222 auto it = midpointCache.find(key);
223 if (it != midpointCache.end())
224 return it->second;
225
226 QVector3D mid = ((vertices[i0] + vertices[i1]) / 2.0f).normalized() * radius;
227 uint32_t idx = (uint32_t)vertices.size();
228 vertices.push_back(mid);
229 midpointCache[key] = idx;
230 return idx;
231 };
232
233 for (size_t i = 0; i < indices.size(); i += 3) {
234 uint32_t a = indices[i], b = indices[i + 1], c = indices[i + 2];
235 uint32_t ab = getMidpoint(a, b);
236 uint32_t bc = getMidpoint(b, c);
237 uint32_t ca = getMidpoint(c, a);
238
239 newIndices.insert(newIndices.end(), {a, ab, ca});
240 newIndices.insert(newIndices.end(), {b, bc, ab});
241 newIndices.insert(newIndices.end(), {c, ca, bc});
242 newIndices.insert(newIndices.end(), {ab, bc, ca});
243 }
244
245 indices = std::move(newIndices);
246 }
247
248 // Build vertex data with normals (normal = normalized position for sphere)
249 std::vector<VertexData> vd;
250 vd.reserve(vertices.size());
251 for (const auto& v : vertices) {
252 QVector3D n = v.normalized();
253 vd.push_back({v.x(), v.y(), v.z(), n.x(), n.y(), n.z()});
254 }
255
256 m_nodeIndexCount = (int)indices.size();
257
258 m_nodeVertexData.resize(vd.size() * sizeof(VertexData));
259 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
260
261 m_nodeIndexData.resize(indices.size() * sizeof(uint32_t));
262 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
263
264 m_nodeGeometryDirty = true;
265}
266
267//=============================================================================================================
268
269void NetworkObject::createEdgeGeometry()
270{
271 if (!m_edgeVertexData.isEmpty())
272 return;
273
274 // Create a unit cylinder along Y axis (height=1, radius=1, scaled per-instance)
275 const int segments = 8;
276 const float radius = 1.0f;
277 const float halfHeight = 0.5f;
278
279 std::vector<VertexData> vertices;
280 std::vector<uint32_t> indices;
281
282 // Top center (0)
283 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
284 // Bottom center (1)
285 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
286
287 // Side vertices: top ring (2..2+segments-1), bottom ring (2+segments..2+2*segments-1)
288 for (int i = 0; i < segments; ++i) {
289 float angle = 2.0f * (float)M_PI * i / segments;
290 float x = radius * std::cos(angle);
291 float z = radius * std::sin(angle);
292
293 QVector3D normal(x, 0, z);
294 normal.normalize();
295
296 // Top side vertex
297 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
298 // Bottom side vertex
299 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
300 }
301
302 // Top cap vertices (for proper normals)
303 int topCapStart = (int)vertices.size();
304 for (int i = 0; i < segments; ++i) {
305 float angle = 2.0f * (float)M_PI * i / segments;
306 float x = radius * std::cos(angle);
307 float z = radius * std::sin(angle);
308 vertices.push_back({x, halfHeight, z, 0, 1, 0});
309 }
310
311 // Bottom cap vertices
312 int botCapStart = (int)vertices.size();
313 for (int i = 0; i < segments; ++i) {
314 float angle = 2.0f * (float)M_PI * i / segments;
315 float x = radius * std::cos(angle);
316 float z = radius * std::sin(angle);
317 vertices.push_back({x, -halfHeight, z, 0, -1, 0});
318 }
319
320 // Side faces
321 int sideStart = 2;
322 for (int i = 0; i < segments; ++i) {
323 int next = (i + 1) % segments;
324 int topCur = sideStart + i * 2;
325 int botCur = sideStart + i * 2 + 1;
326 int topNext = sideStart + next * 2;
327 int botNext = sideStart + next * 2 + 1;
328
329 indices.insert(indices.end(), {(uint32_t)topCur, (uint32_t)topNext, (uint32_t)botCur});
330 indices.insert(indices.end(), {(uint32_t)botCur, (uint32_t)topNext, (uint32_t)botNext});
331 }
332
333 // Top cap
334 for (int i = 0; i < segments; ++i) {
335 int next = (i + 1) % segments;
336 indices.push_back(0); // center
337 indices.push_back(topCapStart + i);
338 indices.push_back(topCapStart + next);
339 }
340
341 // Bottom cap
342 for (int i = 0; i < segments; ++i) {
343 int next = (i + 1) % segments;
344 indices.push_back(1); // center
345 indices.push_back(botCapStart + next);
346 indices.push_back(botCapStart + i);
347 }
348
349 m_edgeIndexCount = (int)indices.size();
350
351 m_edgeVertexData.resize(vertices.size() * sizeof(VertexData));
352 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
353
354 m_edgeIndexData.resize(indices.size() * sizeof(uint32_t));
355 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
356
357 m_edgeGeometryDirty = true;
358}
359
360//=============================================================================================================
361
362void NetworkObject::buildNodeInstances()
363{
364 if (m_network.isEmpty()) {
365 m_nodeInstanceCount = 0;
366 m_nodeInstancesDirty = true;
367 return;
368 }
369
370 const auto& nodes = m_network.getNodes();
371 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
372 if (iMaxDegree == 0)
373 iMaxDegree = 1;
374
375 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
376
377 std::vector<InstanceData> instances;
378 instances.reserve(nodes.size());
379
380 for (int i = 0; i < nodes.size(); ++i) {
381 qint16 degree = nodes[i]->getThresholdedDegree();
382 if (degree == 0)
383 continue;
384
385 const RowVectorXf& vert = nodes[i]->getVert();
386 QVector3D pos(vert(0), vert(1), vert(2));
387
388 // Scale: nodes with higher degree are larger
389 // Range: 0.0006 to 0.005 (same as disp3D)
390 float scaleFactor = ((float)degree / (float)iMaxDegree) * (0.005f - 0.0006f) + 0.0006f;
391
392 QMatrix4x4 m;
393 m.translate(pos);
394 m.scale(scaleFactor);
395
396 InstanceData inst;
397 const float* mPtr = m.constData();
398 for (int j = 0; j < 16; ++j)
399 inst.model[j] = mPtr[j];
400
401 // Color: colormap-based or fixed
402 if (vizInfo.sMethod == "Map") {
403 float normalized = (float)degree / (float)iMaxDegree;
404 QRgb rgb = ColorMap::valueToColor(normalized, vizInfo.sColormap.isEmpty() ? m_colormap : vizInfo.sColormap);
405 QColor color(rgb);
406 float alpha = std::pow(normalized, 4.0f); // Same as disp3D
407 inst.color[0] = color.redF();
408 inst.color[1] = color.greenF();
409 inst.color[2] = color.blueF();
410 inst.color[3] = alpha;
411 } else {
412 inst.color[0] = vizInfo.colNodes[0] / 255.0f;
413 inst.color[1] = vizInfo.colNodes[1] / 255.0f;
414 inst.color[2] = vizInfo.colNodes[2] / 255.0f;
415 inst.color[3] = vizInfo.colNodes[3] / 255.0f;
416 }
417 inst.isSelected = 0.0f;
418
419 instances.push_back(inst);
420 }
421
422 m_nodeInstanceCount = (int)instances.size();
423 m_nodeInstanceData.resize(m_nodeInstanceCount * sizeof(InstanceData));
424 if (m_nodeInstanceCount > 0) {
425 memcpy(m_nodeInstanceData.data(), instances.data(), m_nodeInstanceData.size());
426 }
427 m_nodeInstancesDirty = true;
428
429 qDebug() << "NetworkObject: Built" << m_nodeInstanceCount << "node instances";
430}
431
432//=============================================================================================================
433
434void NetworkObject::buildEdgeInstances()
435{
436 if (m_network.isEmpty()) {
437 m_edgeInstanceCount = 0;
438 m_edgeInstancesDirty = true;
439 return;
440 }
441
442 const auto& edges = m_network.getThresholdedEdges();
443 const auto& nodes = m_network.getNodes();
444
445 double dMaxWeight = m_network.getMinMaxThresholdedWeights().second;
446 double dMinWeight = m_network.getMinMaxThresholdedWeights().first;
447 double dWeightRange = dMaxWeight - dMinWeight;
448 if (dWeightRange == 0.0)
449 dWeightRange = 1.0;
450
451 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
452
453 std::vector<InstanceData> instances;
454 instances.reserve(edges.size());
455
456 for (int i = 0; i < edges.size(); ++i) {
457 auto& edge = edges[i];
458 if (!edge->isActive())
459 continue;
460
461 int iStart = edge->getStartNodeID();
462 int iEnd = edge->getEndNodeID();
463
464 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size())
465 continue;
466
467 const RowVectorXf& vStart = nodes[iStart]->getVert();
468 const RowVectorXf& vEnd = nodes[iEnd]->getVert();
469
470 QVector3D startPos(vStart(0), vStart(1), vStart(2));
471 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
472
473 if (startPos == endPos)
474 continue;
475
476 double dWeight = std::fabs(edge->getWeight());
477 if (dWeight == 0.0)
478 continue;
479
480 QVector3D diff = endPos - startPos;
481 QVector3D midPoint = startPos + diff / 2.0f;
482 float length = diff.length();
483
484 // Build transform: translate to midpoint, rotate Y-axis to diff direction, scale
485 float normalizedWeight = (float)std::fabs((dWeight - dMinWeight) / dWeightRange);
486
487 // Cylinder radius: proportional to weight, range 0.0001 to 0.001
488 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
489
490 QMatrix4x4 m;
491 m.translate(midPoint);
492 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
493 m.scale(edgeRadius, length, edgeRadius);
494
495 InstanceData inst;
496 const float* mPtr = m.constData();
497 for (int j = 0; j < 16; ++j)
498 inst.model[j] = mPtr[j];
499
500 // Color
501 if (vizInfo.sMethod == "Map") {
502 float normalized = (dMaxWeight != 0.0) ? (float)std::fabs(dWeight / dMaxWeight) : 0.0f;
503 QRgb rgb = ColorMap::valueToColor(normalized, vizInfo.sColormap.isEmpty() ? m_colormap : vizInfo.sColormap);
504 QColor color(rgb);
505 float alpha = std::pow(normalized, 1.5f); // Same as disp3D
506 inst.color[0] = color.redF();
507 inst.color[1] = color.greenF();
508 inst.color[2] = color.blueF();
509 inst.color[3] = alpha;
510 } else {
511 inst.color[0] = vizInfo.colEdges[0] / 255.0f;
512 inst.color[1] = vizInfo.colEdges[1] / 255.0f;
513 inst.color[2] = vizInfo.colEdges[2] / 255.0f;
514 inst.color[3] = vizInfo.colEdges[3] / 255.0f;
515 }
516 inst.isSelected = 0.0f;
517
518 instances.push_back(inst);
519 }
520
521 m_edgeInstanceCount = (int)instances.size();
522 m_edgeInstanceData.resize(m_edgeInstanceCount * sizeof(InstanceData));
523 if (m_edgeInstanceCount > 0) {
524 memcpy(m_edgeInstanceData.data(), instances.data(), m_edgeInstanceData.size());
525 }
526 m_edgeInstancesDirty = true;
527
528 qDebug() << "NetworkObject: Built" << m_edgeInstanceCount << "edge instances";
529}
530
531//=============================================================================================================
532
533void NetworkObject::updateNodeBuffers(QRhi* rhi, QRhiResourceUpdateBatch* u)
534{
535 if (m_nodeGeometryDirty) {
536 if (!m_gpu->nodeVertexBuffer) {
537 m_gpu->nodeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_nodeVertexData.size()));
538 m_gpu->nodeVertexBuffer->create();
539 }
540 if (!m_gpu->nodeIndexBuffer) {
541 m_gpu->nodeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_nodeIndexData.size()));
542 m_gpu->nodeIndexBuffer->create();
543 }
544 u->uploadStaticBuffer(m_gpu->nodeVertexBuffer.get(), m_nodeVertexData.constData());
545 u->uploadStaticBuffer(m_gpu->nodeIndexBuffer.get(), m_nodeIndexData.constData());
546 m_nodeGeometryDirty = false;
547 }
548
549 if (m_nodeInstancesDirty && m_nodeInstanceCount > 0) {
550 int requiredSize = m_nodeInstanceData.size();
551 if (!m_gpu->nodeInstanceBuffer || m_gpu->nodeInstanceBuffer->size() < static_cast<quint32>(requiredSize)) {
552 m_gpu->nodeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
553 m_gpu->nodeInstanceBuffer->create();
554 }
555 u->updateDynamicBuffer(m_gpu->nodeInstanceBuffer.get(), 0, requiredSize, m_nodeInstanceData.constData());
556 m_nodeInstancesDirty = false;
557 }
558}
559
560//=============================================================================================================
561
562void NetworkObject::updateEdgeBuffers(QRhi* rhi, QRhiResourceUpdateBatch* u)
563{
564 if (m_edgeGeometryDirty) {
565 if (!m_gpu->edgeVertexBuffer) {
566 m_gpu->edgeVertexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, m_edgeVertexData.size()));
567 m_gpu->edgeVertexBuffer->create();
568 }
569 if (!m_gpu->edgeIndexBuffer) {
570 m_gpu->edgeIndexBuffer.reset(rhi->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, m_edgeIndexData.size()));
571 m_gpu->edgeIndexBuffer->create();
572 }
573 u->uploadStaticBuffer(m_gpu->edgeVertexBuffer.get(), m_edgeVertexData.constData());
574 u->uploadStaticBuffer(m_gpu->edgeIndexBuffer.get(), m_edgeIndexData.constData());
575 m_edgeGeometryDirty = false;
576 }
577
578 if (m_edgeInstancesDirty && m_edgeInstanceCount > 0) {
579 int requiredSize = m_edgeInstanceData.size();
580 if (!m_gpu->edgeInstanceBuffer || m_gpu->edgeInstanceBuffer->size() < static_cast<quint32>(requiredSize)) {
581 m_gpu->edgeInstanceBuffer.reset(rhi->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::VertexBuffer, requiredSize));
582 m_gpu->edgeInstanceBuffer->create();
583 }
584 u->updateDynamicBuffer(m_gpu->edgeInstanceBuffer.get(), 0, requiredSize, m_edgeInstanceData.constData());
585 m_edgeInstancesDirty = false;
586 }
587}
588
589} // namespace DISP3DLIB
Static scalar-to-colour lookup helpers (Jet, Hot, Bone, Viridis, Cool, RedBlue, MNE) used by every pl...
#define M_PI
Instanced connectivity-graph renderable: node spheres and edge cylinders coloured by weight through a...
Node of a connectivity CONNECTIVITYLIB::Network; carries a 3D position and the lists of incident (in ...
Weighted edge between two CONNECTIVITYLIB::NetworkNode instances; stores the full per-frequency weigh...
Functional connectivity metrics (coherence, PLV, cross-correlation, etc.).
2-D display widgets and visualisation helpers (charts, topography, colour maps).
3-D brain visualisation using the Qt RHI rendering backend.
Graph container for one connectivity metric; nodes + weighted edges + threshold/visualisation state.
Definition network.h:106
static QRgb valueToColor(double v, const QString &sMap)
Definition colormap.h:683
std::unique_ptr< QRhiBuffer > nodeIndexBuffer
std::unique_ptr< QRhiBuffer > nodeVertexBuffer
std::unique_ptr< QRhiBuffer > edgeIndexBuffer
std::unique_ptr< QRhiBuffer > nodeInstanceBuffer
std::unique_ptr< QRhiBuffer > edgeVertexBuffer
std::unique_ptr< QRhiBuffer > edgeInstanceBuffer
QRhiBuffer * edgeInstanceBuffer() const
QRhiBuffer * nodeInstanceBuffer() const
void load(const CONNECTIVITYLIB::Network &network, const QString &sColormap="Viridis")
QRhiBuffer * nodeIndexBuffer() const
void setThreshold(double dThreshold)
void setColormap(const QString &sColormap)
void updateEdgeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
void updateNodeBuffers(QRhi *rhi, QRhiResourceUpdateBatch *u)
QRhiBuffer * edgeVertexBuffer() const
QRhiBuffer * edgeIndexBuffer() const
QRhiBuffer * nodeVertexBuffer() const