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
22
23#include <QQuaternion>
24#include <QDebug>
25#include <cmath>
26
27using namespace CONNECTIVITYLIB;
28using namespace DISPLIB;
29using namespace Eigen;
30
31namespace DISP3DLIB
32{
33
34//=============================================================================================================
35// DEFINE MEMBER METHODS
36//=============================================================================================================
37
39
40//=============================================================================================================
41
43
44//=============================================================================================================
45
47{
48 return {{m_nodeVertexData.constData(), static_cast<quint32>(m_nodeVertexData.size()), m_nodeGeometryGeneration},
49 {m_nodeIndexData.constData(), static_cast<quint32>(m_nodeIndexData.size()), m_nodeGeometryGeneration},
50 {m_nodeInstanceData.constData(), static_cast<quint32>(m_nodeInstanceCount * sizeof(InstanceData)), m_nodeInstanceGeneration},
51 m_nodeIndexCount,
52 m_nodeInstanceCount};
53}
54
56{
57 return {{m_edgeVertexData.constData(), static_cast<quint32>(m_edgeVertexData.size()), m_edgeGeometryGeneration},
58 {m_edgeIndexData.constData(), static_cast<quint32>(m_edgeIndexData.size()), m_edgeGeometryGeneration},
59 {m_edgeInstanceData.constData(), static_cast<quint32>(m_edgeInstanceCount * sizeof(InstanceData)), m_edgeInstanceGeneration},
60 m_edgeIndexCount,
61 m_edgeInstanceCount};
62}
63
64//=============================================================================================================
65
66void NetworkObject::load(const Network& network, const QString& sColormap)
67{
68 m_network = network;
69 m_colormap = sColormap;
70
71 createNodeGeometry();
72 createEdgeGeometry();
73 buildNodeInstances();
74 buildEdgeInstances();
75}
76
77//=============================================================================================================
78
79void NetworkObject::setThreshold(double dThreshold)
80{
81 m_network.setThreshold(dThreshold);
82 buildNodeInstances();
83 buildEdgeInstances();
84}
85
86//=============================================================================================================
87
88void NetworkObject::setColormap(const QString& sColormap)
89{
90 m_colormap = sColormap;
91 buildNodeInstances();
92 buildEdgeInstances();
93}
94
95//=============================================================================================================
96
97void NetworkObject::createNodeGeometry()
98{
99 if (!m_nodeVertexData.isEmpty())
100 return;
101
102 // Create an icosphere (subdivision level 1) for nodes
103 const int subdivisions = 1;
104 const float radius = 1.0f; // Unit sphere, scaled per-instance
105
106 // Start with icosahedron
107 const float t = (1.0f + std::sqrt(5.0f)) / 2.0f;
108
109 std::vector<QVector3D> vertices = {
110 QVector3D(-1, t, 0).normalized() * radius,
111 QVector3D(1, t, 0).normalized() * radius,
112 QVector3D(-1, -t, 0).normalized() * radius,
113 QVector3D(1, -t, 0).normalized() * radius,
114 QVector3D(0, -1, t).normalized() * radius,
115 QVector3D(0, 1, t).normalized() * radius,
116 QVector3D(0, -1, -t).normalized() * radius,
117 QVector3D(0, 1, -t).normalized() * radius,
118 QVector3D(t, 0, -1).normalized() * radius,
119 QVector3D(t, 0, 1).normalized() * radius,
120 QVector3D(-t, 0, -1).normalized() * radius,
121 QVector3D(-t, 0, 1).normalized() * radius,
122 };
123
124 std::vector<uint32_t> indices = {
125 0,
126 11,
127 5,
128 0,
129 5,
130 1,
131 0,
132 1,
133 7,
134 0,
135 7,
136 10,
137 0,
138 10,
139 11,
140 1,
141 5,
142 9,
143 5,
144 11,
145 4,
146 11,
147 10,
148 2,
149 10,
150 7,
151 6,
152 7,
153 1,
154 8,
155 3,
156 9,
157 4,
158 3,
159 4,
160 2,
161 3,
162 2,
163 6,
164 3,
165 6,
166 8,
167 3,
168 8,
169 9,
170 4,
171 9,
172 5,
173 2,
174 4,
175 11,
176 6,
177 2,
178 10,
179 8,
180 6,
181 7,
182 9,
183 8,
184 1,
185 };
186
187 // Subdivide
188 for (int s = 0; s < subdivisions; ++s) {
189 std::vector<uint32_t> newIndices;
190 std::map<uint64_t, uint32_t> midpointCache;
191
192 auto getMidpoint = [&](uint32_t i0, uint32_t i1) -> uint32_t {
193 uint64_t key = static_cast<uint64_t>(std::min(i0, i1)) << 32 | std::max(i0, i1);
194 auto it = midpointCache.find(key);
195 if (it != midpointCache.end())
196 return it->second;
197
198 QVector3D mid = ((vertices[i0] + vertices[i1]) / 2.0f).normalized() * radius;
199 uint32_t idx = static_cast<uint32_t>(vertices.size());
200 vertices.push_back(mid);
201 midpointCache[key] = idx;
202 return idx;
203 };
204
205 for (size_t i = 0; i < indices.size(); i += 3) {
206 uint32_t a = indices[i], b = indices[i + 1], c = indices[i + 2];
207 uint32_t ab = getMidpoint(a, b);
208 uint32_t bc = getMidpoint(b, c);
209 uint32_t ca = getMidpoint(c, a);
210
211 newIndices.insert(newIndices.end(), {a, ab, ca});
212 newIndices.insert(newIndices.end(), {b, bc, ab});
213 newIndices.insert(newIndices.end(), {c, ca, bc});
214 newIndices.insert(newIndices.end(), {ab, bc, ca});
215 }
216
217 indices = std::move(newIndices);
218 }
219
220 // Build vertex data with normals (normal = normalized position for sphere)
221 std::vector<VertexData> vd;
222 vd.reserve(vertices.size());
223 for (const auto& v : vertices) {
224 QVector3D n = v.normalized();
225 vd.push_back({v.x(), v.y(), v.z(), n.x(), n.y(), n.z()});
226 }
227
228 m_nodeIndexCount = static_cast<int>(indices.size());
229
230 m_nodeVertexData.resize(vd.size() * sizeof(VertexData));
231 memcpy(m_nodeVertexData.data(), vd.data(), m_nodeVertexData.size());
232
233 m_nodeIndexData.resize(indices.size() * sizeof(uint32_t));
234 memcpy(m_nodeIndexData.data(), indices.data(), m_nodeIndexData.size());
235
236 ++m_nodeGeometryGeneration;
237}
238
239//=============================================================================================================
240
241void NetworkObject::createEdgeGeometry()
242{
243 if (!m_edgeVertexData.isEmpty())
244 return;
245
246 // Create a unit cylinder along Y axis (height=1, radius=1, scaled per-instance)
247 const int segments = 8;
248 const float radius = 1.0f;
249 const float halfHeight = 0.5f;
250
251 std::vector<VertexData> vertices;
252 std::vector<uint32_t> indices;
253
254 // Top center (0)
255 vertices.push_back({0, halfHeight, 0, 0, 1, 0});
256 // Bottom center (1)
257 vertices.push_back({0, -halfHeight, 0, 0, -1, 0});
258
259 // Side vertices: top ring (2..2+segments-1), bottom ring (2+segments..2+2*segments-1)
260 for (int i = 0; i < segments; ++i) {
261 float angle = 2.0f * static_cast<float>(M_PI) * i / segments;
262 float x = radius * std::cos(angle);
263 float z = radius * std::sin(angle);
264
265 QVector3D normal(x, 0, z);
266 normal.normalize();
267
268 // Top side vertex
269 vertices.push_back({x, halfHeight, z, normal.x(), normal.y(), normal.z()});
270 // Bottom side vertex
271 vertices.push_back({x, -halfHeight, z, normal.x(), normal.y(), normal.z()});
272 }
273
274 // Top cap vertices (for proper normals)
275 int topCapStart = static_cast<int>(vertices.size());
276 for (int i = 0; i < segments; ++i) {
277 float angle = 2.0f * static_cast<float>(M_PI) * i / segments;
278 float x = radius * std::cos(angle);
279 float z = radius * std::sin(angle);
280 vertices.push_back({x, halfHeight, z, 0, 1, 0});
281 }
282
283 // Bottom cap vertices
284 int botCapStart = static_cast<int>(vertices.size());
285 for (int i = 0; i < segments; ++i) {
286 float angle = 2.0f * static_cast<float>(M_PI) * i / segments;
287 float x = radius * std::cos(angle);
288 float z = radius * std::sin(angle);
289 vertices.push_back({x, -halfHeight, z, 0, -1, 0});
290 }
291
292 // Side faces
293 int sideStart = 2;
294 for (int i = 0; i < segments; ++i) {
295 int next = (i + 1) % segments;
296 int topCur = sideStart + i * 2;
297 int botCur = sideStart + i * 2 + 1;
298 int topNext = sideStart + next * 2;
299 int botNext = sideStart + next * 2 + 1;
300
301 indices.insert(indices.end(), {static_cast<uint32_t>(topCur), static_cast<uint32_t>(topNext), static_cast<uint32_t>(botCur)});
302 indices.insert(indices.end(), {static_cast<uint32_t>(botCur), static_cast<uint32_t>(topNext), static_cast<uint32_t>(botNext)});
303 }
304
305 // Top cap
306 for (int i = 0; i < segments; ++i) {
307 int next = (i + 1) % segments;
308 indices.push_back(0); // center
309 indices.push_back(topCapStart + i);
310 indices.push_back(topCapStart + next);
311 }
312
313 // Bottom cap
314 for (int i = 0; i < segments; ++i) {
315 int next = (i + 1) % segments;
316 indices.push_back(1); // center
317 indices.push_back(botCapStart + next);
318 indices.push_back(botCapStart + i);
319 }
320
321 m_edgeIndexCount = static_cast<int>(indices.size());
322
323 m_edgeVertexData.resize(vertices.size() * sizeof(VertexData));
324 memcpy(m_edgeVertexData.data(), vertices.data(), m_edgeVertexData.size());
325
326 m_edgeIndexData.resize(indices.size() * sizeof(uint32_t));
327 memcpy(m_edgeIndexData.data(), indices.data(), m_edgeIndexData.size());
328
329 ++m_edgeGeometryGeneration;
330}
331
332//=============================================================================================================
333
334void NetworkObject::buildNodeInstances()
335{
336 if (m_network.isEmpty()) {
337 m_nodeInstanceCount = 0;
338 ++m_nodeInstanceGeneration;
339 return;
340 }
341
342 const auto& nodes = m_network.getNodes();
343 qint16 iMaxDegree = m_network.getMinMaxThresholdedDegrees().second;
344 if (iMaxDegree == 0)
345 iMaxDegree = 1;
346
347 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
348
349 std::vector<InstanceData> instances;
350 instances.reserve(nodes.size());
351
352 for (int i = 0; i < nodes.size(); ++i) {
353 qint16 degree = nodes[i]->getThresholdedDegree();
354 if (degree == 0)
355 continue;
356
357 const RowVectorXf& vert = nodes[i]->getVert();
358 QVector3D pos(vert(0), vert(1), vert(2));
359
360 // Scale: nodes with higher degree are larger
361 // Range: 0.0006 to 0.005 (same as disp3D)
362 float scaleFactor = (static_cast<float>(degree) / static_cast<float>(iMaxDegree)) * (0.005f - 0.0006f) + 0.0006f;
363
364 QMatrix4x4 m;
365 m.translate(pos);
366 m.scale(scaleFactor);
367
368 InstanceData inst;
369 const float* mPtr = m.constData();
370 for (int j = 0; j < 16; ++j)
371 inst.model[j] = mPtr[j];
372
373 // Color: colormap-based or fixed
374 if (vizInfo.sMethod == "Map") {
375 float normalized = static_cast<float>(degree) / static_cast<float>(iMaxDegree);
376 QRgb rgb = ColorMap::valueToColor(normalized, vizInfo.sColormap.isEmpty() ? m_colormap : vizInfo.sColormap);
377 QColor color(rgb);
378 float alpha = std::pow(normalized, 4.0f); // Same as disp3D
379 inst.color[0] = color.redF();
380 inst.color[1] = color.greenF();
381 inst.color[2] = color.blueF();
382 inst.color[3] = alpha;
383 } else {
384 inst.color[0] = vizInfo.colNodes[0] / 255.0f;
385 inst.color[1] = vizInfo.colNodes[1] / 255.0f;
386 inst.color[2] = vizInfo.colNodes[2] / 255.0f;
387 inst.color[3] = vizInfo.colNodes[3] / 255.0f;
388 }
389 inst.isSelected = 0.0f;
390
391 instances.push_back(inst);
392 }
393
394 m_nodeInstanceCount = static_cast<int>(instances.size());
395 m_nodeInstanceData.resize(m_nodeInstanceCount * sizeof(InstanceData));
396 if (m_nodeInstanceCount > 0) {
397 memcpy(m_nodeInstanceData.data(), instances.data(), m_nodeInstanceData.size());
398 }
399 ++m_nodeInstanceGeneration;
400
401 qDebug() << "NetworkObject: Built" << m_nodeInstanceCount << "node instances";
402}
403
404//=============================================================================================================
405
406void NetworkObject::buildEdgeInstances()
407{
408 if (m_network.isEmpty()) {
409 m_edgeInstanceCount = 0;
410 ++m_edgeInstanceGeneration;
411 return;
412 }
413
414 const auto& edges = m_network.getThresholdedEdges();
415 const auto& nodes = m_network.getNodes();
416
417 double dMaxWeight = m_network.getMinMaxThresholdedWeights().second;
418 double dMinWeight = m_network.getMinMaxThresholdedWeights().first;
419 double dWeightRange = dMaxWeight - dMinWeight;
420 if (dWeightRange == 0.0)
421 dWeightRange = 1.0;
422
423 VisualizationInfo vizInfo = m_network.getVisualizationInfo();
424
425 std::vector<InstanceData> instances;
426 instances.reserve(edges.size());
427
428 for (int i = 0; i < edges.size(); ++i) {
429 auto& edge = edges[i];
430 if (!edge->isActive())
431 continue;
432
433 int iStart = edge->getStartNodeID();
434 int iEnd = edge->getEndNodeID();
435
436 if (iStart < 0 || iStart >= nodes.size() || iEnd < 0 || iEnd >= nodes.size())
437 continue;
438
439 const RowVectorXf& vStart = nodes[iStart]->getVert();
440 const RowVectorXf& vEnd = nodes[iEnd]->getVert();
441
442 QVector3D startPos(vStart(0), vStart(1), vStart(2));
443 QVector3D endPos(vEnd(0), vEnd(1), vEnd(2));
444
445 if (startPos == endPos)
446 continue;
447
448 double dWeight = std::fabs(edge->getWeight());
449 if (dWeight == 0.0)
450 continue;
451
452 QVector3D diff = endPos - startPos;
453 QVector3D midPoint = startPos + diff / 2.0f;
454 float length = diff.length();
455
456 // Build transform: translate to midpoint, rotate Y-axis to diff direction, scale
457 float normalizedWeight = static_cast<float>(std::fabs((dWeight - dMinWeight) / dWeightRange));
458
459 // Cylinder radius: proportional to weight, range 0.0001 to 0.001
460 float edgeRadius = 0.0001f + normalizedWeight * 0.0009f;
461
462 QMatrix4x4 m;
463 m.translate(midPoint);
464 m.rotate(QQuaternion::rotationTo(QVector3D(0, 1, 0), diff.normalized()));
465 m.scale(edgeRadius, length, edgeRadius);
466
467 InstanceData inst;
468 const float* mPtr = m.constData();
469 for (int j = 0; j < 16; ++j)
470 inst.model[j] = mPtr[j];
471
472 // Color
473 if (vizInfo.sMethod == "Map") {
474 float normalized = (dMaxWeight != 0.0) ? static_cast<float>(std::fabs(dWeight / dMaxWeight)) : 0.0f;
475 QRgb rgb = ColorMap::valueToColor(normalized, vizInfo.sColormap.isEmpty() ? m_colormap : vizInfo.sColormap);
476 QColor color(rgb);
477 float alpha = std::pow(normalized, 1.5f); // Same as disp3D
478 inst.color[0] = color.redF();
479 inst.color[1] = color.greenF();
480 inst.color[2] = color.blueF();
481 inst.color[3] = alpha;
482 } else {
483 inst.color[0] = vizInfo.colEdges[0] / 255.0f;
484 inst.color[1] = vizInfo.colEdges[1] / 255.0f;
485 inst.color[2] = vizInfo.colEdges[2] / 255.0f;
486 inst.color[3] = vizInfo.colEdges[3] / 255.0f;
487 }
488 inst.isSelected = 0.0f;
489
490 instances.push_back(inst);
491 }
492
493 m_edgeInstanceCount = static_cast<int>(instances.size());
494 m_edgeInstanceData.resize(m_edgeInstanceCount * sizeof(InstanceData));
495 if (m_edgeInstanceCount > 0) {
496 memcpy(m_edgeInstanceData.data(), instances.data(), m_edgeInstanceData.size());
497 }
498 ++m_edgeInstanceGeneration;
499
500 qDebug() << "NetworkObject: Built" << m_edgeInstanceCount << "edge instances";
501}
502
503//=============================================================================================================
504
505} // namespace DISP3DLIB
Static scalar-to-colour lookup helpers (Jet, Hot, Bone, Viridis, Cool, RedBlue, MNE) used by every pl...
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
CPU streams of an instanced mesh: one shape (vertices, indices) drawn once per instance.
InstancedStreams edgeStreams() const
InstancedStreams nodeStreams() const
void load(const CONNECTIVITYLIB::Network &network, const QString &sColormap="Viridis")
void setThreshold(double dThreshold)
void setColormap(const QString &sColormap)