48#include <QResizeEvent>
52#include <QStandardItem>
53#include <QCoreApplication>
54#include <QElapsedTimer>
79 QColor color = item.color();
80 color.setAlphaF(color.alphaF() * item.alpha());
100 setMinimumSize(800, 600);
102 setAutoRenderTarget(
false);
104#if defined(WASMBUILD) || defined(__EMSCRIPTEN__)
106#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
108#elif defined(Q_OS_WIN)
109 setApi(Api::Direct3D11);
114 setMouseTracking(
true);
120 m_fpsLabel =
new QLabel(
this);
121 m_fpsLabel->setStyleSheet(
"color: white; font-weight: bold; font-family: monospace; font-size: 13px; background: transparent; padding: 5px;");
122 m_fpsLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
123 m_fpsLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
124 m_fpsLabel->setText(
"FPS: --.-\nVertices: 0");
125 m_fpsLabel->adjustSize();
126 m_fpsLabel->move(width() - m_fpsLabel->width() - 10, 10);
129 m_singleViewInfoLabel =
new QLabel(
this);
130 m_singleViewInfoLabel->setStyleSheet(
"color: white; font-family: monospace; font-size: 10px; background: rgba(0,0,0,110); border-radius: 3px; padding: 2px 4px;");
131 m_singleViewInfoLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
132 m_singleViewInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
133 m_singleViewInfoLabel->setText(
"");
134 m_singleViewInfoLabel->adjustSize();
135 m_singleViewInfoLabel->hide();
139 m_regionLabel =
new QLabel(
this);
140 m_regionLabel->setStyleSheet(
"color: white; font-weight: bold; font-family: sans-serif; font-size: 16px; background: transparent; padding: 5px;");
141 m_regionLabel->setText(
"");
142 m_regionLabel->move(10, 10);
143 m_regionLabel->resize(300, 30);
144 m_regionLabel->hide();
147 m_subViews.resize(kDefaultViewportCount);
148 m_viewportNameLabels.resize(kDefaultViewportCount,
nullptr);
149 m_viewportInfoLabels.resize(kDefaultViewportCount,
nullptr);
150 for (
int i = 0; i < kDefaultViewportCount; ++i) {
153 m_viewportNameLabels[i] =
new QLabel(
this);
154 m_viewportNameLabels[i]->setStyleSheet(
"color: white; font-weight: bold; font-family: sans-serif; font-size: 12px; background: transparent; padding: 2px 4px;");
155 m_viewportNameLabels[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
157 m_viewportNameLabels[i]->adjustSize();
158 m_viewportNameLabels[i]->hide();
160 m_viewportInfoLabels[i] =
new QLabel(
this);
161 m_viewportInfoLabels[i]->setStyleSheet(
"color: white; font-family: monospace; font-size: 10px; background: rgba(0,0,0,110); border-radius: 3px; padding: 2px 4px;");
162 m_viewportInfoLabels[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
163 m_viewportInfoLabels[i]->setAlignment(Qt::AlignLeft | Qt::AlignTop);
164 m_viewportInfoLabels[i]->setText(
"");
165 m_viewportInfoLabels[i]->adjustSize();
166 m_viewportInfoLabels[i]->hide();
169 m_verticalSeparator =
new QFrame(
this);
170 m_verticalSeparator->setFrameShape(QFrame::NoFrame);
171 m_verticalSeparator->setAttribute(Qt::WA_TransparentForMouseEvents);
172 m_verticalSeparator->hide();
174 m_horizontalSeparator =
new QFrame(
this);
175 m_horizontalSeparator->setFrameShape(QFrame::NoFrame);
176 m_horizontalSeparator->setAttribute(Qt::WA_TransparentForMouseEvents);
177 m_horizontalSeparator->hide();
179 QColor sepColor = palette().color(QPalette::Midlight);
180 if (sepColor.alpha() == 255) {
181 sepColor.setAlpha(180);
183 const QString sepStyle = QString(
"background-color: rgba(%1,%2,%3,%4);")
185 .arg(sepColor.green())
186 .arg(sepColor.blue())
187 .arg(sepColor.alpha());
188 m_verticalSeparator->setStyleSheet(sepStyle);
189 m_horizontalSeparator->setStyleSheet(sepStyle);
191 loadMultiViewSettings();
192 updateViewportSeparators();
193 updateOverlayLayout();
197 QColor(200, 255, 255, 160));
201 this, &BrainView::onSourceEstimateLoaded);
209 this, &BrainView::onRealtimeColorsAvailable);
213 this, &BrainView::onSensorStreamColorsAvailable);
217 m_videoOverlay = std::make_unique<DISP3DLIB::VideoOverlay>();
224 saveMultiViewSettings();
231 if (model == m_model)
235 disconnect(m_model,
nullptr,
this,
nullptr);
236 for (
auto it = m_itemSurfaceMap.cbegin(); it != m_itemSurfaceMap.cend(); ++it) {
237 for (
auto sit = m_surfaces.begin(); sit != m_surfaces.end();) {
238 sit = (sit.value() == it.value()) ? m_surfaces.erase(sit) : std::next(sit);
241 m_itemSurfaceMap.clear();
242 m_itemDipoleMap.clear();
243 m_hoveredItem =
nullptr;
244 m_activeSurface.reset();
257 if (m_model->rowCount() > 0) {
270 m_cameraRotation = rotation;
271 saveMultiViewSettings();
281 for (
int i = first; i <= last; ++i) {
282 QModelIndex index = m_model->index(i, 0, parent);
283 QStandardItem* item = m_model->itemFromIndex(index);
290 auto brainSurf = std::make_shared<BrainSurface>();
296 if (absItem->parent()) {
297 QString parentText = absItem->parent()->text();
298 if (parentText ==
"lh")
299 brainSurf->setHemi(0);
300 else if (parentText ==
"rh")
301 brainSurf->setHemi(1);
305 brainSurf->setVisible(surfItem->
isVisible());
310 m_itemSurfaceMap[item] = brainSurf;
314 if (absItem->parent()) {
315 key = absItem->parent()->text() +
"_" + surfItem->text();
317 key = surfItem->text();
319 m_surfaces[key] = brainSurf;
327 if (!m_activeSurface) {
328 m_activeSurface = brainSurf;
329 m_activeSurfaceType = surfItem->text();
337 auto brainSurf = std::make_shared<BrainSurface>();
340 brainSurf->fromBemSurface(bemSurfData, itemColor(*bemItem));
342 brainSurf->setVisible(bemItem->
isVisible());
345 QString surfName = bemItem->text().toLower();
346 if (surfName.contains(
"head") || surfName.contains(
"skin") || surfName.contains(
"scalp")) {
348 }
else if (surfName.contains(
"outer") && surfName.contains(
"skull")) {
350 }
else if (surfName.contains(
"inner") && surfName.contains(
"skull")) {
352 }
else if (surfName.contains(
"skull")) {
354 }
else if (surfName.contains(
"brain")) {
358 m_itemSurfaceMap[item] = brainSurf;
361 m_surfaces[
"bem_" + bemItem->text()] = brainSurf;
368 std::shared_ptr<BrainSurface> brainSurf;
370 QString parentText =
"";
371 if (sensItem->parent())
372 parentText = sensItem->parent()->text();
374 if (parentText.contains(
"MEG/Grad") && sensItem->
hasOrientation()) {
376 itemColor(*sensItem), sensItem->
scale());
377 }
else if (parentText.contains(
"MEG/Mag") && sensItem->
hasOrientation()) {
379 itemColor(*sensItem), sensItem->
scale());
383 itemColor(*sensItem));
387 m_itemSurfaceMap[item] = brainSurf;
393 QString key = keyPrefix + sensItem->text() +
"_" + QString::number((quintptr)sensItem);
394 m_surfaces[key] = brainSurf;
400 auto dipObject = std::make_shared<DipoleObject>();
401 dipObject->load(dipItem->
ecdSet());
402 dipObject->setVisible(dipItem->
isVisible());
403 dipObject->applyTransform(itemTransform(item));
405 m_itemDipoleMap[item] = dipObject;
411 const QVector<QVector3D>& positions = srcItem->
positions();
412 if (positions.isEmpty())
416 itemColor(*srcItem));
417 brainSurf->setVisible(srcItem->
isVisible());
418 m_itemSurfaceMap[item] = brainSurf;
420 QString key =
"srcsp_" + srcItem->text();
421 m_surfaces[key] = brainSurf;
427 const QVector<QVector3D>& positions = digItem->
positions();
428 if (positions.isEmpty())
432 itemColor(*digItem));
433 brainSurf->setVisible(digItem->
isVisible());
435 m_itemSurfaceMap[item] = brainSurf;
442 static int s_digKeyCounter = 0;
446 catName =
"cardinal";
458 catName = digItem->text().toLower().replace(
' ',
'_');
461 QString key = QStringLiteral(
"dig_%1_%2").arg(catName).arg(s_digKeyCounter++);
462 m_surfaces[key] = brainSurf;
465 if (m_itemSurfaceMap.contains(item))
466 m_itemSurfaceMap[item]->applyTransform(itemTransform(item));
469 if (m_model->hasChildren(index)) {
473 updateInflatedSurfaceTransforms();
475 m_vertexCountDirty =
true;
482 bool transformChanged =
false;
483 for (
int i = topLeft.row(); i <= bottomRight.row(); ++i) {
484 QModelIndex index = m_model->index(i, 0, topLeft.parent());
485 QStandardItem* item = m_model->itemFromIndex(index);
487 if (m_itemSurfaceMap.contains(item)) {
488 auto surf = m_itemSurfaceMap[item];
494 m_vertexCountDirty =
true;
497 surf->setColor(itemColor(*absItem));
500 surf->applyTransform(itemTransform(item));
501 transformChanged =
true;
512 if (m_itemDipoleMap.contains(item)) {
515 m_itemDipoleMap[item]->
setVisible(absItem->isVisible());
517 m_itemDipoleMap[item]->applyTransform(itemTransform(item));
520 if (transformChanged) {
521 updateInflatedSurfaceTransforms();
522 m_vertexCountDirty =
true;
533 subViewForTarget(m_visualizationEditTarget).surfaceType = type;
535 m_activeSurfaceType = type;
538 QString key =
"lh_" + type;
539 if (m_surfaces.contains(key))
540 m_activeSurface = m_surfaces[key];
543 if (m_surfaces.contains(key))
544 m_activeSurface = m_surfaces[key];
547 updateInflatedSurfaceTransforms();
548 saveMultiViewSettings();
551 m_vertexCountDirty =
true;
556void BrainView::updateSceneBounds()
558 QVector3D min(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
559 QVector3D max(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
560 bool hasContent =
false;
563 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
564 if (it.value()->isVisible()) {
565 QVector3D sMin, sMax;
566 it.value()->boundingBox(sMin, sMax);
568 min.setX(std::min(min.x(), sMin.x()));
569 min.setY(std::min(min.y(), sMin.y()));
570 min.setZ(std::min(min.z(), sMin.z()));
572 max.setX(std::max(max.x(), sMax.x()));
573 max.setY(std::max(max.y(), sMax.y()));
574 max.setZ(std::max(max.z(), sMax.z()));
579 for (
auto it = m_itemDipoleMap.cbegin(); it != m_itemDipoleMap.cend(); ++it) {
580 QVector3D dMin, dMax;
581 if (it.value()->isVisible() && it.value()->boundingBox(dMin, dMax)) {
582 min = QVector3D(std::min(min.x(), dMin.x()), std::min(min.y(), dMin.y()), std::min(min.z(), dMin.z()));
583 max = QVector3D(std::max(max.x(), dMax.x()), std::max(max.y(), dMax.y()), std::max(max.z(), dMax.z()));
589 m_sceneCenter = (min + max) * 0.5f;
591 QVector3D diag = max - min;
592 m_sceneSize = std::max(diag.x(), std::max(diag.y(), diag.z()));
595 if (m_sceneSize < 0.01f)
600 m_sceneCenter = QVector3D(0, 0, 0);
610 subViewForTarget(m_visualizationEditTarget).brainShader = mode;
612 m_brainShaderMode = mode;
613 saveMultiViewSettings();
623 const int prev = m_visualizationEditTarget;
626 const SubView& sv = subViewForTarget(m_visualizationEditTarget);
633 const bool remapMegSurface = (m_fieldMapper.megFieldMapOnHead() != visibility.
megFieldMapOnHead);
635 m_dipolesVisible = visibility.
dipoles;
636 m_networkVisible = visibility.
network;
644 if (m_fieldMapper.isLoaded()) {
645 if (remapMegSurface) {
646 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
648 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
652 updateViewportLabelHighlight();
654 saveMultiViewSettings();
656 if (prev != m_visualizationEditTarget) {
665 return m_visualizationEditTarget;
672 return subViewForTarget(target).surfaceType;
705const ViewVisibilityProfile& BrainView::visibilityProfileForTarget(
int target)
const
712SubView& BrainView::subViewForTarget(
int target)
715 return (normalized < 0) ? m_singleView : m_subViews[normalized];
720const SubView& BrainView::subViewForTarget(
int target)
const
723 return (normalized < 0) ? m_singleView : m_subViews[normalized];
735 return visibilityProfileForTarget(target).isObjectVisible(
object);
742 return visibilityProfileForTarget(target).megFieldMapOnHead;
747void BrainView::updateInflatedSurfaceTransforms()
749 const bool needsInflated = (m_singleView.
surfaceType ==
"inflated") || std::any_of(m_subViews.cbegin(), m_subViews.cend(), [](
const SubView& sv) { return sv.surfaceType ==
"inflated"; });
751 const QString lhKey =
"lh_inflated";
752 const QString rhKey =
"rh_inflated";
754 if (!m_surfaces.contains(lhKey) || !m_surfaces.contains(rhKey)) {
758 auto lhSurf = m_surfaces[lhKey];
759 auto rhSurf = m_surfaces[rhKey];
761 lhSurf->applyTransform(itemTransform(m_itemSurfaceMap.key(lhSurf)));
762 rhSurf->applyTransform(itemTransform(m_itemSurfaceMap.key(rhSurf)));
764 if (!needsInflated) {
768 const float lhMaxX = lhSurf->maxX();
769 const float rhMinX = rhSurf->minX();
771 const float gap = 0.005f;
772 const float lhOffset = -gap / 2.0f - lhMaxX;
773 const float rhOffset = gap / 2.0f - rhMinX;
775 lhSurf->translateX(lhOffset);
776 rhSurf->translateX(rhOffset);
783 subViewForTarget(m_visualizationEditTarget).bemShader = mode;
785 m_bemShaderMode = mode;
786 saveMultiViewSettings();
795 m_singleView.bemShader = m_singleView.brainShader;
796 for (
int i = 0; i < m_subViews.size(); ++i) {
797 m_subViews[i].bemShader = m_subViews[i].brainShader;
800 m_bemShaderMode = subViewForTarget(m_visualizationEditTarget).bemShader;
802 saveMultiViewSettings();
810 if (
object.isEmpty())
813 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
814 profile.setObjectVisible(
object, visible);
819 if (type == QLatin1String(
"MEG")) {
820 profile.sensMegGrad = visible;
821 profile.sensMegMag = visible;
822 }
else if (type == QLatin1String(
"EEG")) {
824 }
else if (type == QLatin1String(
"Digitizer")) {
825 profile.digCardinal = visible;
826 profile.digHpi = visible;
827 profile.digEeg = visible;
828 profile.digExtra = visible;
831 saveMultiViewSettings();
838 if (m_applySensorTrans != enabled) {
839 m_applySensorTrans = enabled;
840 refreshSensorTransforms();
850 m_megHelmetOverridePath = path;
855 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
856 profile.dipoles = visible;
857 m_dipolesVisible = visible;
858 saveMultiViewSettings();
868 SubView& sv = subViewForTarget(m_visualizationEditTarget);
871 m_currentVisMode = mode;
877 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
878 const QString& key = it.key();
879 if (key.startsWith(
"lh_") || key.startsWith(
"rh_")) {
880 it.value()->setVisualizationMode(mode);
884 saveMultiViewSettings();
893 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
895 profile.lh = visible;
896 }
else if (hemiIdx == 1) {
897 profile.rh = visible;
899 saveMultiViewSettings();
908 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
909 profile.setObjectVisible(
"bem_" + name, visible);
910 saveMultiViewSettings();
917 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
918 if (it.key().startsWith(
"bem_")) {
919 it.value()->setUseDefaultColor(enabled);
930 m_lightingEnabled = enabled;
940 if (m_pAutoRotateTimer) {
941 m_pAutoRotateTimer->stop();
946 if (!m_pAutoRotateTimer) {
947 m_pAutoRotateTimer =
new QTimer(
this);
948 m_pAutoRotateTimer->setInterval(33);
950 connect(m_pAutoRotateTimer, &QTimer::timeout,
this, [
this]() {
954 const QPoint step(2, 0);
957 for (
int i = 0; i < m_subViews.size(); ++i) {
971 m_pAutoRotateTimer->start();
978 return m_pAutoRotateTimer && m_pAutoRotateTimer->isActive();
986 if (vecPositions.size() < 2) {
992 m_headPath = std::make_unique<PolylineObject>();
995 m_headPath->setPoints(vecPositions);
996 m_headPath->setVisible(
true);
1011 m_sceneDirty =
true;
1019 const QDateTime now = QDateTime::currentDateTime();
1020 return takeScreenshot(QStringLiteral(
"./Screenshots/%1-%2-3DView.png").arg(now.toString(QStringLiteral(
"yyyy_MM_dd")), now.toString(QStringLiteral(
"hh_mm_ss"))));
1027 const QImage frame = grabFramebuffer();
1028 if (frame.isNull()) {
1029 qWarning() <<
"[BrainView::takeScreenshot] No rendered frame - nothing written to" << fileName;
1032 if (!QDir().mkpath(QFileInfo(fileName).absolutePath()) || !frame.save(fileName)) {
1033 qWarning() <<
"[BrainView::takeScreenshot] Cannot write" << fileName;
1042 const QString& surfaceType)
1047 setAttribute(Qt::WA_DontShowOnScreen,
true);
1048 resize(width, height);
1052 m_singleView.surfaceType = surfaceType;
1059 QElapsedTimer timer;
1061 while (timer.elapsed() < 5000) {
1062 QCoreApplication::processEvents(QEventLoop::AllEvents, 16);
1063 if (m_renderer && !m_surfaces.isEmpty())
1069 setAttribute(Qt::WA_DontShowOnScreen,
false);
1074 m_sceneDirty =
true;
1075 updateSceneBounds();
1078 for (
int i = 0; i < 5; ++i)
1079 QCoreApplication::processEvents(QEventLoop::AllEvents, 16);
1083 QImage img = grabFramebuffer();
1086 setAttribute(Qt::WA_DontShowOnScreen,
false);
1091 return img.save(path,
"PNG");
1099 m_isDraggingSplitter =
false;
1102 saveMultiViewSettings();
1103 updateViewportSeparators();
1104 updateOverlayLayout();
1105 m_sceneDirty =
true;
1114 saveMultiViewSettings();
1115 updateViewportSeparators();
1116 updateOverlayLayout();
1117 m_sceneDirty =
true;
1125 count = std::clamp(count, 1,
static_cast<int>(m_subViews.size()));
1126 m_viewCount = count;
1130 m_isDraggingSplitter =
false;
1137 if (m_visualizationEditTarget < 0)
1142 for (
int i = 0; i < m_subViews.size(); ++i)
1143 m_subViews[i].enabled = (i < count);
1145 saveMultiViewSettings();
1146 updateViewportSeparators();
1147 updateOverlayLayout();
1148 m_sceneDirty =
true;
1157 m_layout.resetSplits();
1158 m_multiSplitX = m_layout.splitX();
1159 m_multiSplitY = m_layout.splitY();
1160 saveMultiViewSettings();
1161 updateViewportSeparators();
1162 updateOverlayLayout();
1163 m_sceneDirty =
true;
1169 if (index < 0 || index >= m_subViews.size()) {
1173 return m_subViews[index].enabled;
1178int BrainView::enabledViewportCount()
const
1185 for (
int i = 0; i < m_subViews.size(); ++i) {
1186 if (m_subViews[i].enabled) {
1191 return numEnabled > 0 ? numEnabled : 1;
1196QVector<int> BrainView::enabledViewportIndices()
const
1200 for (
int i = 0; i < m_subViews.size(); ++i) {
1201 if (m_subViews[i].enabled)
1214int BrainView::viewportIndexAt(
const QPoint& pos)
const
1220 const auto enabledViewports = enabledViewportIndices();
1221 return m_layout.viewportIndexAt(pos, enabledViewports, size());
1226QRect BrainView::multiViewSlotRect(
int slot,
int numEnabled,
const QSize& outputSize)
const
1228 return m_layout.slotRect(slot, numEnabled, outputSize);
1233SplitterHit BrainView::hitTestSplitter(
const QPoint& pos,
int numEnabled,
const QSize& outputSize)
const
1235 if (m_viewMode !=
MultiView || numEnabled <= 1) {
1238 return m_layout.hitTestSplitter(pos, numEnabled, outputSize);
1243void BrainView::updateSplitterCursor(
const QPoint& pos)
1245 const SplitterHit hit = hitTestSplitter(pos, enabledViewportCount(), size());
1247 if (shape == Qt::ArrowCursor) {
1256void BrainView::updateViewportSeparators()
1258 if (!m_verticalSeparator || !m_horizontalSeparator) {
1262 m_verticalSeparator->hide();
1263 m_horizontalSeparator->hide();
1265 const int numEnabled = enabledViewportCount();
1266 if (m_viewMode !=
MultiView || numEnabled <= 1) {
1271 m_layout.separatorGeometries(numEnabled, size(), vRect, hRect);
1273 if (!vRect.isEmpty()) {
1274 m_verticalSeparator->setGeometry(vRect);
1275 m_verticalSeparator->show();
1276 m_verticalSeparator->raise();
1278 if (!hRect.isEmpty()) {
1279 m_horizontalSeparator->setGeometry(hRect);
1280 m_horizontalSeparator->show();
1281 m_horizontalSeparator->raise();
1284 updateOverlayLayout();
1289void BrainView::updateOverlayLayout()
1291 const auto enabledViewports = enabledViewportIndices();
1294 m_fpsLabel->setVisible(m_infoPanelVisible);
1295 m_fpsLabel->adjustSize();
1296 const int perfBottomMargin = 2;
1299 m_fpsLabel->move(width() - m_fpsLabel->width() - 10,
1300 height() - m_fpsLabel->height() - perfBottomMargin);
1302 m_fpsLabel->move(width() - m_fpsLabel->width() - 10,
1303 height() - m_fpsLabel->height() - perfBottomMargin);
1306 m_fpsLabel->raise();
1309 if (m_singleViewInfoLabel) {
1310 const bool showSingleInfo = (m_viewMode ==
SingleView) && m_infoPanelVisible;
1311 m_singleViewInfoLabel->setVisible(showSingleInfo);
1312 if (showSingleInfo) {
1313 m_singleViewInfoLabel->adjustSize();
1314 m_singleViewInfoLabel->move(width() - m_singleViewInfoLabel->width() - 8, 8);
1315 m_singleViewInfoLabel->raise();
1319 if (m_regionLabel) {
1320 const int regionY = (m_viewMode ==
MultiView) ? 38 : 10;
1321 m_regionLabel->move(10, regionY);
1322 if (!m_regionLabel->text().isEmpty()) {
1323 m_regionLabel->raise();
1327 for (
int i = 0; i < m_viewportNameLabels.size(); ++i) {
1328 if (m_viewportNameLabels[i]) {
1329 m_viewportNameLabels[i]->hide();
1331 if (m_viewportInfoLabels[i]) {
1332 m_viewportInfoLabels[i]->hide();
1340 const int numEnabled = enabledViewports.size();
1341 const QSize overlaySize = size();
1342 for (
int slot = 0; slot < numEnabled; ++slot) {
1343 const int vp = enabledViewports[slot];
1344 QLabel* label = m_viewportNameLabels[vp];
1345 QLabel* infoLabel = m_viewportInfoLabels[vp];
1350 const int preset = std::clamp(m_subViews[vp].preset, 0, 6);
1353 const QRect pane = multiViewSlotRect(slot, numEnabled, overlaySize);
1354 label->adjustSize();
1355 label->move(pane.x() + 8, pane.y() + 8);
1356 label->setVisible(
true);
1360 infoLabel->adjustSize();
1361 infoLabel->move(pane.x() + pane.width() - infoLabel->width() - 8,
1363 infoLabel->setVisible(m_infoPanelVisible);
1368 updateViewportLabelHighlight();
1373void BrainView::updateViewportLabelHighlight()
1375 static const QString normalStyle =
1376 QStringLiteral(
"color: white; font-weight: bold; font-family: sans-serif; "
1377 "font-size: 12px; background: transparent; padding: 2px 4px;");
1378 static const QString selectedStyle =
1379 QStringLiteral(
"color: #FFD54F; font-weight: bold; font-family: sans-serif; "
1380 "font-size: 13px; background: rgba(255,213,79,40); "
1381 "border: 1px solid #FFD54F; border-radius: 3px; padding: 2px 6px;");
1383 for (
int i = 0; i < m_viewportNameLabels.size(); ++i) {
1384 if (!m_viewportNameLabels[i])
1386 const bool selected = (m_viewMode ==
MultiView && m_visualizationEditTarget == i);
1387 m_viewportNameLabels[i]->setStyleSheet(selected ? selectedStyle : normalStyle);
1388 m_viewportNameLabels[i]->adjustSize();
1394void BrainView::logPerspectiveRotation(
const QString& context)
const
1401void BrainView::loadMultiViewSettings()
1404 settings.beginGroup(QStringLiteral(
"BrainView"));
1406 m_multiSplitX = settings.value(
"multiSplitX", 0.5f).toFloat();
1407 m_multiSplitY = settings.value(
"multiSplitY", 0.5f).toFloat();
1409 const int savedViewMode = settings.value(
"viewMode",
static_cast<int>(
SingleView)).toInt();
1410 m_viewCount = std::clamp(settings.value(
"viewCount", 1).toInt(), 1,
static_cast<int>(m_subViews.size()));
1414 const bool hasCameraQuat = settings.contains(
"cameraRotW") && settings.contains(
"cameraRotX") && settings.contains(
"cameraRotY") && settings.contains(
"cameraRotZ");
1415 if (hasCameraQuat) {
1416 const float w = settings.value(
"cameraRotW", 1.0f).toFloat();
1417 const float x = settings.value(
"cameraRotX", 0.0f).toFloat();
1418 const float y = settings.value(
"cameraRotY", 0.0f).toFloat();
1419 const float z = settings.value(
"cameraRotZ", 0.0f).toFloat();
1420 m_cameraRotation = QQuaternion(w, x, y, z);
1421 if (m_cameraRotation.lengthSquared() <= std::numeric_limits<float>::epsilon()) {
1422 m_cameraRotation = QQuaternion();
1424 m_cameraRotation.normalize();
1429 for (
int i = 0; i < m_subViews.size(); ++i) {
1431 m_subViews[i].enabled = (i < m_viewCount);
1435 m_singleView.load(settings,
"single_", m_cameraRotation);
1436 for (
int i = 0; i < m_subViews.size(); ++i)
1437 m_subViews[i].load(settings, QStringLiteral(
"multi%1_").arg(i), m_cameraRotation);
1439 const int maxIdx =
static_cast<int>(m_subViews.size()) - 1;
1441 settings.value(
"visualizationEditTarget", -1).toInt(), maxIdx);
1443 m_infoPanelVisible = settings.value(
"infoPanelVisible",
true).toBool();
1445 settings.endGroup();
1447 m_multiSplitX = std::clamp(m_multiSplitX, 0.15f, 0.85f);
1448 m_multiSplitY = std::clamp(m_multiSplitY, 0.15f, 0.85f);
1449 m_layout.setSplitX(m_multiSplitX);
1450 m_layout.setSplitY(m_multiSplitY);
1459 QTimer::singleShot(0,
this, [
this]() {
1467void BrainView::saveMultiViewSettings()
const
1470 settings.beginGroup(QStringLiteral(
"BrainView"));
1471 settings.setValue(
"multiSplitX", m_multiSplitX);
1472 settings.setValue(
"multiSplitY", m_multiSplitY);
1473 settings.setValue(
"viewMode",
static_cast<int>(m_viewMode));
1474 settings.setValue(
"viewCount", m_viewCount);
1475 settings.setValue(
"cameraRotW", m_cameraRotation.scalar());
1476 settings.setValue(
"cameraRotX", m_cameraRotation.x());
1477 settings.setValue(
"cameraRotY", m_cameraRotation.y());
1478 settings.setValue(
"cameraRotZ", m_cameraRotation.z());
1479 for (
int i = 0; i < m_subViews.size(); ++i)
1480 settings.setValue(QStringLiteral(
"viewportEnabled%1").arg(i), m_subViews[i].enabled);
1481 settings.setValue(
"visualizationEditTarget", m_visualizationEditTarget);
1482 settings.setValue(
"infoPanelVisible", m_infoPanelVisible);
1485 m_singleView.save(settings,
"single_");
1486 for (
int i = 0; i < m_subViews.size(); ++i)
1487 m_subViews[i].save(settings, QStringLiteral(
"multi%1_").arg(i));
1489 settings.endGroup();
1496 if (index >= 0 && index < m_subViews.size()) {
1497 m_subViews[index].enabled = enabled;
1498 saveMultiViewSettings();
1499 updateViewportSeparators();
1500 updateOverlayLayout();
1501 m_sceneDirty =
true;
1510 if (index < 0 || index >=
static_cast<int>(m_subViews.size()))
1512 preset = std::clamp(preset, 0, 6);
1513 if (m_subViews[index].preset == preset)
1515 m_subViews[index].preset = preset;
1516 saveMultiViewSettings();
1517 updateOverlayLayout();
1518 m_sceneDirty =
true;
1526 m_cameraRotation = QQuaternion();
1527 m_singleView.zoom = 0.0f;
1528 saveMultiViewSettings();
1529 m_sceneDirty =
true;
1537 if (index < 0 || index >=
static_cast<int>(m_subViews.size())) {
1541 m_subViews[index].zoom = 0.0f;
1542 m_subViews[index].pan = QVector2D();
1543 m_subViews[index].perspectiveRotation = QQuaternion();
1544 saveMultiViewSettings();
1545 m_sceneDirty =
true;
1554 for (
int i = 0; i < m_subViews.size(); ++i) {
1555 const bool wasEnabled = m_subViews[i].enabled;
1557 m_subViews[i].enabled = wasEnabled;
1559 m_cameraRotation = QQuaternion();
1560 saveMultiViewSettings();
1561 updateOverlayLayout();
1562 m_sceneDirty =
true;
1570 if (index < 0 || index >=
static_cast<int>(m_subViews.size()))
1572 return std::clamp(m_subViews[index].preset, 0, 6);
1579 m_infoPanelVisible = visible;
1580 saveMultiViewSettings();
1581 updateOverlayLayout();
1588 QRhiWidget::resizeEvent(event);
1589 updateViewportSeparators();
1590 updateOverlayLayout();
1599 m_renderer = std::make_unique<BrainRenderer>();
1604 m_renderer->ensureRenderTargets(rhi(), colorTexture(), colorTexture()->pixelSize());
1612 bool hasSurfaces = !m_surfaces.isEmpty();
1613 bool hasDipoles = !m_itemDipoleMap.isEmpty() || m_dipoles;
1616 if (!hasSurfaces && !hasDipoles) {
1619 m_renderer = std::make_unique<BrainRenderer>();
1621 m_renderer->ensureRenderTargets(rhi(), colorTexture(), colorTexture()->pixelSize());
1622 m_renderer->initialize(rhi(), m_renderer->rtClear()->renderPassDescriptor(), sampleCount());
1623 m_renderer->beginFrame(cb);
1624 m_renderer->endPass(cb);
1629 if (!m_activeSurface && !m_surfaces.isEmpty()) {
1630 m_activeSurface = m_surfaces.begin().value();
1635 if (m_fpsTimer.elapsed() >= 500) {
1636 float fps = m_frameCount / (m_fpsTimer.elapsed() / 1000.0f);
1639 if (m_vertexCountDirty) {
1640 auto countVerticesForSubView = [
this](
const SubView& sv) -> qint64 {
1642 for (
auto it = m_surfaces.cbegin(); it != m_surfaces.cend(); ++it) {
1643 const QString& key = it.key();
1644 auto surface = it.value();
1653 if (!surface->isVisible())
1656 total += surface->vertexCount();
1663 for (
int vp : enabledViewportIndices()) {
1664 vCount += countVerticesForSubView(m_subViews[vp]);
1667 vCount = countVerticesForSubView(m_singleView);
1669 m_cachedVertexCount = vCount;
1670 m_vertexCountDirty =
false;
1673 m_fpsLabel->setText(QString(
"FPS: %1\nVertices: %2").arg(fps, 0,
'f', 1).arg(m_cachedVertexCount));
1674 updateOverlayLayout();
1675 m_fpsLabel->raise();
1677 m_fpsTimer.restart();
1681 m_renderer->ensureRenderTargets(rhi(), colorTexture(), colorTexture()->pixelSize());
1682 m_renderer->initialize(rhi(), m_renderer->rtClear()->renderPassDescriptor(), sampleCount());
1685 QSize outputSize = m_renderer->rtClear()->pixelSize();
1688 const auto enabledViewports = enabledViewportIndices();
1689 int numEnabled = enabledViewports.size();
1709 QRhiResourceUpdateBatch* preUpload = rhi()->nextResourceUpdateBatch();
1710#ifndef __EMSCRIPTEN__
1713 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1714 it.value()->updateBuffers(rhi(), preUpload);
1716 if (m_debugPointerSurface) {
1717 m_debugPointerSurface->updateBuffers(rhi(), preUpload);
1719 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end(); ++it) {
1720 it.value()->updateBuffers(rhi(), preUpload);
1723 m_dipoles->updateBuffers(rhi(), preUpload);
1726 if (m_videoOverlay && m_videoOverlay->isEnabled()) {
1727 m_renderer->prepareVideoOverlay(rhi(), preUpload, m_videoOverlay.get());
1731 for (
int i = 0; i < kMaxSliceSlots; ++i) {
1732 if (m_slices[i] && m_sliceVisible[i]) {
1733 m_renderer->prepareSlice(rhi(), preUpload, m_slices[i], i);
1735 m_renderer->prepareSlice(rhi(), preUpload,
nullptr, i);
1739#ifdef __EMSCRIPTEN__
1746 const SubView& sv = (m_viewMode ==
MultiView) ? m_subViews[0] : m_singleView;
1748 QVector<BrainSurface*> allSurfaces;
1751 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1758 allSurfaces.append(it.value().get());
1762 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1763 if (!it.key().startsWith(
"srcsp_"))
1767 if (!it.value()->isVisible())
1769 allSurfaces.append(it.value().get());
1773 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1774 if (!it.key().startsWith(
"dig_"))
1778 if (!it.value()->isVisible())
1780 allSurfaces.append(it.value().get());
1784 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1785 bool isSensor = it.key().startsWith(
"sens_");
1786 bool isBem = it.key().startsWith(
"bem_");
1787 if (!isSensor && !isBem)
1791 if (!it.value()->isVisible())
1793 allSurfaces.append(it.value().get());
1796 m_renderer->prepareMergedSurfaces(rhi(), preUpload, allSurfaces, QStringLiteral(
"default"));
1801 cb->resourceUpdate(preUpload);
1805 m_renderer->beginFrame(cb);
1807 for (
int slot = 0; slot < numEnabled; ++slot) {
1808 int vp = (m_viewMode ==
MultiView) ? enabledViewports[slot] : 0;
1809 const SubView& sv = (m_viewMode ==
MultiView) ? m_subViews[vp] : m_singleView;
1811 const QRect paneRect = (m_viewMode ==
MultiView)
1812 ? multiViewSlotRect(slot, numEnabled, outputSize)
1813 : QRect(0, 0, outputSize.width(), outputSize.height());
1815 QRect renderRect = paneRect;
1816 if (m_viewMode ==
MultiView && numEnabled > 1) {
1817 constexpr int separatorPx = 2;
1819 if (numEnabled == 2) {
1821 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1823 }
else if (numEnabled == 3) {
1827 renderRect.setHeight(std::max(1, renderRect.height() - separatorPx));
1828 }
else if (slot == 1) {
1830 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1834 const int col = slot % 2;
1835 const int row = slot / 2;
1837 const bool hasRightNeighbor = (col == 0) && (slot + 1 < numEnabled) && ((slot / 2) == ((slot + 1) / 2));
1838 const bool hasBottomNeighbor = (row == 0) && (slot + 2 < numEnabled);
1840 if (hasRightNeighbor) {
1841 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1843 if (hasBottomNeighbor) {
1844 renderRect.setHeight(std::max(1, renderRect.height() - separatorPx));
1849 const int viewX = renderRect.x();
1850 const int viewY = outputSize.height() - (renderRect.y() + renderRect.height());
1851 const int viewW = std::max(1, renderRect.width());
1852 const int viewH = std::max(1, renderRect.height());
1854 QRhiViewport viewport(viewX, viewY, viewW, viewH);
1855 QRhiScissor scissor(viewX, viewY, viewW, viewH);
1856 const float aspectRatio = float(viewW) / float(viewH);
1859 cb->setViewport(viewport);
1860 cb->setScissor(scissor);
1863 const QVector3D effectiveCenter = m_cameraFocusOverride ? m_cameraFocusCenter : m_sceneCenter;
1864 const float effectiveSize = m_cameraFocusOverride ? m_cameraFocusSize : m_sceneSize;
1865 m_camera.setSceneCenter(effectiveCenter);
1866 m_camera.setSceneSize(effectiveSize);
1867 m_camera.setRotation(m_cameraRotation);
1868 m_camera.setZoom(m_singleView.zoom);
1870 ? m_camera.computeMultiView(sv, aspectRatio)
1871 : m_camera.computeSingleView(aspectRatio);
1874 sceneData.
mvp = rhi()->clipSpaceCorrMatrix();
1902#ifndef __EMSCRIPTEN__
1903 QStringList drawnKeys;
1905 const QString drawnInfo = QStringLiteral(
"merged");
1908 if (m_viewMode ==
MultiView && m_viewportInfoLabels[vp]) {
1909 m_viewportInfoLabels[vp]->setText(
1910 QString(
"Shader: %1\nSurface: %2\nOverlay: %3")
1912 }
else if (m_viewMode ==
SingleView && m_singleViewInfoLabel) {
1913 m_singleViewInfoLabel->setText(
1914 QString(
"Shader: %1\nSurface: %2\nOverlay: %3")
1918#ifndef __EMSCRIPTEN__
1920 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1925 drawnKeys << it.key();
1926 m_renderer->renderSurface(cb, rhi(), sceneData, it.value().get(), currentShader);
1930#ifndef __EMSCRIPTEN__
1933 const QString drawnInfo = drawnKeys.isEmpty() ? QStringLiteral(
"none") : drawnKeys.join(QStringLiteral(
", "));
1934 if (m_viewMode ==
MultiView && m_viewportInfoLabels[vp]) {
1935 m_viewportInfoLabels[vp]->setText(m_viewportInfoLabels[vp]->text() + QStringLiteral(
"\nDrawn: ") + drawnInfo);
1936 }
else if (m_viewMode ==
SingleView && m_singleViewInfoLabel) {
1937 m_singleViewInfoLabel->setText(m_singleViewInfoLabel->text() + QStringLiteral(
"\nDrawn: ") + drawnInfo);
1942#ifdef __EMSCRIPTEN__
1953 m_renderer->drawMergedSurfaces(cb, rhi(), sceneData, currentShader, QStringLiteral(
"default"));
1967 const QString& megFieldKey = m_fieldMapper.megSurfaceKey();
1968 const QString& eegFieldKey = m_fieldMapper.eegSurfaceKey();
1979 QVector<DrawItem> opaqueDraws;
1980 QVector<DrawItem> transparentDraws;
1982 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1983 const QString& key = it.key();
1989 }
else if (key.startsWith(
"srcsp_") || key.startsWith(
"dig_")) {
1994 if (key.startsWith(QLatin1String(
"dig_live_t_")) || key.startsWith(QLatin1String(
"dig_ray_")) || key.startsWith(QLatin1String(
"dig_probe_"))) {
1995 QVector3D bmin, bmax;
1997 QVector3D ctr = (bmin + bmax) * 0.5f;
1998 float dist = (sceneData.
cameraPos - ctr).lengthSquared();
2003 opaqueDraws.append({surf, currentShader,
2008 bool isSensor = key.startsWith(
"sens_");
2009 bool isBem = key.startsWith(
"bem_");
2010 if (!isSensor && !isBem)
2017 QVector3D bmin, bmax;
2019 QVector3D center = (bmin + bmax) * 0.5f;
2020 float dist = (sceneData.
cameraPos - center).lengthSquared();
2024 if (key == megFieldKey && !megFieldVisible)
2026 else if (key == eegFieldKey && !eegFieldVisible)
2029 transparentDraws.append({surf, mode, itemOverlay, dist, -1});
2034 std::sort(transparentDraws.begin(), transparentDraws.end(),
2035 [](
const DrawItem& a,
const DrawItem& b) { return a.distSq > b.distSq; });
2038 QRhiResourceUpdateBatch* surfBatch = rhi()->nextResourceUpdateBatch();
2041 for (
auto& item : opaqueDraws) {
2043 item.uniformOffset = m_renderer->prepareSurfaceDraw(surfBatch, batchData, item.surface);
2045 for (
auto& item : transparentDraws) {
2047 item.uniformOffset = m_renderer->prepareSurfaceDraw(surfBatch, batchData, item.surface);
2051 int sliceOffsets[kMaxSliceSlots] = {-1, -1, -1};
2053 for (
int i = 0; i < kMaxSliceSlots; ++i) {
2054 if (m_slices[i] && m_sliceVisible[i]) {
2055 sliceOffsets[i] = m_renderer->prepareSliceDraw(surfBatch, sceneData, i);
2060 cb->resourceUpdate(surfBatch);
2063 cb->setViewport(viewport);
2064 cb->setScissor(scissor);
2067 for (
const auto& item : opaqueDraws)
2068 m_renderer->issueSurfaceDraw(cb, item.surface, item.mode, item.uniformOffset);
2075 for (
int i = 0; i < kMaxSliceSlots; ++i) {
2076 if (sliceOffsets[i] >= 0) {
2077 m_renderer->issueSliceDraw(cb, i, sliceOffsets[i]);
2082 const bool hasVideoOverlay = m_videoOverlay && m_videoOverlay->isEnabled() && m_videoOverlay->hasFrame();
2083 if (hasVideoOverlay) {
2085 if (m_surfaces.contains(QStringLiteral(
"bem_head"))) {
2086 videoOverlayTargetSurface = m_surfaces[QStringLiteral(
"bem_head")].get();
2088 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
2090 videoOverlayTargetSurface = it.value().get();
2097 bool videoOverlayDrawn =
false;
2098 auto drawVideoOverlay = [&]() {
2099 if (videoOverlayDrawn)
2101 if (videoOverlayTargetSurface) {
2102 m_renderer->renderVideoOverlayOnSurface(cb, rhi(), sceneData,
2103 m_videoOverlay.get(), videoOverlayTargetSurface);
2104 }
else if (hasVideoOverlay) {
2105 m_renderer->renderVideoOverlay(cb, rhi(), sceneData, m_videoOverlay.get());
2107 videoOverlayDrawn =
true;
2110 for (
const auto& item : transparentDraws) {
2111 m_renderer->issueSurfaceDraw(cb, item.surface, item.mode, item.uniformOffset);
2112 if (item.surface == videoOverlayTargetSurface) {
2119 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end(); ++it) {
2121 m_renderer->renderDipoles(cb, rhi(), sceneData, it.value().get());
2126 m_renderer->renderDipoles(cb, rhi(), sceneData, m_dipoles.get());
2131 m_renderer->renderNetwork(cb, rhi(), sceneData, m_network.get());
2137 m_renderer->renderPolyline(cb, rhi(), sceneData, m_headPath.get());
2141 if (m_hasIntersection && m_debugPointerSurface) {
2145 QMatrix4x4 translation;
2146 translation.translate(m_lastIntersectionPoint);
2157 m_renderer->endPass(cb);
2167 if (e->button() == Qt::LeftButton) {
2168 m_perspectiveRotatedSincePress =
false;
2169 m_draggedSincePress =
false;
2172 if (e->button() == Qt::LeftButton && m_viewMode ==
MultiView) {
2173 const int clickedVp = viewportIndexAt(e->pos());
2174 if (clickedVp >= 0 && m_viewportNameLabels[clickedVp] && m_viewportNameLabels[clickedVp]->isVisible()) {
2175 if (m_viewportNameLabels[clickedVp]->geometry().contains(e->pos())) {
2176 if (clickedVp != m_visualizationEditTarget) {
2179 showViewportPresetMenu(clickedVp, mapToGlobal(e->pos()));
2180 m_lastMousePos = e->pos();
2185 const int numEnabled = enabledViewportCount();
2186 const SplitterHit hit = hitTestSplitter(e->pos(), numEnabled, size());
2188 m_isDraggingSplitter =
true;
2189 m_activeSplitter = hit;
2190 m_lastMousePos = e->pos();
2191 updateSplitterCursor(e->pos());
2196 const int clickedVpForSelection = viewportIndexAt(e->pos());
2197 if (clickedVpForSelection >= 0 && clickedVpForSelection != m_visualizationEditTarget) {
2202 m_lastMousePos = e->pos();
2209 if (m_isDraggingSplitter && (event->buttons() & Qt::LeftButton)) {
2210 m_layout.dragSplitter(event->pos(), m_activeSplitter, size());
2211 m_multiSplitX = m_layout.splitX();
2212 m_multiSplitY = m_layout.splitY();
2214 m_lastMousePos =
event->pos();
2215 updateViewportSeparators();
2216 m_sceneDirty =
true;
2221 if (event->buttons() & Qt::LeftButton) {
2223 const int activeVp = viewportIndexAt(event->pos());
2224 const int activePreset = (activeVp >= 0 && activeVp < m_subViews.size())
2225 ? std::clamp(m_subViews[activeVp].preset, 0, 6)
2230 const QPoint diff =
event->pos() - m_lastMousePos;
2232 m_draggedSincePress =
true;
2233 m_lastMousePos =
event->pos();
2234 m_sceneDirty =
true;
2241 QPoint diff =
event->pos() - m_lastMousePos;
2244 m_perspectiveRotatedSincePress =
true;
2245 m_draggedSincePress =
true;
2246 m_lastMousePos =
event->pos();
2247 m_sceneDirty =
true;
2252 m_lastMousePos =
event->pos();
2257 QPoint diff =
event->pos() - m_lastMousePos;
2259 m_draggedSincePress =
true;
2261 m_lastMousePos =
event->pos();
2262 m_sceneDirty =
true;
2266 updateSplitterCursor(event->pos());
2278 if (event->button() == Qt::LeftButton && m_isDraggingSplitter) {
2279 m_isDraggingSplitter =
false;
2281 saveMultiViewSettings();
2282 updateSplitterCursor(event->pos());
2286 if (event->button() == Qt::LeftButton && m_viewMode ==
MultiView && m_perspectiveRotatedSincePress) {
2287 m_perspectiveRotatedSincePress =
false;
2288 saveMultiViewSettings();
2292 if (event->button() == Qt::LeftButton && m_viewMode ==
MultiView && !m_perspectiveRotatedSincePress) {
2293 saveMultiViewSettings();
2297 if (event->button() == Qt::LeftButton && !m_draggedSincePress) {
2299 if (m_hasIntersection) {
2305 updateSplitterCursor(event->pos());
2315 if (event->button() == Qt::LeftButton) {
2317 if (m_hasIntersection) {
2322 QRhiWidget::mouseDoubleClickEvent(event);
2329 const float delta =
event->angleDelta().y() / 120.0f;
2332 const int vp = viewportIndexAt(event->position().toPoint());
2333 if (vp >= 0 && vp < m_subViews.size()) {
2334 m_subViews[vp].zoom += delta;
2335 saveMultiViewSettings();
2338 m_singleView.zoom += delta;
2339 saveMultiViewSettings();
2341 m_sceneDirty =
true;
2349 if (event->key() == Qt::Key_S) {
2351 }
else if (event->key() == Qt::Key_R) {
2352 m_cameraRotation = QQuaternion();
2353 logPerspectiveRotation(
"reset-initial");
2354 saveMultiViewSettings();
2355 m_sceneDirty =
true;
2364 return m_sourceManager.load(lhPath, rhPath, m_surfaces, m_activeSurfaceType);
2369void BrainView::onSourceEstimateLoaded(
int numTimePoints)
2380 m_sourceManager.setTimePoint(index, m_surfaces, m_singleView, m_subViews);
2381 m_sceneDirty =
true;
2389 m_sourceManager.setColormap(name);
2397 m_sourceManager.setThresholds(min, mid, max);
2406 m_sourceManager.startStreaming(m_surfaces, m_singleView, m_subViews);
2413 m_sourceManager.stopStreaming();
2420 return m_sourceManager.isStreaming();
2427 m_sourceManager.pushData(matData);
2434 m_sourceManager.setInterval(msec);
2441 m_sourceManager.setLooping(enabled);
2446void BrainView::onRealtimeColorsAvailable(
const QVector<uint32_t>& colorsLh,
2447 const QVector<uint32_t>& colorsRh)
2450 QSet<QString> activeTypes;
2452 for (
int i = 0; i < m_subViews.size(); ++i) {
2453 activeTypes.insert(m_subViews[i].surfaceType);
2456 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
2460 for (
const QString& type : activeTypes) {
2461 if (it.key().endsWith(type)) {
2462 int hemi = it.value()->hemi();
2463 const QVector<uint32_t>& colors = (hemi == 0) ? colorsLh : colorsRh;
2464 if (!colors.isEmpty()) {
2465 it.value()->applySourceEstimateColors(colors);
2472 m_sceneDirty =
true;
2481 if (evoked.isEmpty())
2486 const int previousTimePoint = m_fieldMapper.timePoint();
2487 const bool canReuse = m_fieldMapper.hasMappingFor(evoked);
2489 m_fieldMapper.setEvoked(evoked);
2493 if (!m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans)) {
2499 m_fieldMapper.computeNormRange();
2503 const int numTimes =
static_cast<int>(m_fieldMapper.evoked().times.size());
2504 const int tp = qBound(0, previousTimePoint, numTimes - 1);
2522 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().isEmpty()) {
2526 int maxIdx =
static_cast<int>(m_fieldMapper.evoked().times.size()) - 1;
2531 m_fieldMapper.setTimePoint(qBound(0, index, maxIdx));
2532 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2534 m_sceneDirty =
true;
2542 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2543 if (type ==
"MEG") {
2544 profile.megFieldMap = visible;
2545 }
else if (type ==
"EEG") {
2546 profile.eegFieldMap = visible;
2551 saveMultiViewSettings();
2552 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2553 m_sceneDirty =
true;
2561 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2562 if (type ==
"MEG") {
2563 profile.megFieldContours = visible;
2564 }
else if (type ==
"EEG") {
2565 profile.eegFieldContours = visible;
2570 saveMultiViewSettings();
2571 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2572 m_sceneDirty =
true;
2580 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2581 if (profile.megFieldMapOnHead == useHead && m_fieldMapper.megFieldMapOnHead() == useHead) {
2585 profile.megFieldMapOnHead = useHead;
2586 m_fieldMapper.setMegFieldMapOnHead(useHead);
2587 saveMultiViewSettings();
2588 if (m_fieldMapper.isLoaded()) {
2589 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
2590 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2591 m_sceneDirty =
true;
2600 if (m_fieldMapper.colormap() == name) {
2603 m_fieldMapper.setColormap(name);
2604 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2605 m_sceneDirty =
true;
2613 return m_sourceManager.tstep();
2620 return m_sourceManager.tmin();
2627 return m_sourceManager.numTimePoints();
2634 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().nave == -1 || m_fieldMapper.evoked().times.size() == 0) {
2639 float bestDist = std::abs(m_fieldMapper.evoked().times(0) - timeSec);
2640 for (
int i = 1; i < m_fieldMapper.evoked().times.size(); ++i) {
2641 float dist = std::abs(m_fieldMapper.evoked().times(i) - timeSec);
2642 if (dist < bestDist) {
2654 return m_sourceManager.closestIndex(timeSec);
2661 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().nave == -1 || m_fieldMapper.evoked().times.size() == 0) {
2664 tmin = m_fieldMapper.evoked().times(0);
2665 tmax = m_fieldMapper.evoked().times(m_fieldMapper.evoked().times.size() - 1);
2675 m_sensorStreamManager.startStreaming(modality, m_fieldMapper, m_surfaces);
2682 m_sensorStreamManager.stopStreaming();
2689 return m_sensorStreamManager.isStreaming();
2696 m_sensorStreamManager.pushData(vecData);
2703 m_sensorStreamManager.setInterval(msec);
2710 m_sensorStreamManager.setLooping(enabled);
2717 m_sensorStreamManager.setAverages(numAvr);
2724 m_sensorStreamManager.setColormap(name);
2729void BrainView::onSensorStreamColorsAvailable(
const QString& surfaceKey,
2730 const QVector<uint32_t>& colors)
2732 if (surfaceKey.isEmpty() || !m_surfaces.contains(surfaceKey)) {
2736 auto surface = m_surfaces[surfaceKey];
2737 if (surface && !colors.isEmpty()) {
2738 surface->applySourceEstimateColors(colors);
2741 m_sceneDirty =
true;
2753 if (!r.hasInfo && !r.hasDigitizer)
2757 m_devHeadTrans = r.devHeadTrans;
2758 m_hasDevHead = r.hasDevHead;
2760 if (!r.megGradItems.isEmpty())
2761 m_model->addSensors(
"MEG/Grad", r.megGradItems);
2762 if (!r.megMagItems.isEmpty())
2763 m_model->addSensors(
"MEG/Mag", r.megMagItems);
2764 if (!r.eegItems.isEmpty())
2765 m_model->addSensors(
"EEG", r.eegItems);
2767 if (r.helmetSurface) {
2768 m_surfaces[
"sens_surface_meg"] = r.helmetSurface;
2770 qWarning() <<
"BrainView::loadSensors: NO helmet surface returned from DataLoader!";
2773 if (!r.digitizerPoints.isEmpty())
2774 m_model->addDigitizerData(r.digitizerPoints);
2777 m_cardinalDigPoints.clear();
2778 for (
const auto& dp : r.digitizerPoints) {
2780 m_cardinalDigPoints.append(dp);
2790 QMap<int, QVector3D> result;
2791 if (m_cardinalDigPoints.isEmpty())
2795 const bool haveTrans = (m_headToMriTrans.from != 0 || m_headToMriTrans.to != 0);
2796 QMatrix4x4 headToMri;
2797 headToMri.setToIdentity();
2801 for (
const auto& dp : m_cardinalDigPoints) {
2802 const QVector3D posHead(dp.r[0], dp.r[1], dp.r[2]);
2803 result[dp.ident] = haveTrans ? headToMri.map(posHead) : posHead;
2812 auto it = m_surfaces.find(QStringLiteral(
"bem_head"));
2813 if (it == m_surfaces.end() || !it.value())
2817 QVector3D bmin, bmax;
2821 pos = QVector3D((bmin.x() + bmax.x()) * 0.5f,
2822 (bmin.y() + bmax.y()) * 0.5f,
2833 qWarning() <<
"BrainView::loadMegHelmetSurface: DataLoader returned nullptr!";
2837 m_surfaces[
"sens_surface_meg"] = surface;
2838 refreshSensorTransforms();
2839 updateSceneBounds();
2840 m_sceneDirty =
true;
2853 if (ecdSet.size() == 0)
2855 m_model->addDipoles(ecdSet);
2866 m_network = std::make_unique<NetworkObject>();
2867 m_network->load(network);
2868 m_network->setVisible(
true);
2872 m_model->addNetwork(network, name);
2874 m_sceneDirty =
true;
2883 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2884 profile.network = visible;
2885 m_networkVisible = visible;
2887 m_network->setVisible(visible);
2888 saveMultiViewSettings();
2889 m_sceneDirty =
true;
2898 m_network->setThreshold(threshold);
2899 m_sceneDirty =
true;
2909 m_network->setColormap(name);
2910 m_sceneDirty =
true;
2923 if (srcSpace.isEmpty())
2925 m_model->addSourceSpace(srcSpace);
2933 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2934 profile.sourceSpace = visible;
2935 saveMultiViewSettings();
2936 m_sceneDirty =
true;
2948 m_headToMriTrans = trans;
2949 refreshSensorTransforms();
2955QMatrix4x4 BrainView::itemTransform(
const QStandardItem* item)
const
2960 QMatrix4x4 trans = absItem->
transform();
2962 const int type = absItem->type();
2964 if (inHead && m_applySensorTrans && !m_headToMriTrans.
isEmpty())
2971void BrainView::refreshSensorTransforms()
2974 if (m_applySensorTrans && !m_headToMriTrans.isEmpty()) {
2978 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
2979 if ((it.key().startsWith(
"sens_") || it.key().startsWith(
"dig_")) && it.value()) {
2980 const QStandardItem* item = m_itemSurfaceMap.key(it.value());
2981 it.value()->applyTransform(item ? itemTransform(item) : qmat);
2984 for (
auto it = m_itemDipoleMap.cbegin(); it != m_itemDipoleMap.cend(); ++it) {
2985 it.value()->applyTransform(itemTransform(it.key()));
2988 if (m_fieldMapper.isLoaded()) {
2989 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
2990 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2999 const QSize outputSize = size();
3001 const auto enabledViewports = enabledViewportIndices();
3003 const int numEnabled = enabledViewports.size();
3005 QRect activePane(0, 0, outputSize.width(), outputSize.height());
3007 bool hasValidPane =
true;
3008 if (m_viewMode ==
MultiView && numEnabled > 1) {
3009 bool foundSlot =
false;
3010 for (
int slot = 0; slot < numEnabled; ++slot) {
3011 const QRect pane = multiViewSlotRect(slot, numEnabled, outputSize);
3012 if (pane.contains(pos)) {
3020 hasValidPane = foundSlot;
3023 const int vp = (m_viewMode ==
MultiView) ? enabledViewports[activeSlot] : 0;
3024 const SubView& sv = (m_viewMode ==
MultiView) ? m_subViews[vp] : m_singleView;
3026 const QVector3D effectiveCenter2 = m_cameraFocusOverride ? m_cameraFocusCenter : m_sceneCenter;
3027 const float effectiveSize2 = m_cameraFocusOverride ? m_cameraFocusSize : m_sceneSize;
3028 m_camera.setSceneCenter(effectiveCenter2);
3029 m_camera.setSceneSize(effectiveSize2);
3030 m_camera.setRotation(m_cameraRotation);
3031 m_camera.setZoom(m_singleView.zoom);
3032 const float aspect = float(std::max(1, activePane.width())) / float(std::max(1, activePane.height()));
3034 ? m_camera.computeMultiView(sv, aspect)
3035 : m_camera.computeSingleView(aspect);
3039 QVector3D rayOrigin, rayDir;
3046 pickResult =
RayPicker::pick(rayOrigin, rayDir, sv, m_surfaces, m_itemSurfaceMap, m_itemDipoleMap);
3048 m_hasIntersection = pickResult.
hit;
3049 if (pickResult.
hit) {
3050 m_lastIntersectionPoint = pickResult.
hitPoint;
3053 QStandardItem* hitItem = pickResult.
item;
3058 const QString& hitKey = pickResult.
surfaceKey;
3059 int currentRegionId = pickResult.
regionId;
3061 if (displayLabel != m_hoveredRegion) {
3062 m_hoveredRegion = displayLabel;
3064 if (m_regionLabel) {
3065 if (m_hoveredRegion.isEmpty()) {
3066 m_regionLabel->hide();
3068 m_regionLabel->setText(m_hoveredRegion);
3069 m_regionLabel->show();
3074 QString hoveredSurfaceKey;
3075 if (hitKey.startsWith(
"sens_surface_meg")) {
3076 hoveredSurfaceKey = hitKey;
3079 if (hitItem != m_hoveredItem || hitIndex != m_hoveredIndex || hoveredSurfaceKey != m_hoveredSurfaceKey) {
3081 if (m_hoveredItem) {
3082 if (m_itemSurfaceMap.contains(m_hoveredItem)) {
3083 m_itemSurfaceMap[m_hoveredItem]->setSelected(
false);
3084 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
3085 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(-1, 0);
3086 }
else if (m_itemDipoleMap.contains(m_hoveredItem)) {
3087 m_itemDipoleMap[m_hoveredItem]->setSelected(m_hoveredIndex,
false);
3090 if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
3091 m_surfaces[m_hoveredSurfaceKey]->setSelected(
false);
3092 m_surfaces[m_hoveredSurfaceKey]->setSelectedRegion(-1);
3093 m_surfaces[m_hoveredSurfaceKey]->setSelectedVertexRange(-1, 0);
3096 m_hoveredItem = hitItem;
3097 m_hoveredIndex = hitIndex;
3098 m_hoveredSurfaceKey = hoveredSurfaceKey;
3100 if (m_hoveredItem) {
3102 if (m_itemSurfaceMap.contains(m_hoveredItem)) {
3105 bool isDigitizer = absHitSel &&
3108 if (isDigitizer && m_hoveredIndex >= 0) {
3110 int sphereIdx = m_hoveredIndex / vertsPerSphere;
3111 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(
3112 sphereIdx * vertsPerSphere, vertsPerSphere);
3113 }
else if (currentRegionId != -1) {
3114 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(currentRegionId);
3118 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3120 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3121 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
3123 }
else if (m_itemDipoleMap.contains(m_hoveredItem)) {
3124 m_itemDipoleMap[m_hoveredItem]->setSelected(m_hoveredIndex,
true);
3126 }
else if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
3127 m_surfaces[m_hoveredSurfaceKey]->setSelected(
true);
3128 m_surfaces[m_hoveredSurfaceKey]->setSelectedRegion(-1);
3130 }
else if (m_hoveredItem && m_itemSurfaceMap.contains(m_hoveredItem)) {
3132 bool isDigitizer = absHitUpd &&
3135 if (isDigitizer && m_hoveredIndex >= 0) {
3137 int sphereIdx = m_hoveredIndex / vertsPerSphere;
3138 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(
3139 sphereIdx * vertsPerSphere, vertsPerSphere);
3140 }
else if (currentRegionId != -1) {
3141 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(currentRegionId);
3142 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3144 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
3145 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3147 }
else if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
3148 m_surfaces[m_hoveredSurfaceKey]->setSelected(
true);
3150 m_sceneDirty =
true;
3156void BrainView::showViewportPresetMenu(
int viewport,
const QPoint& globalPos)
3158 if (viewport < 0 || viewport >= m_subViews.size()) {
3163 QAction* topAction = menu.addAction(
"Top");
3164 QAction* perspectiveAction = menu.addAction(
"Perspective");
3165 QAction* frontAction = menu.addAction(
"Front");
3166 QAction* leftAction = menu.addAction(
"Left");
3167 menu.addSeparator();
3168 QAction* bottomAction = menu.addAction(
"Bottom");
3169 QAction* backAction = menu.addAction(
"Back");
3170 QAction* rightAction = menu.addAction(
"Right");
3172 const int currentPreset = std::clamp(m_subViews[viewport].preset, 0, 6);
3173 topAction->setCheckable(
true);
3174 perspectiveAction->setCheckable(
true);
3175 frontAction->setCheckable(
true);
3176 leftAction->setCheckable(
true);
3177 bottomAction->setCheckable(
true);
3178 backAction->setCheckable(
true);
3179 rightAction->setCheckable(
true);
3181 topAction->setChecked(currentPreset == 0);
3182 perspectiveAction->setChecked(currentPreset == 1);
3183 frontAction->setChecked(currentPreset == 2);
3184 leftAction->setChecked(currentPreset == 3);
3185 bottomAction->setChecked(currentPreset == 4);
3186 backAction->setChecked(currentPreset == 5);
3187 rightAction->setChecked(currentPreset == 6);
3189 QAction* selected = menu.exec(globalPos);
3194 int newPreset = currentPreset;
3195 if (selected == topAction) {
3197 }
else if (selected == perspectiveAction) {
3199 }
else if (selected == frontAction) {
3201 }
else if (selected == leftAction) {
3203 }
else if (selected == bottomAction) {
3205 }
else if (selected == backAction) {
3207 }
else if (selected == rightAction) {
3211 if (newPreset == currentPreset) {
3215 m_subViews[viewport].preset = newPreset;
3216 saveMultiViewSettings();
3217 updateOverlayLayout();
3218 m_sceneDirty =
true;
3226void BrainView::removeSurfacesByPrefix(
const QStringList& prefixes)
3229 for (
auto it = m_itemSurfaceMap.begin(); it != m_itemSurfaceMap.end();) {
3230 const QString key = m_surfaces.key(it.value());
3231 const bool remove = std::any_of(prefixes.cbegin(), prefixes.cend(), [&key](
const QString& prefix) { return key.startsWith(prefix); });
3237 QStandardItem* item =
const_cast<QStandardItem*
>(it.key());
3239 item->parent()->removeRow(item->row());
3241 m_model->removeRow(item->row());
3243 it = m_itemSurfaceMap.erase(it);
3245 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3246 const QString& key = it.key();
3247 if (std::any_of(prefixes.cbegin(), prefixes.cend(), [&key](
const QString& prefix) { return key.startsWith(prefix); }))
3248 it = m_surfaces.erase(it);
3252 updateSceneBounds();
3253 m_sceneDirty =
true;
3261 m_activeSurface.reset();
3262 m_activeSurfaceType.clear();
3263 removeSurfacesByPrefix({QStringLiteral(
"lh_"), QStringLiteral(
"rh_")});
3270 removeSurfacesByPrefix({QStringLiteral(
"bem_")});
3277 m_sourceManager.stopStreaming();
3278 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
3279 if (it.key().startsWith(
"lh_") || it.key().startsWith(
"rh_")) {
3280 it.value()->clearSourceEstimateColors();
3283 m_sceneDirty =
true;
3294 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end();) {
3296 QStandardItem* mutableItem =
const_cast<QStandardItem*
>(it.key());
3297 if (mutableItem->parent())
3298 mutableItem->parent()->removeRow(mutableItem->row());
3300 m_model->removeRow(mutableItem->row());
3302 it = m_itemDipoleMap.erase(it);
3304 m_sceneDirty =
true;
3312 removeSurfacesByPrefix({QStringLiteral(
"srcsp_")});
3320 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3321 if (it.key().startsWith(QLatin1String(
"dig_live_")))
3322 it = m_surfaces.erase(it);
3327 for (
int i = 0; i < markers.size(); ++i) {
3329 {markers[i].position}, markers[i].radius, markers[i].color);
3330 surf->setVisible(
true);
3331 const QString prefix = markers[i].transparent
3332 ? QStringLiteral(
"dig_live_t_%1")
3333 : QStringLiteral(
"dig_live_%1");
3334 m_surfaces[prefix.arg(i)] = surf;
3338 m_sceneDirty =
true;
3344 bool removed =
false;
3345 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3346 if (it.key().startsWith(QLatin1String(
"dig_live_"))) {
3347 it = m_surfaces.erase(it);
3354 m_sceneDirty =
true;
3360 const QColor& color,
float radius)
3363 m_surfaces.remove(QLatin1String(
"dig_ray_0"));
3366 surf->setVisible(
true);
3367 m_surfaces[QStringLiteral(
"dig_ray_0")] = surf;
3369 m_sceneDirty =
true;
3375 if (m_surfaces.remove(QLatin1String(
"dig_ray_0"))) {
3376 m_sceneDirty =
true;
3382 float length,
const QColor& color,
3383 const QColor& glowColor,
3384 const QQuaternion& orientation)
3387 m_surfaces.remove(QLatin1String(
"dig_probe_shaft"));
3388 m_surfaces.remove(QLatin1String(
"dig_probe_tip"));
3389 m_surfaces.remove(QLatin1String(
"dig_probe_tipglow"));
3390 m_surfaces.remove(QLatin1String(
"dig_probe_axis_x"));
3391 m_surfaces.remove(QLatin1String(
"dig_probe_axis_y"));
3392 m_surfaces.remove(QLatin1String(
"dig_probe_axis_z"));
3393 m_surfaces.remove(QLatin1String(
"dig_probe_axis_x_tip"));
3394 m_surfaces.remove(QLatin1String(
"dig_probe_axis_y_tip"));
3395 m_surfaces.remove(QLatin1String(
"dig_probe_axis_z_tip"));
3396 m_surfaces.remove(QLatin1String(
"dig_probe_axis_xn_tip"));
3397 m_surfaces.remove(QLatin1String(
"dig_probe_axis_yn_tip"));
3398 m_surfaces.remove(QLatin1String(
"dig_probe_axis_zn_tip"));
3399 m_surfaces.remove(QLatin1String(
"dig_probe_cross_x"));
3400 m_surfaces.remove(QLatin1String(
"dig_probe_cross_y"));
3401 m_surfaces.remove(QLatin1String(
"dig_probe_cross_z"));
3404 if (length > 0.0f && !direction.isNull()) {
3405 const QVector3D shaftEnd = tip - direction * length;
3406 constexpr float kShaftRadius = 0.0015f;
3408 shaft->setVisible(
true);
3409 m_surfaces[QStringLiteral(
"dig_probe_shaft")] = shaft;
3413 constexpr float kTipRadius = 0.0010f;
3415 tipSurf->setVisible(
true);
3416 m_surfaces[QStringLiteral(
"dig_probe_tip")] = tipSurf;
3419 if (glowColor.alpha() > 0) {
3420 constexpr float kGlowTipRadius = 0.003f;
3422 tipGlow->setVisible(
true);
3423 m_surfaces[QStringLiteral(
"dig_probe_tipglow")] = tipGlow;
3427 if (!orientation.isNull()) {
3428 constexpr float kCrossLen = 0.008f;
3429 constexpr float kCrossRadius = 0.0002f;
3431 const QVector3D xDir = orientation.rotatedVector(QVector3D(1, 0, 0)).normalized();
3432 const QVector3D yDir = orientation.rotatedVector(QVector3D(0, 1, 0)).normalized();
3433 const QVector3D zDir = orientation.rotatedVector(QVector3D(0, 0, 1)).normalized();
3441 const CrossDef cross[] = {
3442 {xDir, QColor(255, 50, 50, 160), QStringLiteral(
"dig_probe_cross_x")},
3443 {yDir, QColor(50, 220, 50, 160), QStringLiteral(
"dig_probe_cross_y")},
3444 {zDir, QColor(80, 140, 255, 160), QStringLiteral(
"dig_probe_cross_z")},
3446 for (
const auto& c : cross) {
3447 const QVector3D from = tip - c.dir * kCrossLen;
3448 const QVector3D to = tip + c.dir * kCrossLen;
3450 cyl->setVisible(
true);
3451 m_surfaces[c.key] = cyl;
3456 if (!orientation.isNull()) {
3457 constexpr float kAxisLen = 0.012f;
3458 constexpr float kAxisRadius = 0.0004f;
3459 constexpr float kPosTipR = 0.0012f;
3460 constexpr float kNegTipR = 0.0006f;
3462 const QVector3D xDir = orientation.rotatedVector(QVector3D(1, 0, 0)).normalized();
3463 const QVector3D yDir = orientation.rotatedVector(QVector3D(0, 1, 0)).normalized();
3464 const QVector3D zDir = orientation.rotatedVector(QVector3D(0, 0, 1)).normalized();
3470 QString cylKey, posTipKey, negTipKey;
3472 const AxisDef axes[] = {
3473 {xDir, QColor(255, 50, 50), QStringLiteral(
"dig_probe_axis_x"),
3474 QStringLiteral(
"dig_probe_axis_x_tip"), QStringLiteral(
"dig_probe_axis_xn_tip")},
3475 {yDir, QColor(50, 220, 50), QStringLiteral(
"dig_probe_axis_y"),
3476 QStringLiteral(
"dig_probe_axis_y_tip"), QStringLiteral(
"dig_probe_axis_yn_tip")},
3477 {zDir, QColor(80, 140, 255), QStringLiteral(
"dig_probe_axis_z"),
3478 QStringLiteral(
"dig_probe_axis_z_tip"), QStringLiteral(
"dig_probe_axis_zn_tip")},
3481 for (
const auto& a : axes) {
3482 const QVector3D posEnd = tip + a.dir * kAxisLen;
3483 const QVector3D negEnd = tip - a.dir * kAxisLen;
3487 cyl->setVisible(
true);
3488 m_surfaces[a.cylKey] = cyl;
3492 posTip->setVisible(
true);
3493 m_surfaces[a.posTipKey] = posTip;
3496 QColor dimColor = a.color;
3497 dimColor.setAlpha(120);
3499 negTip->setVisible(
true);
3500 m_surfaces[a.negTipKey] = negTip;
3504 m_sceneDirty =
true;
3512 qsizetype nRemoved = 0;
3513 for (
const char* key : {
"dig_probe_shaft",
3515 "dig_probe_tipglow",
3519 "dig_probe_axis_x_tip",
3520 "dig_probe_axis_y_tip",
3521 "dig_probe_axis_z_tip",
3522 "dig_probe_axis_xn_tip",
3523 "dig_probe_axis_yn_tip",
3524 "dig_probe_axis_zn_tip",
3525 "dig_probe_cross_x",
3526 "dig_probe_cross_y",
3527 "dig_probe_cross_z"}) {
3528 nRemoved += m_surfaces.remove(QLatin1String(key));
3531 const bool removed = nRemoved > 0;
3533 m_sceneDirty =
true;
3540 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3541 if (it.key().startsWith(QLatin1String(
"dig_static_")))
3542 it = m_surfaces.erase(it);
3547 for (
int i = 0; i < markers.size(); ++i) {
3549 {markers[i].position}, markers[i].radius, markers[i].color);
3550 surf->setVisible(
true);
3551 m_surfaces[QStringLiteral(
"dig_static_%1").arg(i)] = surf;
3554 m_sceneDirty =
true;
3560 bool removed =
false;
3561 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3562 if (it.key().startsWith(QLatin1String(
"dig_static_"))) {
3563 it = m_surfaces.erase(it);
3570 m_sceneDirty =
true;
3577 m_cameraFocusOverride =
true;
3578 m_cameraFocusCenter = center;
3579 m_cameraFocusSize = size;
3580 m_sceneDirty =
true;
3586 if (!m_cameraFocusOverride)
3588 m_cameraFocusOverride =
false;
3589 updateSceneBounds();
3590 m_sceneDirty =
true;
3596 m_devHeadTrans = QMatrix4x4();
3597 m_hasDevHead =
false;
3598 removeSurfacesByPrefix({QStringLiteral(
"sens_"), QStringLiteral(
"dig_")});
3606 m_sensorStreamManager.stopStreaming();
3607 m_sceneDirty =
true;
3616 refreshSensorTransforms();
3617 m_sceneDirty =
true;
3626 m_networkVisible =
false;
3630 for (
int r = m_model->rowCount() - 1; r >= 0; --r) {
3631 QStandardItem* item = m_model->item(r);
3632 if (item && item->text() ==
"Networks") {
3633 m_model->removeRow(r);
3638 m_sceneDirty =
true;
3646 m_sourceManager.cancelLoading();
3647 m_sourceManager.stopStreaming();
3648 m_sensorStreamManager.stopStreaming();
3657 if (!m_videoOverlay)
3659 if (m_videoOverlay->isEnabled() == enabled)
3661 m_videoOverlay->setEnabled(enabled);
3662 m_sceneDirty =
true;
3668 return m_videoOverlay && m_videoOverlay->isEnabled();
3673 if (!m_videoOverlay)
3675 m_videoOverlay->setFocusPosition(position);
3676 if (m_videoOverlay->isEnabled()) {
3677 m_sceneDirty =
true;
3684 if (!m_videoOverlay)
3686 m_videoOverlay->setUpHint(dir);
3687 if (m_videoOverlay->isEnabled()) {
3688 m_sceneDirty =
true;
3695 if (!m_videoOverlay)
3697 m_videoOverlay->setSizeMeters(std::max(0.001f, meters));
3698 if (m_videoOverlay->isEnabled()) {
3699 m_sceneDirty =
true;
3706 if (!m_videoOverlay)
3708 m_videoOverlay->setOpacity(std::clamp(opacity, 0.0f, 1.0f));
3709 if (m_videoOverlay->isEnabled()) {
3710 m_sceneDirty =
true;
3717 if (!m_videoOverlay)
3719 m_videoOverlay->setFrame(frame);
3720 if (m_videoOverlay->isEnabled()) {
3721 m_sceneDirty =
true;
3728 if (!m_videoOverlay)
3730 m_videoOverlay->setDepthEnabled(enabled);
3731 if (m_videoOverlay->isEnabled()) {
3732 m_sceneDirty =
true;
3739 if (!m_videoOverlay)
3741 m_videoOverlay->setDepthScale(std::clamp(scale, 0.0f, 1.0f));
3742 if (m_videoOverlay->isEnabled()) {
3743 m_sceneDirty =
true;
3750 if (!m_videoOverlay)
3752 m_videoOverlay->setDepthSteps(std::clamp(steps, 8, 64));
3753 if (m_videoOverlay->isEnabled()) {
3754 m_sceneDirty =
true;
3761 if (!m_videoOverlay)
3763 m_videoOverlay->setDepthFrame(depthFrame);
3764 if (m_videoOverlay->isEnabled() && m_videoOverlay->isDepthEnabled()) {
3765 m_sceneDirty =
true;
3775 float closestDist = std::numeric_limits<float>::max();
3780 auto headIt = m_surfaces.constFind(QStringLiteral(
"bem_head"));
3781 if (headIt != m_surfaces.cend() && headIt.value()) {
3784 if (headIt.value()->intersects(origin, direction, dist, vertexIdx) && dist < closestDist) {
3786 hitPoint = origin + dist * direction;
3793 for (
auto it = m_surfaces.cbegin(); it != m_surfaces.cend(); ++it) {
3794 const auto& surf = it.value();
3795 if (!surf || !surf->isVisible())
3799 if (surf->intersects(origin, direction, dist, vertexIdx) && dist < closestDist) {
3801 hitPoint = origin + dist * direction;
3815 if (slotIndex < 0 || slotIndex >= kMaxSliceSlots)
3817 m_slices[slotIndex] = slice;
3818 m_sceneDirty =
true;
3824 if (slotIndex < 0 || slotIndex >= kMaxSliceSlots)
3826 m_sliceVisible[slotIndex] = visible;
3827 m_sceneDirty =
true;
3835 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
3836 profile.mriSlices = visible;
3837 saveMultiViewSettings();
3838 m_sceneDirty =
true;
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
#define FIFFV_POINT_CARDINAL
Set of averaged evoked responses sharing a FiffInfo, plus the ave-style category / rejection descript...
QStandardItemModel hierarchy that organises every 3-D scene object (surfaces, sensors,...
Tree item holding a single category of digitizer points rendered as a batched-sphere mesh.
Tree item wrapping a fitted INVLIB::InvEcdSet of equivalent current dipoles.
Tree item wrapping a single MNELIB::MNEBemSurface (brain / inner-skull / outer-skull / scalp shell).
Tree item for a single MEG / EEG sensor with position, optional coil orientation and rendered scale.
Tree item wrapping a FreeSurfer FSLIB::FsSurface plus optional FSLIB::FsAnnotation parcellation.
Tree item holding one hemisphere of source-space dipole positions rendered as batched spheres.
Mouse-ray vs scene-object intersection for picking dipoles, electrodes and surfaces.
Static factory for procedural Qt-RHI meshes (spheres, plates, barbells, cylinders,...
Top-level QRhiWidget that hosts the 3-D scene, drives camera interaction and exposes the multi-view +...
Qt-RHI scene renderer: shader pipelines, lighting, dual render targets and per-frame draw orchestrati...
Instanced connectivity-graph renderable: node spheres and edge cylinders coloured by weight through a...
PolylineObject class declaration.
Instanced-arrow renderable for fitted equivalent current dipoles, driven by QRhi instancing.
Renderable cortical / BEM mesh with interleaved vertex attributes and Qt-RHI buffer management.
Single MRI volume slice rendered as a textured quad with adjustable axis, position,...
Generic live-RGB video texture overlay rendered as a screen-aligned quad with chroma keying.
String key constants for surfaces in the DISP3DLIB scene map.
Static helpers for loading FreeSurfer / MNE source-space and BEM data into the disp3D model tree.
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...
Graph container that stores the result of one functional-connectivity metric as nodes (sources/sensor...
Boundary element model bundle (inner skull, outer skull, outer skin) loaded from -bem....
Container pairing the left and right cortical source spaces of a subject.
FIFF file I/O, in-memory data structures and high-level readers/writers.
3-D brain visualisation using the Qt RHI rendering backend.
ShaderMode shaderModeFromName(const QString &name)
QString shaderModeName(ShaderMode mode)
QString visualizationModeName(VisualizationMode mode)
bool multiViewPresetIsPerspective(int preset)
QString multiViewPresetName(int preset)
int normalizedVisualizationTarget(int target, int maxIndex)
VisualizationMode visualizationModeFromName(const QString &name)
QString sensorParentToKeyPrefix(const QString &parentText)
QString sensorTypeToObjectKey(const QString &uiType)
QMatrix4x4 toQMatrix4x4(const Eigen::Matrix4f &m)
Graph container for one connectivity metric; nodes + weighted edges + threshold/visualisation state.
const QList< QSharedPointer< NetworkNode > > & getNodes() const
static std::shared_ptr< BrainSurface > loadHelmetSurface(const QString &helmetFilePath, const QMatrix4x4 &devHeadTrans=QMatrix4x4(), bool applyTrans=false)
static FIFFLIB::FiffEvoked loadEvoked(const QString &evokedPath, int aveIndex=0)
static MNELIB::MNESourceSpaces loadSourceSpace(const QString &fwdPath)
static INVLIB::InvEcdSet loadDipoles(const QString &dipPath)
static QStringList probeEvokedSets(const QString &evokedPath)
static SensorLoadResult loadSensors(const QString &fifPath, const QString &megHelmetOverridePath={})
static bool loadHeadToMriTransform(const QString &transPath, FIFFLIB::FiffCoordTrans &trans)
Per-view toggle flags controlling which data layers (brain, sensors, sources, network) are visible.
Viewport subdivision holding its own camera, projection, and scissor rectangle.
static SubView defaultForIndex(int index)
bool matchesSurfaceType(const QString &key) const
bool shouldRenderSurface(const QString &key) const
ViewVisibilityProfile visibility
VisualizationMode overlayMode
static bool isBrainSurfaceKey(const QString &key)
static std::shared_ptr< BrainSurface > createBarbell(const QVector3D ¢er, const QMatrix4x4 &orientation, const QColor &color, float size)
static std::shared_ptr< BrainSurface > createCylinder(const QVector3D &from, const QVector3D &to, float radius, const QColor &color, int sides=12)
static std::shared_ptr< BrainSurface > createBatchedSpheres(const QVector< QVector3D > &positions, float radius, const QColor &color, int subdivisions=1)
static std::shared_ptr< BrainSurface > createSphere(const QVector3D ¢er, float radius, const QColor &color, int subdivisions=1)
static int sphereVertexCount(int subdivisions=1)
static std::shared_ptr< BrainSurface > createPlate(const QVector3D ¢er, const QMatrix4x4 &orientation, const QColor &color, float size)
Computed camera matrices (projection, view, model) and vectors for a single viewport.
static void applyMousePan(const QPoint &delta, QVector2D &pan, float sceneSize)
static void applyMouseRotation(const QPoint &delta, QQuaternion &rotation, float speed=0.5f)
Result of a ray–mesh intersection test containing the hit point, triangle index, and distance.
QString surfaceKey
FsSurface map key of the hit surface.
int regionId
FsAnnotation label ID.
QStandardItem * item
Tree item that was hit (nullable).
bool hit
True if something was hit.
QVector3D hitPoint
World-space intersection point.
int vertexIndex
Vertex or element index at hit.
static bool unproject(const QPoint &screenPos, const QRect &paneRect, const QMatrix4x4 &pvm, QVector3D &rayOrigin, QVector3D &rayDir)
static RayHit pick(const QVector3D &rayOrigin, const QVector3D &rayDir, const SubView &subView, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces, const QMap< const QStandardItem *, std::shared_ptr< BrainSurface > > &itemSurfaceMap, const QMap< const QStandardItem *, std::shared_ptr< DipoleObject > > &itemDipoleMap)
static QString buildLabel(const RayHit &result, const QMap< const QStandardItem *, std::shared_ptr< BrainSurface > > &itemSurfaceMap, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces)
Hierarchical item model organizing all 3-D scene objects (surfaces, sensors, sources,...
Base tree item providing check-state, visibility, and data-role storage for all 3-D scene items.
int type() const override
QMatrix4x4 transform() const
void setVisible(bool visible)
static constexpr int itemTypeId(ItemType type)
Tree item representing a BEM surface layer in the 3-D scene hierarchy.
const MNELIB::MNEBemSurface & bemSurfaceData() const
Digitizer point group tree item.
PointKind pointKind() const
const QVector< QVector3D > & positions() const
Tree item representing a set of fitted dipoles in the 3-D scene hierarchy.
const INVLIB::InvEcdSet & ecdSet() const
Tree item representing MEG or EEG sensor positions in the 3-D scene hierarchy.
bool hasOrientation() const
QVector3D position() const
const QMatrix4x4 & orientation() const
Source space point tree item.
const QVector< QVector3D > & positions() const
Tree item representing a FreeSurfer cortical surface in the 3-D scene hierarchy.
FSLIB::FsSurface surfaceData() const
FSLIB::FsAnnotation annotationData() const
Renderable cortical surface mesh with per-vertex color, curvature data, and GPU buffer management.
static constexpr VisualizationMode ModeSurface
DISP3DLIB::VisualizationMode VisualizationMode
void boundingBox(QVector3D &min, QVector3D &max) const
static constexpr VisualizationMode ModeScientific
Data model for a single 2-D MRI volume slice.
void colorsAvailable(const QString &surfaceKey, const QVector< uint32_t > &colors)
void loadingProgress(int percent, const QString &message)
void loaded(int numTimePoints)
void realtimeColorsAvailable(const QVector< uint32_t > &colorsLh, const QVector< uint32_t > &colorsRh)
void timePointChanged(int index, float time)
void thresholdsUpdated(float min, float mid, float max)
static constexpr ShaderMode Holographic
DISP3DLIB::ShaderMode ShaderMode
Aggregated GPU resources and render state for the 3-D brain visualization scene.
void setMriSlicesVisible(bool visible)
void resetMultiViewLayout()
void initialize(QRhiCommandBuffer *cb) override
void resetAllSubViewState()
void setTimePoint(int index)
void setRealtimeSensorLooping(bool enabled)
bool loadNetwork(const CONNECTIVITYLIB::Network &network, const QString &name="Network")
void resetViewportCameraState(int index)
bool isVideoOverlayEnabled() const
void setProbeVisualization(const QVector3D &tip, const QVector3D &direction, float length, const QColor &color, const QColor &glowColor=QColor(0, 0, 0, 0), const QQuaternion &orientation=QQuaternion(0, 0, 0, 0))
void setSourceSpaceVisible(bool visible)
void setMegHelmetOverride(const QString &path)
void surfacePointClicked(const QVector3D &worldPos)
void keyPressEvent(QKeyEvent *event) override
bool isRealtimeSensorStreaming() const
int viewportCameraPreset(int index) const
void setRealtimeSensorAverages(int numAvr)
void setVideoOverlaySize(float meters)
void visualizationEditTargetChanged(int target)
QString bemShaderModeForTarget(int target) const
void setVisualizationEditTarget(int target)
void wheelEvent(QWheelEvent *event) override
bool loadTransformation(const QString &transPath)
void setSlice(int slotIndex, DISP3DLIB::SliceObject *slice)
void setSensorFieldContourVisible(const QString &type, bool visible)
void setBemHighContrast(bool enabled)
void setVisualizationMode(const QString &mode)
void setVideoOverlayUpHint(const QVector3D &dir)
void pushVideoDepthFrame(const QImage &depthFrame)
bool loadDipoles(const QString &dipPath)
bool isViewportEnabled(int index) const
void clearStaticMarkers()
void setSourceThresholds(float min, float mid, float max)
void setBemShaderMode(const QString &mode)
void setSensorTransEnabled(bool enabled)
bool loadSensors(const QString &fifPath)
void timePointChanged(int index, float time)
void clearProbeVisualization()
void startRealtimeSensorStreaming(const QString &modality=QStringLiteral("MEG"))
void setNetworkVisible(bool visible)
void mouseDoubleClickEvent(QMouseEvent *event) override
void clearSourceEstimate()
void surfacePointDoubleClicked(const QVector3D &worldPos)
void setVideoDepthEnabled(bool enabled)
void setRealtimeLooping(bool enabled)
void clearCameraFocusOverride()
void setModel(BrainTreeModel *model)
void sourceEstimateLoaded(int numTimePoints)
void setRealtimeSensorColormap(const QString &name)
void setHeadMovementPath(const QVector< Eigen::Vector3f > &vecPositions)
void resizeEvent(QResizeEvent *event) override
void setSourceColormap(const QString &name)
void pushVideoOverlayFrame(const QImage &frame)
int closestStcIndex(float timeSec) const
bool megFieldMapOnHeadForTarget(int target) const
void setViewportEnabled(int index, bool enabled)
void setSensorFieldColormap(const QString &name)
void setSensorFieldTimePoint(int index)
bool objectVisibleForTarget(const QString &object, int target) const
void setVideoOverlayEnabled(bool enabled)
void setDipoleVisible(bool visible)
void setVideoDepthSteps(int steps)
QString activeSurfaceForTarget(int target) const
void resetSingleViewCameraState()
void pushRealtimeSourceData(const Eigen::VectorXd &matData)
void viewCountChanged(int count)
void setInitialCameraRotation(const QQuaternion &rotation)
QString overlayModeForTarget(int target) const
void setSensorFieldVisible(const QString &type, bool visible)
bool takeScreenshot(const QString &fileName)
void shaderModeChanged(const QString &modeName)
void clearTransformation()
void render(QRhiCommandBuffer *cb) override
void setHemiVisible(int hemiIdx, bool visible)
void sensorFieldLoaded(int numTimePoints, int initialTimePoint=0)
BrainView(QWidget *parent=nullptr)
void castRay(const QPoint &pos)
bool intersectWorldRay(const QVector3D &origin, const QVector3D &direction, QVector3D &hitPoint) const
Intersect a world-space ray with loaded scene geometry.
void hoveredRegionChanged(const QString ®ionName)
void mousePressEvent(QMouseEvent *event) override
QString shaderModeForTarget(int target) const
void setShaderMode(const QString &mode)
void setViewCount(int count)
void setInfoPanelVisible(bool visible)
static QStringList probeEvokedSets(const QString &evokedPath)
void setRealtimeSensorInterval(int msec)
bool sensorFieldTimeRange(float &tmin, float &tmax) const
void setLightingEnabled(bool enabled)
void mouseMoveEvent(QMouseEvent *event) override
QMap< int, QVector3D > cardinalFiducialsInMri() const
Return cardinal fiducials (NAS/LPA/RPA) transformed to MRI coordinates.
void setMegFieldMapOnHead(bool useHead)
void setVideoOverlayOpacity(float opacity)
void setLiveRay(const QVector3D &from, const QVector3D &to, const QColor &color, float radius=0.001f)
bool savePng(const QString &path, int width=1200, int height=800, const QString &surfaceType=QStringLiteral("pial"))
void stopRealtimeSensorStreaming()
int visualizationEditTarget() const
void setViewportCameraPreset(int index, int preset)
void onRowsInserted(const QModelIndex &parent, int first, int last)
bool loadSourceEstimate(const QString &lhPath, const QString &rhPath)
void syncBemShadersToBrainShaders()
bool bemTopVertexInMri(QVector3D &pos) const
Return the highest-Z vertex of the BEM head surface in MRI coords.
void setAutomatedRotation(bool enabled)
void setActiveSurface(const QString &type)
bool loadSensorField(const QString &evokedPath, int aveIndex=0)
void setRealtimeInterval(int msec)
void setVideoOverlayFocusPosition(const QVector3D &position)
void setLiveMarkers(const QVector< LiveMarker > &markers)
void setBemVisible(const QString &name, bool visible)
void startRealtimeStreaming()
bool isRealtimeStreaming() const
void stcLoadingProgress(int percent, const QString &message)
bool loadMegHelmetSurface(const QString &helmetFilePath)
int closestSensorFieldIndex(float timeSec) const
void mouseReleaseEvent(QMouseEvent *event) override
void setStaticMarkers(const QVector< LiveMarker > &markers)
void setSensorVisible(const QString &type, bool visible)
void stopRealtimeStreaming()
void setVideoDepthScale(float scale)
void setCameraFocusOverride(const QVector3D ¢er, float size)
void clearHeadMovementPath()
bool automatedRotation() const
int stcNumTimePoints() const
void setNetworkColormap(const QString &name)
void sensorFieldTimePointChanged(int index, float time)
void pushRealtimeSensorData(const Eigen::VectorXf &vecData)
bool loadSourceSpace(const QString &fwdPath)
void setNetworkThreshold(double threshold)
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
void sourceThresholdsUpdated(float min, float mid, float max)
void setSliceVisible(int slotIndex, bool visible)
static Qt::CursorShape cursorForHit(SplitterHit hit)
Labelled 4x4 FIFF affine: source frame, destination frame, rotation, translation and cached inverse.
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
Single averaged evoked response: time axis, data, baseline, channel info and averaging metadata.
BEM surface provides geometry information.