49#include <QResizeEvent>
53#include <QStandardItem>
54#include <QCoreApplication>
55#include <QElapsedTimer>
80 QColor color = item.color();
81 color.setAlphaF(color.alphaF() * item.alpha());
101 setMinimumSize(800, 600);
103 setAutoRenderTarget(
false);
105#if defined(WASMBUILD) || defined(__EMSCRIPTEN__)
107#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
109#elif defined(Q_OS_WIN)
110 setApi(Api::Direct3D11);
115 setMouseTracking(
true);
121 m_fpsLabel =
new QLabel(
this);
122 m_fpsLabel->setStyleSheet(
"color: white; font-weight: bold; font-family: monospace; font-size: 13px; background: transparent; padding: 5px;");
123 m_fpsLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
124 m_fpsLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
125 m_fpsLabel->setText(
"FPS: --.-\nVertices: 0");
126 m_fpsLabel->adjustSize();
127 m_fpsLabel->move(width() - m_fpsLabel->width() - 10, 10);
130 m_singleViewInfoLabel =
new QLabel(
this);
131 m_singleViewInfoLabel->setStyleSheet(
"color: white; font-family: monospace; font-size: 10px; background: rgba(0,0,0,110); border-radius: 3px; padding: 2px 4px;");
132 m_singleViewInfoLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
133 m_singleViewInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
134 m_singleViewInfoLabel->setText(
"");
135 m_singleViewInfoLabel->adjustSize();
136 m_singleViewInfoLabel->hide();
140 m_regionLabel =
new QLabel(
this);
141 m_regionLabel->setStyleSheet(
"color: white; font-weight: bold; font-family: sans-serif; font-size: 16px; background: transparent; padding: 5px;");
142 m_regionLabel->setText(
"");
143 m_regionLabel->move(10, 10);
144 m_regionLabel->resize(300, 30);
145 m_regionLabel->hide();
148 m_subViews.resize(kDefaultViewportCount);
149 m_viewportNameLabels.resize(kDefaultViewportCount,
nullptr);
150 m_viewportInfoLabels.resize(kDefaultViewportCount,
nullptr);
151 for (
int i = 0; i < kDefaultViewportCount; ++i) {
154 m_viewportNameLabels[i] =
new QLabel(
this);
155 m_viewportNameLabels[i]->setStyleSheet(
"color: white; font-weight: bold; font-family: sans-serif; font-size: 12px; background: transparent; padding: 2px 4px;");
156 m_viewportNameLabels[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
158 m_viewportNameLabels[i]->adjustSize();
159 m_viewportNameLabels[i]->hide();
161 m_viewportInfoLabels[i] =
new QLabel(
this);
162 m_viewportInfoLabels[i]->setStyleSheet(
"color: white; font-family: monospace; font-size: 10px; background: rgba(0,0,0,110); border-radius: 3px; padding: 2px 4px;");
163 m_viewportInfoLabels[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
164 m_viewportInfoLabels[i]->setAlignment(Qt::AlignLeft | Qt::AlignTop);
165 m_viewportInfoLabels[i]->setText(
"");
166 m_viewportInfoLabels[i]->adjustSize();
167 m_viewportInfoLabels[i]->hide();
170 m_verticalSeparator =
new QFrame(
this);
171 m_verticalSeparator->setFrameShape(QFrame::NoFrame);
172 m_verticalSeparator->setAttribute(Qt::WA_TransparentForMouseEvents);
173 m_verticalSeparator->hide();
175 m_horizontalSeparator =
new QFrame(
this);
176 m_horizontalSeparator->setFrameShape(QFrame::NoFrame);
177 m_horizontalSeparator->setAttribute(Qt::WA_TransparentForMouseEvents);
178 m_horizontalSeparator->hide();
180 QColor sepColor = palette().color(QPalette::Midlight);
181 if (sepColor.alpha() == 255) {
182 sepColor.setAlpha(180);
184 const QString sepStyle = QString(
"background-color: rgba(%1,%2,%3,%4);")
186 .arg(sepColor.green())
187 .arg(sepColor.blue())
188 .arg(sepColor.alpha());
189 m_verticalSeparator->setStyleSheet(sepStyle);
190 m_horizontalSeparator->setStyleSheet(sepStyle);
192 loadMultiViewSettings();
193 updateViewportSeparators();
194 updateOverlayLayout();
198 QColor(200, 255, 255, 160));
202 this, &BrainView::onSourceEstimateLoaded);
210 this, &BrainView::onRealtimeColorsAvailable);
214 this, &BrainView::onSensorStreamColorsAvailable);
218 m_videoOverlay = std::make_unique<DISP3DLIB::VideoOverlay>();
225 saveMultiViewSettings();
232 if (model == m_model)
236 disconnect(m_model,
nullptr,
this,
nullptr);
237 for (
auto it = m_itemSurfaceMap.cbegin(); it != m_itemSurfaceMap.cend(); ++it) {
238 for (
auto sit = m_surfaces.begin(); sit != m_surfaces.end();) {
239 sit = (sit.value() == it.value()) ? m_surfaces.erase(sit) : std::next(sit);
242 m_itemSurfaceMap.clear();
243 m_itemDipoleMap.clear();
244 m_hoveredItem =
nullptr;
245 m_activeSurface.reset();
258 if (m_model->rowCount() > 0) {
271 m_cameraRotation = rotation;
272 saveMultiViewSettings();
282 for (
int i = first; i <= last; ++i) {
283 QModelIndex index = m_model->index(i, 0, parent);
284 QStandardItem* item = m_model->itemFromIndex(index);
291 auto brainSurf = std::make_shared<BrainSurface>();
297 if (absItem->parent()) {
298 QString parentText = absItem->parent()->text();
299 if (parentText ==
"lh")
300 brainSurf->setHemi(0);
301 else if (parentText ==
"rh")
302 brainSurf->setHemi(1);
306 brainSurf->setVisible(surfItem->
isVisible());
311 m_itemSurfaceMap[item] = brainSurf;
315 if (absItem->parent()) {
316 key = absItem->parent()->text() +
"_" + surfItem->text();
318 key = surfItem->text();
320 m_surfaces[key] = brainSurf;
328 if (!m_activeSurface) {
329 m_activeSurface = brainSurf;
330 m_activeSurfaceType = surfItem->text();
338 auto brainSurf = std::make_shared<BrainSurface>();
341 brainSurf->fromBemSurface(bemSurfData, itemColor(*bemItem));
343 brainSurf->setVisible(bemItem->
isVisible());
346 QString surfName = bemItem->text().toLower();
347 if (surfName.contains(
"head") || surfName.contains(
"skin") || surfName.contains(
"scalp")) {
349 }
else if (surfName.contains(
"outer") && surfName.contains(
"skull")) {
351 }
else if (surfName.contains(
"inner") && surfName.contains(
"skull")) {
353 }
else if (surfName.contains(
"skull")) {
355 }
else if (surfName.contains(
"brain")) {
359 m_itemSurfaceMap[item] = brainSurf;
362 m_surfaces[
"bem_" + bemItem->text()] = brainSurf;
369 std::shared_ptr<BrainSurface> brainSurf;
371 QString parentText =
"";
372 if (sensItem->parent())
373 parentText = sensItem->parent()->text();
375 if (parentText.contains(
"MEG/Grad") && sensItem->
hasOrientation()) {
377 itemColor(*sensItem), sensItem->
scale());
378 }
else if (parentText.contains(
"MEG/Mag") && sensItem->
hasOrientation()) {
380 itemColor(*sensItem), sensItem->
scale());
384 itemColor(*sensItem));
388 m_itemSurfaceMap[item] = brainSurf;
394 QString key = keyPrefix + sensItem->text() +
"_" + QString::number((quintptr)sensItem);
395 m_surfaces[key] = brainSurf;
401 auto dipObject = std::make_shared<DipoleObject>();
402 dipObject->load(dipItem->
ecdSet());
403 dipObject->setVisible(dipItem->
isVisible());
404 dipObject->applyTransform(itemTransform(item));
406 m_itemDipoleMap[item] = dipObject;
412 const QVector<QVector3D>& positions = srcItem->
positions();
413 if (positions.isEmpty())
417 itemColor(*srcItem));
418 brainSurf->setVisible(srcItem->
isVisible());
419 m_itemSurfaceMap[item] = brainSurf;
421 QString key =
"srcsp_" + srcItem->text();
422 m_surfaces[key] = brainSurf;
428 const QVector<QVector3D>& positions = digItem->
positions();
429 if (positions.isEmpty())
433 itemColor(*digItem));
434 brainSurf->setVisible(digItem->
isVisible());
436 m_itemSurfaceMap[item] = brainSurf;
443 static int s_digKeyCounter = 0;
447 catName =
"cardinal";
459 catName = digItem->text().toLower().replace(
' ',
'_');
462 QString key = QStringLiteral(
"dig_%1_%2").arg(catName).arg(s_digKeyCounter++);
463 m_surfaces[key] = brainSurf;
466 if (m_itemSurfaceMap.contains(item))
467 m_itemSurfaceMap[item]->applyTransform(itemTransform(item));
470 if (m_model->hasChildren(index)) {
474 updateInflatedSurfaceTransforms();
476 m_vertexCountDirty =
true;
483 bool transformChanged =
false;
484 for (
int i = topLeft.row(); i <= bottomRight.row(); ++i) {
485 QModelIndex index = m_model->index(i, 0, topLeft.parent());
486 QStandardItem* item = m_model->itemFromIndex(index);
488 if (m_itemSurfaceMap.contains(item)) {
489 auto surf = m_itemSurfaceMap[item];
495 m_vertexCountDirty =
true;
498 surf->setColor(itemColor(*absItem));
501 surf->applyTransform(itemTransform(item));
502 transformChanged =
true;
513 if (m_itemDipoleMap.contains(item)) {
516 m_itemDipoleMap[item]->
setVisible(absItem->isVisible());
518 m_itemDipoleMap[item]->applyTransform(itemTransform(item));
521 if (transformChanged) {
522 updateInflatedSurfaceTransforms();
523 m_vertexCountDirty =
true;
534 subViewForTarget(m_visualizationEditTarget).surfaceType = type;
536 m_activeSurfaceType = type;
539 QString key =
"lh_" + type;
540 if (m_surfaces.contains(key))
541 m_activeSurface = m_surfaces[key];
544 if (m_surfaces.contains(key))
545 m_activeSurface = m_surfaces[key];
548 updateInflatedSurfaceTransforms();
549 saveMultiViewSettings();
552 m_vertexCountDirty =
true;
557void BrainView::updateSceneBounds()
559 QVector3D min(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
560 QVector3D max(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
561 bool hasContent =
false;
564 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
565 if (it.value()->isVisible()) {
566 QVector3D sMin, sMax;
567 it.value()->boundingBox(sMin, sMax);
569 min.setX(std::min(min.x(), sMin.x()));
570 min.setY(std::min(min.y(), sMin.y()));
571 min.setZ(std::min(min.z(), sMin.z()));
573 max.setX(std::max(max.x(), sMax.x()));
574 max.setY(std::max(max.y(), sMax.y()));
575 max.setZ(std::max(max.z(), sMax.z()));
580 for (
auto it = m_itemDipoleMap.cbegin(); it != m_itemDipoleMap.cend(); ++it) {
581 QVector3D dMin, dMax;
582 if (it.value()->isVisible() && it.value()->boundingBox(dMin, dMax)) {
583 min = QVector3D(std::min(min.x(), dMin.x()), std::min(min.y(), dMin.y()), std::min(min.z(), dMin.z()));
584 max = QVector3D(std::max(max.x(), dMax.x()), std::max(max.y(), dMax.y()), std::max(max.z(), dMax.z()));
590 m_sceneCenter = (min + max) * 0.5f;
592 QVector3D diag = max - min;
593 m_sceneSize = std::max(diag.x(), std::max(diag.y(), diag.z()));
596 if (m_sceneSize < 0.01f)
601 m_sceneCenter = QVector3D(0, 0, 0);
611 subViewForTarget(m_visualizationEditTarget).brainShader = mode;
613 m_brainShaderMode = mode;
614 saveMultiViewSettings();
624 const int prev = m_visualizationEditTarget;
627 const SubView& sv = subViewForTarget(m_visualizationEditTarget);
634 const bool remapMegSurface = (m_fieldMapper.megFieldMapOnHead() != visibility.
megFieldMapOnHead);
636 m_dipolesVisible = visibility.
dipoles;
637 m_networkVisible = visibility.
network;
645 if (m_fieldMapper.isLoaded()) {
646 if (remapMegSurface) {
647 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
649 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
653 updateViewportLabelHighlight();
655 saveMultiViewSettings();
657 if (prev != m_visualizationEditTarget) {
666 return m_visualizationEditTarget;
673 return subViewForTarget(target).surfaceType;
706const ViewVisibilityProfile& BrainView::visibilityProfileForTarget(
int target)
const
713SubView& BrainView::subViewForTarget(
int target)
716 return (normalized < 0) ? m_singleView : m_subViews[normalized];
721const SubView& BrainView::subViewForTarget(
int target)
const
724 return (normalized < 0) ? m_singleView : m_subViews[normalized];
736 return visibilityProfileForTarget(target).isObjectVisible(
object);
743 return visibilityProfileForTarget(target).megFieldMapOnHead;
748void BrainView::updateInflatedSurfaceTransforms()
750 const bool needsInflated = (m_singleView.
surfaceType ==
"inflated") || std::any_of(m_subViews.cbegin(), m_subViews.cend(), [](
const SubView& sv) { return sv.surfaceType ==
"inflated"; });
752 const QString lhKey =
"lh_inflated";
753 const QString rhKey =
"rh_inflated";
755 if (!m_surfaces.contains(lhKey) || !m_surfaces.contains(rhKey)) {
759 auto lhSurf = m_surfaces[lhKey];
760 auto rhSurf = m_surfaces[rhKey];
762 lhSurf->applyTransform(itemTransform(m_itemSurfaceMap.key(lhSurf)));
763 rhSurf->applyTransform(itemTransform(m_itemSurfaceMap.key(rhSurf)));
765 if (!needsInflated) {
769 const float lhMaxX = lhSurf->maxX();
770 const float rhMinX = rhSurf->minX();
772 const float gap = 0.005f;
773 const float lhOffset = -gap / 2.0f - lhMaxX;
774 const float rhOffset = gap / 2.0f - rhMinX;
776 lhSurf->translateX(lhOffset);
777 rhSurf->translateX(rhOffset);
784 subViewForTarget(m_visualizationEditTarget).bemShader = mode;
786 m_bemShaderMode = mode;
787 saveMultiViewSettings();
796 m_singleView.bemShader = m_singleView.brainShader;
797 for (
int i = 0; i < m_subViews.size(); ++i) {
798 m_subViews[i].bemShader = m_subViews[i].brainShader;
801 m_bemShaderMode = subViewForTarget(m_visualizationEditTarget).bemShader;
803 saveMultiViewSettings();
811 if (
object.isEmpty())
814 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
815 profile.setObjectVisible(
object, visible);
820 if (type == QLatin1String(
"MEG")) {
821 profile.sensMegGrad = visible;
822 profile.sensMegMag = visible;
823 }
else if (type == QLatin1String(
"EEG")) {
825 }
else if (type == QLatin1String(
"Digitizer")) {
826 profile.digCardinal = visible;
827 profile.digHpi = visible;
828 profile.digEeg = visible;
829 profile.digExtra = visible;
832 saveMultiViewSettings();
839 if (m_applySensorTrans != enabled) {
840 m_applySensorTrans = enabled;
841 refreshSensorTransforms();
851 m_megHelmetOverridePath = path;
856 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
857 profile.dipoles = visible;
858 m_dipolesVisible = visible;
859 saveMultiViewSettings();
869 SubView& sv = subViewForTarget(m_visualizationEditTarget);
872 m_currentVisMode = mode;
878 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
879 const QString& key = it.key();
880 if (key.startsWith(
"lh_") || key.startsWith(
"rh_")) {
881 it.value()->setVisualizationMode(mode);
885 saveMultiViewSettings();
894 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
896 profile.lh = visible;
897 }
else if (hemiIdx == 1) {
898 profile.rh = visible;
900 saveMultiViewSettings();
909 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
910 profile.setObjectVisible(
"bem_" + name, visible);
911 saveMultiViewSettings();
918 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
919 if (it.key().startsWith(
"bem_")) {
920 it.value()->setUseDefaultColor(enabled);
931 m_lightingEnabled = enabled;
941 if (m_pAutoRotateTimer) {
942 m_pAutoRotateTimer->stop();
947 if (!m_pAutoRotateTimer) {
948 m_pAutoRotateTimer =
new QTimer(
this);
949 m_pAutoRotateTimer->setInterval(33);
951 connect(m_pAutoRotateTimer, &QTimer::timeout,
this, [
this]() {
955 const QPoint step(2, 0);
958 for (
int i = 0; i < m_subViews.size(); ++i) {
972 m_pAutoRotateTimer->start();
979 return m_pAutoRotateTimer && m_pAutoRotateTimer->isActive();
987 if (vecPositions.size() < 2) {
993 m_headPath = std::make_unique<PolylineObject>();
996 m_headPath->setPoints(vecPositions);
997 m_headPath->setVisible(
true);
1012 m_sceneDirty =
true;
1020 const QDateTime now = QDateTime::currentDateTime();
1021 return takeScreenshot(QStringLiteral(
"./Screenshots/%1-%2-3DView.png").arg(now.toString(QStringLiteral(
"yyyy_MM_dd")), now.toString(QStringLiteral(
"hh_mm_ss"))));
1028 const QImage frame = grabFramebuffer();
1029 if (frame.isNull()) {
1030 qWarning() <<
"[BrainView::takeScreenshot] No rendered frame - nothing written to" << fileName;
1033 if (!QDir().mkpath(QFileInfo(fileName).absolutePath()) || !frame.save(fileName)) {
1034 qWarning() <<
"[BrainView::takeScreenshot] Cannot write" << fileName;
1043 const QString& surfaceType)
1048 setAttribute(Qt::WA_DontShowOnScreen,
true);
1049 resize(width, height);
1053 m_singleView.surfaceType = surfaceType;
1060 QElapsedTimer timer;
1062 while (timer.elapsed() < 5000) {
1063 QCoreApplication::processEvents(QEventLoop::AllEvents, 16);
1064 if (m_renderer && !m_surfaces.isEmpty())
1070 setAttribute(Qt::WA_DontShowOnScreen,
false);
1075 m_sceneDirty =
true;
1076 updateSceneBounds();
1079 for (
int i = 0; i < 5; ++i)
1080 QCoreApplication::processEvents(QEventLoop::AllEvents, 16);
1084 QImage img = grabFramebuffer();
1087 setAttribute(Qt::WA_DontShowOnScreen,
false);
1092 return img.save(path,
"PNG");
1100 m_isDraggingSplitter =
false;
1103 saveMultiViewSettings();
1104 updateViewportSeparators();
1105 updateOverlayLayout();
1106 m_sceneDirty =
true;
1115 saveMultiViewSettings();
1116 updateViewportSeparators();
1117 updateOverlayLayout();
1118 m_sceneDirty =
true;
1126 count = std::clamp(count, 1,
static_cast<int>(m_subViews.size()));
1127 m_viewCount = count;
1131 m_isDraggingSplitter =
false;
1138 if (m_visualizationEditTarget < 0)
1143 for (
int i = 0; i < m_subViews.size(); ++i)
1144 m_subViews[i].enabled = (i < count);
1146 saveMultiViewSettings();
1147 updateViewportSeparators();
1148 updateOverlayLayout();
1149 m_sceneDirty =
true;
1158 m_layout.resetSplits();
1159 m_multiSplitX = m_layout.splitX();
1160 m_multiSplitY = m_layout.splitY();
1161 saveMultiViewSettings();
1162 updateViewportSeparators();
1163 updateOverlayLayout();
1164 m_sceneDirty =
true;
1170 if (index < 0 || index >= m_subViews.size()) {
1174 return m_subViews[index].enabled;
1179int BrainView::enabledViewportCount()
const
1186 for (
int i = 0; i < m_subViews.size(); ++i) {
1187 if (m_subViews[i].enabled) {
1192 return numEnabled > 0 ? numEnabled : 1;
1197QVector<int> BrainView::enabledViewportIndices()
const
1201 for (
int i = 0; i < m_subViews.size(); ++i) {
1202 if (m_subViews[i].enabled)
1215int BrainView::viewportIndexAt(
const QPoint& pos)
const
1221 const auto enabledViewports = enabledViewportIndices();
1222 return m_layout.viewportIndexAt(pos, enabledViewports, size());
1227QRect BrainView::multiViewSlotRect(
int slot,
int numEnabled,
const QSize& outputSize)
const
1229 return m_layout.slotRect(slot, numEnabled, outputSize);
1234SplitterHit BrainView::hitTestSplitter(
const QPoint& pos,
int numEnabled,
const QSize& outputSize)
const
1236 if (m_viewMode !=
MultiView || numEnabled <= 1) {
1239 return m_layout.hitTestSplitter(pos, numEnabled, outputSize);
1244void BrainView::updateSplitterCursor(
const QPoint& pos)
1246 const SplitterHit hit = hitTestSplitter(pos, enabledViewportCount(), size());
1248 if (shape == Qt::ArrowCursor) {
1257void BrainView::updateViewportSeparators()
1259 if (!m_verticalSeparator || !m_horizontalSeparator) {
1263 m_verticalSeparator->hide();
1264 m_horizontalSeparator->hide();
1266 const int numEnabled = enabledViewportCount();
1267 if (m_viewMode !=
MultiView || numEnabled <= 1) {
1272 m_layout.separatorGeometries(numEnabled, size(), vRect, hRect);
1274 if (!vRect.isEmpty()) {
1275 m_verticalSeparator->setGeometry(vRect);
1276 m_verticalSeparator->show();
1277 m_verticalSeparator->raise();
1279 if (!hRect.isEmpty()) {
1280 m_horizontalSeparator->setGeometry(hRect);
1281 m_horizontalSeparator->show();
1282 m_horizontalSeparator->raise();
1285 updateOverlayLayout();
1290void BrainView::updateOverlayLayout()
1292 const auto enabledViewports = enabledViewportIndices();
1295 m_fpsLabel->setVisible(m_infoPanelVisible);
1296 m_fpsLabel->adjustSize();
1297 const int perfBottomMargin = 2;
1300 m_fpsLabel->move(width() - m_fpsLabel->width() - 10,
1301 height() - m_fpsLabel->height() - perfBottomMargin);
1303 m_fpsLabel->move(width() - m_fpsLabel->width() - 10,
1304 height() - m_fpsLabel->height() - perfBottomMargin);
1307 m_fpsLabel->raise();
1310 if (m_singleViewInfoLabel) {
1311 const bool showSingleInfo = (m_viewMode ==
SingleView) && m_infoPanelVisible;
1312 m_singleViewInfoLabel->setVisible(showSingleInfo);
1313 if (showSingleInfo) {
1314 m_singleViewInfoLabel->adjustSize();
1315 m_singleViewInfoLabel->move(width() - m_singleViewInfoLabel->width() - 8, 8);
1316 m_singleViewInfoLabel->raise();
1320 if (m_regionLabel) {
1321 const int regionY = (m_viewMode ==
MultiView) ? 38 : 10;
1322 m_regionLabel->move(10, regionY);
1323 if (!m_regionLabel->text().isEmpty()) {
1324 m_regionLabel->raise();
1328 for (
int i = 0; i < m_viewportNameLabels.size(); ++i) {
1329 if (m_viewportNameLabels[i]) {
1330 m_viewportNameLabels[i]->hide();
1332 if (m_viewportInfoLabels[i]) {
1333 m_viewportInfoLabels[i]->hide();
1341 const int numEnabled = enabledViewports.size();
1342 const QSize overlaySize = size();
1343 for (
int slot = 0; slot < numEnabled; ++slot) {
1344 const int vp = enabledViewports[slot];
1345 QLabel* label = m_viewportNameLabels[vp];
1346 QLabel* infoLabel = m_viewportInfoLabels[vp];
1351 const int preset = std::clamp(m_subViews[vp].preset, 0, 6);
1354 const QRect pane = multiViewSlotRect(slot, numEnabled, overlaySize);
1355 label->adjustSize();
1356 label->move(pane.x() + 8, pane.y() + 8);
1357 label->setVisible(
true);
1361 infoLabel->adjustSize();
1362 infoLabel->move(pane.x() + pane.width() - infoLabel->width() - 8,
1364 infoLabel->setVisible(m_infoPanelVisible);
1369 updateViewportLabelHighlight();
1374void BrainView::updateViewportLabelHighlight()
1376 static const QString normalStyle =
1377 QStringLiteral(
"color: white; font-weight: bold; font-family: sans-serif; "
1378 "font-size: 12px; background: transparent; padding: 2px 4px;");
1379 static const QString selectedStyle =
1380 QStringLiteral(
"color: #FFD54F; font-weight: bold; font-family: sans-serif; "
1381 "font-size: 13px; background: rgba(255,213,79,40); "
1382 "border: 1px solid #FFD54F; border-radius: 3px; padding: 2px 6px;");
1384 for (
int i = 0; i < m_viewportNameLabels.size(); ++i) {
1385 if (!m_viewportNameLabels[i])
1387 const bool selected = (m_viewMode ==
MultiView && m_visualizationEditTarget == i);
1388 m_viewportNameLabels[i]->setStyleSheet(selected ? selectedStyle : normalStyle);
1389 m_viewportNameLabels[i]->adjustSize();
1395void BrainView::logPerspectiveRotation(
const QString& context)
const
1402void BrainView::loadMultiViewSettings()
1405 settings.beginGroup(QStringLiteral(
"BrainView"));
1407 m_multiSplitX = settings.value(
"multiSplitX", 0.5f).toFloat();
1408 m_multiSplitY = settings.value(
"multiSplitY", 0.5f).toFloat();
1410 const int savedViewMode = settings.value(
"viewMode",
static_cast<int>(
SingleView)).toInt();
1411 m_viewCount = std::clamp(settings.value(
"viewCount", 1).toInt(), 1,
static_cast<int>(m_subViews.size()));
1415 const bool hasCameraQuat = settings.contains(
"cameraRotW") && settings.contains(
"cameraRotX") && settings.contains(
"cameraRotY") && settings.contains(
"cameraRotZ");
1416 if (hasCameraQuat) {
1417 const float w = settings.value(
"cameraRotW", 1.0f).toFloat();
1418 const float x = settings.value(
"cameraRotX", 0.0f).toFloat();
1419 const float y = settings.value(
"cameraRotY", 0.0f).toFloat();
1420 const float z = settings.value(
"cameraRotZ", 0.0f).toFloat();
1421 m_cameraRotation = QQuaternion(w, x, y, z);
1422 if (m_cameraRotation.lengthSquared() <= std::numeric_limits<float>::epsilon()) {
1423 m_cameraRotation = QQuaternion();
1425 m_cameraRotation.normalize();
1430 for (
int i = 0; i < m_subViews.size(); ++i) {
1432 m_subViews[i].enabled = (i < m_viewCount);
1436 m_singleView.load(settings,
"single_", m_cameraRotation);
1437 for (
int i = 0; i < m_subViews.size(); ++i)
1438 m_subViews[i].load(settings, QStringLiteral(
"multi%1_").arg(i), m_cameraRotation);
1440 const int maxIdx =
static_cast<int>(m_subViews.size()) - 1;
1442 settings.value(
"visualizationEditTarget", -1).toInt(), maxIdx);
1444 m_infoPanelVisible = settings.value(
"infoPanelVisible",
true).toBool();
1446 settings.endGroup();
1448 m_multiSplitX = std::clamp(m_multiSplitX, 0.15f, 0.85f);
1449 m_multiSplitY = std::clamp(m_multiSplitY, 0.15f, 0.85f);
1450 m_layout.setSplitX(m_multiSplitX);
1451 m_layout.setSplitY(m_multiSplitY);
1460 QTimer::singleShot(0,
this, [
this]() {
1468void BrainView::saveMultiViewSettings()
const
1471 settings.beginGroup(QStringLiteral(
"BrainView"));
1472 settings.setValue(
"multiSplitX", m_multiSplitX);
1473 settings.setValue(
"multiSplitY", m_multiSplitY);
1474 settings.setValue(
"viewMode",
static_cast<int>(m_viewMode));
1475 settings.setValue(
"viewCount", m_viewCount);
1476 settings.setValue(
"cameraRotW", m_cameraRotation.scalar());
1477 settings.setValue(
"cameraRotX", m_cameraRotation.x());
1478 settings.setValue(
"cameraRotY", m_cameraRotation.y());
1479 settings.setValue(
"cameraRotZ", m_cameraRotation.z());
1480 for (
int i = 0; i < m_subViews.size(); ++i)
1481 settings.setValue(QStringLiteral(
"viewportEnabled%1").arg(i), m_subViews[i].enabled);
1482 settings.setValue(
"visualizationEditTarget", m_visualizationEditTarget);
1483 settings.setValue(
"infoPanelVisible", m_infoPanelVisible);
1486 m_singleView.save(settings,
"single_");
1487 for (
int i = 0; i < m_subViews.size(); ++i)
1488 m_subViews[i].save(settings, QStringLiteral(
"multi%1_").arg(i));
1490 settings.endGroup();
1497 if (index >= 0 && index < m_subViews.size()) {
1498 m_subViews[index].enabled = enabled;
1499 saveMultiViewSettings();
1500 updateViewportSeparators();
1501 updateOverlayLayout();
1502 m_sceneDirty =
true;
1511 if (index < 0 || index >=
static_cast<int>(m_subViews.size()))
1513 preset = std::clamp(preset, 0, 6);
1514 if (m_subViews[index].preset == preset)
1516 m_subViews[index].preset = preset;
1517 saveMultiViewSettings();
1518 updateOverlayLayout();
1519 m_sceneDirty =
true;
1527 m_cameraRotation = QQuaternion();
1528 m_singleView.zoom = 0.0f;
1529 saveMultiViewSettings();
1530 m_sceneDirty =
true;
1538 if (index < 0 || index >=
static_cast<int>(m_subViews.size())) {
1542 m_subViews[index].zoom = 0.0f;
1543 m_subViews[index].pan = QVector2D();
1544 m_subViews[index].perspectiveRotation = QQuaternion();
1545 saveMultiViewSettings();
1546 m_sceneDirty =
true;
1555 for (
int i = 0; i < m_subViews.size(); ++i) {
1556 const bool wasEnabled = m_subViews[i].enabled;
1558 m_subViews[i].enabled = wasEnabled;
1560 m_cameraRotation = QQuaternion();
1561 saveMultiViewSettings();
1562 updateOverlayLayout();
1563 m_sceneDirty =
true;
1571 if (index < 0 || index >=
static_cast<int>(m_subViews.size()))
1573 return std::clamp(m_subViews[index].preset, 0, 6);
1580 m_infoPanelVisible = visible;
1581 saveMultiViewSettings();
1582 updateOverlayLayout();
1589 QRhiWidget::resizeEvent(event);
1590 updateViewportSeparators();
1591 updateOverlayLayout();
1600 m_renderer = std::make_unique<BrainRenderer>();
1605 m_renderer->ensureRenderTargets(rhi(), colorTexture(), colorTexture()->pixelSize());
1621 bool hasSurfaces = !m_surfaces.isEmpty();
1622 bool hasDipoles = !m_itemDipoleMap.isEmpty() || m_dipoles;
1625 if (!hasSurfaces && !hasDipoles && !m_electrodes) {
1628 m_renderer = std::make_unique<BrainRenderer>();
1630 m_renderer->ensureRenderTargets(rhi(), colorTexture(), colorTexture()->pixelSize());
1631 m_renderer->initialize(rhi(), m_renderer->rtClear()->renderPassDescriptor(), sampleCount());
1632 m_renderer->beginFrame(cb);
1633 m_renderer->endPass(cb);
1638 if (!m_activeSurface && !m_surfaces.isEmpty()) {
1639 m_activeSurface = m_surfaces.begin().value();
1644 if (m_fpsTimer.elapsed() >= 500) {
1645 float fps = m_frameCount / (m_fpsTimer.elapsed() / 1000.0f);
1648 if (m_vertexCountDirty) {
1649 auto countVerticesForSubView = [
this](
const SubView& sv) -> qint64 {
1651 for (
auto it = m_surfaces.cbegin(); it != m_surfaces.cend(); ++it) {
1652 const QString& key = it.key();
1653 auto surface = it.value();
1662 if (!surface->isVisible())
1665 total += surface->vertexCount();
1672 for (
int vp : enabledViewportIndices()) {
1673 vCount += countVerticesForSubView(m_subViews[vp]);
1676 vCount = countVerticesForSubView(m_singleView);
1678 m_cachedVertexCount = vCount;
1679 m_vertexCountDirty =
false;
1682 m_fpsLabel->setText(QString(
"FPS: %1\nVertices: %2").arg(fps, 0,
'f', 1).arg(m_cachedVertexCount));
1683 updateOverlayLayout();
1684 m_fpsLabel->raise();
1686 m_fpsTimer.restart();
1690 m_renderer->ensureRenderTargets(rhi(), colorTexture(), colorTexture()->pixelSize());
1691 m_renderer->initialize(rhi(), m_renderer->rtClear()->renderPassDescriptor(), sampleCount());
1694 QSize outputSize = m_renderer->rtClear()->pixelSize();
1697 const auto enabledViewports = enabledViewportIndices();
1698 int numEnabled = enabledViewports.size();
1706 m_renderer->newFrame();
1707 QRhiResourceUpdateBatch* updates = rhi()->nextResourceUpdateBatch();
1708 if (m_videoOverlay && m_videoOverlay->isEnabled()) {
1709 m_renderer->prepareVideoOverlay(rhi(), updates, m_videoOverlay.get());
1711 for (
int i = 0; i < kMaxSliceSlots; ++i) {
1712 m_renderer->prepareSlice(rhi(), updates, (m_slices[i] && m_sliceVisible[i]) ? m_slices[i] :
nullptr, i);
1716 const bool hasVideoOverlay = m_videoOverlay && m_videoOverlay->isEnabled() && m_videoOverlay->hasFrame();
1717 if (hasVideoOverlay) {
1719 if (m_surfaces.contains(QStringLiteral(
"bem_head"))) {
1720 videoOverlayTargetSurface = m_surfaces[QStringLiteral(
"bem_head")].get();
1722 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1724 videoOverlayTargetSurface = it.value().get();
1743 QRhiViewport viewport;
1744 QRhiScissor scissor;
1745 QVector<DrawItem> opaqueDraws;
1746 QVector<DrawItem> transparentDraws;
1747 int sliceOffsets[kMaxSliceSlots] = {-1, -1, -1};
1748 int videoOverlayOffset = -1;
1749 int electrodeOffset = -1;
1750 QVector<QPair<DipoleObject*, int>> dipoleDraws;
1751 int networkOffset = -1;
1752 int headPathOffset = -1;
1753 int pointerOffset = -1;
1755 std::vector<PaneDraws> panes(
static_cast<size_t>(numEnabled));
1757 for (
int slot = 0; slot < numEnabled; ++slot) {
1758 PaneDraws& pane = panes[
static_cast<size_t>(slot)];
1759 int vp = (m_viewMode ==
MultiView) ? enabledViewports[slot] : 0;
1760 const SubView& sv = (m_viewMode ==
MultiView) ? m_subViews[vp] : m_singleView;
1762 const QRect paneRect = (m_viewMode ==
MultiView)
1763 ? multiViewSlotRect(slot, numEnabled, outputSize)
1764 : QRect(0, 0, outputSize.width(), outputSize.height());
1766 QRect renderRect = paneRect;
1767 if (m_viewMode ==
MultiView && numEnabled > 1) {
1768 constexpr int separatorPx = 2;
1770 if (numEnabled == 2) {
1772 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1774 }
else if (numEnabled == 3) {
1778 renderRect.setHeight(std::max(1, renderRect.height() - separatorPx));
1779 }
else if (slot == 1) {
1781 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1785 const int col = slot % 2;
1786 const int row = slot / 2;
1788 const bool hasRightNeighbor = (col == 0) && (slot + 1 < numEnabled) && ((slot / 2) == ((slot + 1) / 2));
1789 const bool hasBottomNeighbor = (row == 0) && (slot + 2 < numEnabled);
1791 if (hasRightNeighbor) {
1792 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1794 if (hasBottomNeighbor) {
1795 renderRect.setHeight(std::max(1, renderRect.height() - separatorPx));
1800 const int viewX = renderRect.x();
1801 const int viewY = outputSize.height() - (renderRect.y() + renderRect.height());
1802 const int viewW = std::max(1, renderRect.width());
1803 const int viewH = std::max(1, renderRect.height());
1805 pane.viewport = QRhiViewport(viewX, viewY, viewW, viewH);
1806 pane.scissor = QRhiScissor(viewX, viewY, viewW, viewH);
1807 const float aspectRatio = float(viewW) / float(viewH);
1810 const QVector3D effectiveCenter = m_cameraFocusOverride ? m_cameraFocusCenter : m_sceneCenter;
1811 const float effectiveSize = m_cameraFocusOverride ? m_cameraFocusSize : m_sceneSize;
1812 m_camera.setSceneCenter(effectiveCenter);
1813 m_camera.setSceneSize(effectiveSize);
1814 m_camera.setRotation(m_cameraRotation);
1815 m_camera.setZoom(m_singleView.zoom);
1817 ? m_camera.computeMultiView(sv, aspectRatio)
1818 : m_camera.computeSingleView(aspectRatio);
1821 sceneData.
mvp = rhi()->clipSpaceCorrMatrix();
1841 QStringList drawnKeys;
1843 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1848 drawnKeys << it.key();
1850 pane.opaqueDraws.append({surf, currentShader, sceneData.
overlayMode, 0.0f,
1851 m_renderer->prepareSurfaceDraw(updates, sceneData, surf)});
1855 QLabel* infoLabel = (m_viewMode ==
MultiView) ? m_viewportInfoLabels[vp] : m_singleViewInfoLabel;
1857 const QString info = QStringLiteral(
"Shader: %1\nSurface: %2\nOverlay: %3\nDrawn: %4")
1859 drawnKeys.isEmpty() ? QStringLiteral(
"none") : drawnKeys.join(QStringLiteral(
", ")));
1860 if (infoLabel->text() != info)
1861 infoLabel->setText(info);
1871 const QString& megFieldKey = m_fieldMapper.megSurfaceKey();
1872 const QString& eegFieldKey = m_fieldMapper.eegSurfaceKey();
1875 QVector<DrawItem> opaqueDraws;
1876 QVector<DrawItem> transparentDraws;
1878 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1879 const QString& key = it.key();
1885 }
else if (key.startsWith(
"srcsp_") || key.startsWith(
"dig_")) {
1890 if (key.startsWith(QLatin1String(
"dig_live_t_")) || key.startsWith(QLatin1String(
"dig_ray_")) || key.startsWith(QLatin1String(
"dig_probe_"))) {
1891 QVector3D bmin, bmax;
1893 QVector3D ctr = (bmin + bmax) * 0.5f;
1894 float dist = (sceneData.
cameraPos - ctr).lengthSquared();
1899 opaqueDraws.append({surf, currentShader,
1904 bool isSensor = key.startsWith(
"sens_");
1905 bool isBem = key.startsWith(
"bem_");
1906 if (!isSensor && !isBem)
1913 QVector3D bmin, bmax;
1915 QVector3D center = (bmin + bmax) * 0.5f;
1916 float dist = (sceneData.
cameraPos - center).lengthSquared();
1920 if (key == megFieldKey && !megFieldVisible)
1922 else if (key == eegFieldKey && !eegFieldVisible)
1925 transparentDraws.append({surf, mode, itemOverlay, dist, -1});
1930 std::sort(transparentDraws.begin(), transparentDraws.end(),
1931 [](
const DrawItem& a,
const DrawItem& b) { return a.distSq > b.distSq; });
1934 for (
auto& item : opaqueDraws) {
1936 item.uniformOffset = m_renderer->prepareSurfaceDraw(updates, itemData, item.surface);
1938 pane.opaqueDraws += opaqueDraws;
1939 for (
auto& item : transparentDraws) {
1941 item.uniformOffset = m_renderer->prepareSurfaceDraw(updates, itemData, item.surface);
1943 pane.transparentDraws = transparentDraws;
1944 pane.electrodeOffset = m_renderer->prepareElectrodeDraw(updates, sceneData, m_electrodes);
1947 for (
int i = 0; i < kMaxSliceSlots; ++i) {
1948 if (m_slices[i] && m_sliceVisible[i]) {
1949 pane.sliceOffsets[i] = m_renderer->prepareSliceDraw(updates, sceneData, i);
1954 if (hasVideoOverlay) {
1955 pane.videoOverlayOffset = m_renderer->prepareVideoOverlayDraw(updates, sceneData, m_videoOverlay.get(), videoOverlayTargetSurface);
1961 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end(); ++it) {
1962 const int offset = m_renderer->prepareDipoleDraw(updates, sceneData, it.value().get());
1964 pane.dipoleDraws.append({it.value().get(), offset});
1966 const int offset = m_renderer->prepareDipoleDraw(updates, sceneData, m_dipoles.get());
1968 pane.dipoleDraws.append({m_dipoles.get(), offset});
1971 pane.networkOffset = m_renderer->prepareNetworkDraw(updates, sceneData, m_network.get());
1973 pane.headPathOffset = m_renderer->preparePolylineDraw(updates, sceneData, m_headPath.get());
1976 if (m_hasIntersection && m_debugPointerSurface) {
1980 QMatrix4x4 translation;
1981 translation.translate(m_lastIntersectionPoint);
1985 pane.pointerOffset = m_renderer->prepareSurfaceDraw(updates, debugSceneData, m_debugPointerSurface.get());
1990 m_renderer->beginFrame(cb, updates);
1992 for (
const PaneDraws& pane : panes) {
1993 cb->setViewport(pane.viewport);
1994 cb->setScissor(pane.scissor);
1996 for (
const auto& item : pane.opaqueDraws)
1997 m_renderer->issueSurfaceDraw(cb, item.surface, item.mode, item.uniformOffset);
1998 m_renderer->issueElectrodeDraw(cb, m_electrodes, pane.electrodeOffset);
2005 for (
int i = 0; i < kMaxSliceSlots; ++i) {
2006 m_renderer->issueSliceDraw(cb, i, pane.sliceOffsets[i]);
2010 bool videoOverlayDrawn =
false;
2011 for (
const auto& item : pane.transparentDraws) {
2012 m_renderer->issueSurfaceDraw(cb, item.surface, item.mode, item.uniformOffset);
2013 if (item.surface == videoOverlayTargetSurface && !videoOverlayDrawn) {
2014 m_renderer->issueVideoOverlayDraw(cb, m_videoOverlay.get(), videoOverlayTargetSurface, pane.videoOverlayOffset);
2015 videoOverlayDrawn =
true;
2018 if (!videoOverlayDrawn) {
2019 m_renderer->issueVideoOverlayDraw(cb, m_videoOverlay.get(), videoOverlayTargetSurface, pane.videoOverlayOffset);
2022 for (
const auto& [dipoles, offset] : pane.dipoleDraws)
2023 m_renderer->issueDipoleDraw(cb, dipoles, offset);
2024 m_renderer->issueNetworkDraw(cb, m_network.get(), pane.networkOffset);
2025 m_renderer->issuePolylineDraw(cb, m_headPath.get(), pane.headPathOffset);
2030 m_renderer->endPass(cb);
2037 if (e->button() == Qt::LeftButton) {
2038 m_perspectiveRotatedSincePress =
false;
2039 m_draggedSincePress =
false;
2042 if (e->button() == Qt::LeftButton && m_viewMode ==
MultiView) {
2043 const int clickedVp = viewportIndexAt(e->pos());
2044 if (clickedVp >= 0 && m_viewportNameLabels[clickedVp] && m_viewportNameLabels[clickedVp]->isVisible()) {
2045 if (m_viewportNameLabels[clickedVp]->geometry().contains(e->pos())) {
2046 if (clickedVp != m_visualizationEditTarget) {
2049 showViewportPresetMenu(clickedVp, mapToGlobal(e->pos()));
2050 m_lastMousePos = e->pos();
2055 const int numEnabled = enabledViewportCount();
2056 const SplitterHit hit = hitTestSplitter(e->pos(), numEnabled, size());
2058 m_isDraggingSplitter =
true;
2059 m_activeSplitter = hit;
2060 m_lastMousePos = e->pos();
2061 updateSplitterCursor(e->pos());
2066 const int clickedVpForSelection = viewportIndexAt(e->pos());
2067 if (clickedVpForSelection >= 0 && clickedVpForSelection != m_visualizationEditTarget) {
2072 m_lastMousePos = e->pos();
2079 if (m_isDraggingSplitter && (event->buttons() & Qt::LeftButton)) {
2080 m_layout.dragSplitter(event->pos(), m_activeSplitter, size());
2081 m_multiSplitX = m_layout.splitX();
2082 m_multiSplitY = m_layout.splitY();
2084 m_lastMousePos =
event->pos();
2085 updateViewportSeparators();
2086 m_sceneDirty =
true;
2091 if (event->buttons() & Qt::LeftButton) {
2093 const int activeVp = viewportIndexAt(event->pos());
2094 const int activePreset = (activeVp >= 0 && activeVp < m_subViews.size())
2095 ? std::clamp(m_subViews[activeVp].preset, 0, 6)
2100 const QPoint diff =
event->pos() - m_lastMousePos;
2102 m_draggedSincePress =
true;
2103 m_lastMousePos =
event->pos();
2104 m_sceneDirty =
true;
2111 QPoint diff =
event->pos() - m_lastMousePos;
2114 m_perspectiveRotatedSincePress =
true;
2115 m_draggedSincePress =
true;
2116 m_lastMousePos =
event->pos();
2117 m_sceneDirty =
true;
2122 m_lastMousePos =
event->pos();
2127 QPoint diff =
event->pos() - m_lastMousePos;
2129 m_draggedSincePress =
true;
2131 m_lastMousePos =
event->pos();
2132 m_sceneDirty =
true;
2136 updateSplitterCursor(event->pos());
2148 if (event->button() == Qt::LeftButton && m_isDraggingSplitter) {
2149 m_isDraggingSplitter =
false;
2151 saveMultiViewSettings();
2152 updateSplitterCursor(event->pos());
2156 if (event->button() == Qt::LeftButton && m_viewMode ==
MultiView && m_perspectiveRotatedSincePress) {
2157 m_perspectiveRotatedSincePress =
false;
2158 saveMultiViewSettings();
2162 if (event->button() == Qt::LeftButton && m_viewMode ==
MultiView && !m_perspectiveRotatedSincePress) {
2163 saveMultiViewSettings();
2167 if (event->button() == Qt::LeftButton && !m_draggedSincePress) {
2169 if (
const IEEGLIB::IeegContact* contact = m_electrodes ? m_electrodes->contactAt(m_electrodes->hoveredContact()) :
nullptr) {
2170 emit
electrodeContactClicked(contact->name, QVector3D(contact->position.x(), contact->position.y(), contact->position.z()));
2171 }
else if (m_hasIntersection) {
2177 updateSplitterCursor(event->pos());
2187 if (event->button() == Qt::LeftButton) {
2189 if (m_hasIntersection) {
2194 QRhiWidget::mouseDoubleClickEvent(event);
2201 const float delta =
event->angleDelta().y() / 120.0f;
2204 const int vp = viewportIndexAt(event->position().toPoint());
2205 if (vp >= 0 && vp < m_subViews.size()) {
2206 m_subViews[vp].zoom += delta;
2207 saveMultiViewSettings();
2210 m_singleView.zoom += delta;
2211 saveMultiViewSettings();
2213 m_sceneDirty =
true;
2221 if (event->key() == Qt::Key_S) {
2223 }
else if (event->key() == Qt::Key_R) {
2224 m_cameraRotation = QQuaternion();
2225 logPerspectiveRotation(
"reset-initial");
2226 saveMultiViewSettings();
2227 m_sceneDirty =
true;
2236 return m_sourceManager.load(lhPath, rhPath, m_surfaces, m_activeSurfaceType);
2241void BrainView::onSourceEstimateLoaded(
int numTimePoints)
2252 m_sourceManager.setTimePoint(index, m_surfaces, m_singleView, m_subViews);
2253 m_sceneDirty =
true;
2261 m_sourceManager.setColormap(name);
2269 m_sourceManager.setThresholds(min, mid, max);
2278 m_sourceManager.startStreaming(m_surfaces, m_singleView, m_subViews);
2285 m_sourceManager.stopStreaming();
2292 return m_sourceManager.isStreaming();
2299 m_sourceManager.pushData(matData);
2306 m_sourceManager.setInterval(msec);
2313 m_sourceManager.setLooping(enabled);
2318void BrainView::onRealtimeColorsAvailable(
const QVector<uint32_t>& colorsLh,
2319 const QVector<uint32_t>& colorsRh)
2322 QSet<QString> activeTypes;
2324 for (
int i = 0; i < m_subViews.size(); ++i) {
2325 activeTypes.insert(m_subViews[i].surfaceType);
2328 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
2332 for (
const QString& type : activeTypes) {
2333 if (it.key().endsWith(type)) {
2334 int hemi = it.value()->hemi();
2335 const QVector<uint32_t>& colors = (hemi == 0) ? colorsLh : colorsRh;
2336 if (!colors.isEmpty()) {
2337 it.value()->applySourceEstimateColors(colors);
2344 m_sceneDirty =
true;
2353 if (evoked.isEmpty())
2358 const int previousTimePoint = m_fieldMapper.timePoint();
2359 const bool canReuse = m_fieldMapper.hasMappingFor(evoked);
2361 m_fieldMapper.setEvoked(evoked);
2365 if (!m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans)) {
2371 m_fieldMapper.computeNormRange();
2375 const int numTimes =
static_cast<int>(m_fieldMapper.evoked().times.size());
2376 const int tp = qBound(0, previousTimePoint, numTimes - 1);
2394 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().isEmpty()) {
2398 int maxIdx =
static_cast<int>(m_fieldMapper.evoked().times.size()) - 1;
2403 m_fieldMapper.setTimePoint(qBound(0, index, maxIdx));
2404 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2406 m_sceneDirty =
true;
2414 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2415 if (type ==
"MEG") {
2416 profile.megFieldMap = visible;
2417 }
else if (type ==
"EEG") {
2418 profile.eegFieldMap = visible;
2423 saveMultiViewSettings();
2424 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2425 m_sceneDirty =
true;
2433 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2434 if (type ==
"MEG") {
2435 profile.megFieldContours = visible;
2436 }
else if (type ==
"EEG") {
2437 profile.eegFieldContours = visible;
2442 saveMultiViewSettings();
2443 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2444 m_sceneDirty =
true;
2452 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2453 if (profile.megFieldMapOnHead == useHead && m_fieldMapper.megFieldMapOnHead() == useHead) {
2457 profile.megFieldMapOnHead = useHead;
2458 m_fieldMapper.setMegFieldMapOnHead(useHead);
2459 saveMultiViewSettings();
2460 if (m_fieldMapper.isLoaded()) {
2461 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
2462 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2463 m_sceneDirty =
true;
2472 if (m_fieldMapper.colormap() == name) {
2475 m_fieldMapper.setColormap(name);
2476 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2477 m_sceneDirty =
true;
2485 return m_sourceManager.tstep();
2492 return m_sourceManager.tmin();
2499 return m_sourceManager.numTimePoints();
2506 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().nave == -1 || m_fieldMapper.evoked().times.size() == 0) {
2511 float bestDist = std::abs(m_fieldMapper.evoked().times(0) - timeSec);
2512 for (
int i = 1; i < m_fieldMapper.evoked().times.size(); ++i) {
2513 float dist = std::abs(m_fieldMapper.evoked().times(i) - timeSec);
2514 if (dist < bestDist) {
2526 return m_sourceManager.closestIndex(timeSec);
2533 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().nave == -1 || m_fieldMapper.evoked().times.size() == 0) {
2536 tmin = m_fieldMapper.evoked().times(0);
2537 tmax = m_fieldMapper.evoked().times(m_fieldMapper.evoked().times.size() - 1);
2547 m_sensorStreamManager.startStreaming(modality, m_fieldMapper, m_surfaces);
2554 m_sensorStreamManager.stopStreaming();
2561 return m_sensorStreamManager.isStreaming();
2568 m_sensorStreamManager.pushData(vecData);
2575 m_sensorStreamManager.setInterval(msec);
2582 m_sensorStreamManager.setLooping(enabled);
2589 m_sensorStreamManager.setAverages(numAvr);
2596 m_sensorStreamManager.setColormap(name);
2601void BrainView::onSensorStreamColorsAvailable(
const QString& surfaceKey,
2602 const QVector<uint32_t>& colors)
2604 if (surfaceKey.isEmpty() || !m_surfaces.contains(surfaceKey)) {
2608 auto surface = m_surfaces[surfaceKey];
2609 if (surface && !colors.isEmpty()) {
2610 surface->applySourceEstimateColors(colors);
2613 m_sceneDirty =
true;
2625 if (!r.hasInfo && !r.hasDigitizer)
2629 m_devHeadTrans = r.devHeadTrans;
2630 m_hasDevHead = r.hasDevHead;
2632 if (!r.megGradItems.isEmpty())
2633 m_model->addSensors(
"MEG/Grad", r.megGradItems);
2634 if (!r.megMagItems.isEmpty())
2635 m_model->addSensors(
"MEG/Mag", r.megMagItems);
2636 if (!r.eegItems.isEmpty())
2637 m_model->addSensors(
"EEG", r.eegItems);
2639 if (r.helmetSurface) {
2640 m_surfaces[
"sens_surface_meg"] = r.helmetSurface;
2642 qWarning() <<
"BrainView::loadSensors: NO helmet surface returned from DataLoader!";
2645 if (!r.digitizerPoints.isEmpty())
2646 m_model->addDigitizerData(r.digitizerPoints);
2649 m_cardinalDigPoints.clear();
2650 for (
const auto& dp : r.digitizerPoints) {
2652 m_cardinalDigPoints.append(dp);
2662 QMap<int, QVector3D> result;
2663 if (m_cardinalDigPoints.isEmpty())
2667 const bool haveTrans = (m_headToMriTrans.from != 0 || m_headToMriTrans.to != 0);
2668 QMatrix4x4 headToMri;
2669 headToMri.setToIdentity();
2673 for (
const auto& dp : m_cardinalDigPoints) {
2674 const QVector3D posHead(dp.r[0], dp.r[1], dp.r[2]);
2675 result[dp.ident] = haveTrans ? headToMri.map(posHead) : posHead;
2684 auto it = m_surfaces.find(QStringLiteral(
"bem_head"));
2685 if (it == m_surfaces.end() || !it.value())
2689 QVector3D bmin, bmax;
2693 pos = QVector3D((bmin.x() + bmax.x()) * 0.5f,
2694 (bmin.y() + bmax.y()) * 0.5f,
2705 qWarning() <<
"BrainView::loadMegHelmetSurface: DataLoader returned nullptr!";
2709 m_surfaces[
"sens_surface_meg"] = surface;
2710 refreshSensorTransforms();
2711 updateSceneBounds();
2712 m_sceneDirty =
true;
2725 if (ecdSet.size() == 0)
2727 m_model->addDipoles(ecdSet);
2738 m_network = std::make_unique<NetworkObject>();
2739 m_network->load(network);
2740 m_network->setVisible(
true);
2744 m_model->addNetwork(network, name);
2746 m_sceneDirty =
true;
2755 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2756 profile.network = visible;
2757 m_networkVisible = visible;
2759 m_network->setVisible(visible);
2760 saveMultiViewSettings();
2761 m_sceneDirty =
true;
2770 m_network->setThreshold(threshold);
2771 m_sceneDirty =
true;
2781 m_network->setColormap(name);
2782 m_sceneDirty =
true;
2795 if (srcSpace.isEmpty())
2797 m_model->addSourceSpace(srcSpace);
2805 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
2806 profile.sourceSpace = visible;
2807 saveMultiViewSettings();
2808 m_sceneDirty =
true;
2820 m_headToMriTrans = trans;
2821 refreshSensorTransforms();
2827QMatrix4x4 BrainView::itemTransform(
const QStandardItem* item)
const
2832 QMatrix4x4 trans = absItem->
transform();
2834 const int type = absItem->type();
2836 if (inHead && m_applySensorTrans && !m_headToMriTrans.
isEmpty())
2843void BrainView::refreshSensorTransforms()
2846 if (m_applySensorTrans && !m_headToMriTrans.isEmpty()) {
2850 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
2851 if ((it.key().startsWith(
"sens_") || it.key().startsWith(
"dig_")) && it.value()) {
2852 const QStandardItem* item = m_itemSurfaceMap.key(it.value());
2853 it.value()->applyTransform(item ? itemTransform(item) : qmat);
2856 for (
auto it = m_itemDipoleMap.cbegin(); it != m_itemDipoleMap.cend(); ++it) {
2857 it.value()->applyTransform(itemTransform(it.key()));
2860 if (m_fieldMapper.isLoaded()) {
2861 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
2862 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2871 const QSize outputSize = size();
2873 const auto enabledViewports = enabledViewportIndices();
2875 const int numEnabled = enabledViewports.size();
2877 QRect activePane(0, 0, outputSize.width(), outputSize.height());
2879 bool hasValidPane =
true;
2880 if (m_viewMode ==
MultiView && numEnabled > 1) {
2881 bool foundSlot =
false;
2882 for (
int slot = 0; slot < numEnabled; ++slot) {
2883 const QRect pane = multiViewSlotRect(slot, numEnabled, outputSize);
2884 if (pane.contains(pos)) {
2892 hasValidPane = foundSlot;
2895 const int vp = (m_viewMode ==
MultiView) ? enabledViewports[activeSlot] : 0;
2896 const SubView& sv = (m_viewMode ==
MultiView) ? m_subViews[vp] : m_singleView;
2898 const QVector3D effectiveCenter2 = m_cameraFocusOverride ? m_cameraFocusCenter : m_sceneCenter;
2899 const float effectiveSize2 = m_cameraFocusOverride ? m_cameraFocusSize : m_sceneSize;
2900 m_camera.setSceneCenter(effectiveCenter2);
2901 m_camera.setSceneSize(effectiveSize2);
2902 m_camera.setRotation(m_cameraRotation);
2903 m_camera.setZoom(m_singleView.zoom);
2904 const float aspect = float(std::max(1, activePane.width())) / float(std::max(1, activePane.height()));
2906 ? m_camera.computeMultiView(sv, aspect)
2907 : m_camera.computeSingleView(aspect);
2911 QVector3D rayOrigin, rayDir;
2918 pickResult =
RayPicker::pick(rayOrigin, rayDir, sv, m_surfaces, m_itemSurfaceMap, m_itemDipoleMap);
2920 m_hasIntersection = pickResult.
hit;
2921 if (pickResult.
hit) {
2922 m_lastIntersectionPoint = pickResult.
hitPoint;
2926 int hitContact = -1;
2927 if (hasValidPane && m_electrodes && m_electrodes->isVisible()) {
2928 float contactDist = 0.0f;
2929 hitContact = m_electrodes->intersect(rayOrigin, rayDir, contactDist);
2930 if (hitContact >= 0 && pickResult.
hit && pickResult.
distance < contactDist)
2932 if (hitContact >= 0) {
2934 m_hasIntersection =
true;
2935 m_lastIntersectionPoint = rayOrigin + contactDist * rayDir;
2937 m_electrodes->setHoveredContact(hitContact);
2940 QStandardItem* hitItem = pickResult.
item;
2944 const QString displayLabel = hitContact >= 0 ? m_electrodes->contactLabel(hitContact)
2946 const QString& hitKey = pickResult.
surfaceKey;
2947 int currentRegionId = pickResult.
regionId;
2949 if (displayLabel != m_hoveredRegion) {
2950 m_hoveredRegion = displayLabel;
2952 if (m_regionLabel) {
2953 if (m_hoveredRegion.isEmpty()) {
2954 m_regionLabel->hide();
2956 m_regionLabel->setText(m_hoveredRegion);
2957 m_regionLabel->show();
2962 QString hoveredSurfaceKey;
2963 if (hitKey.startsWith(
"sens_surface_meg")) {
2964 hoveredSurfaceKey = hitKey;
2967 if (hitItem != m_hoveredItem || hitIndex != m_hoveredIndex || hoveredSurfaceKey != m_hoveredSurfaceKey) {
2969 if (m_hoveredItem) {
2970 if (m_itemSurfaceMap.contains(m_hoveredItem)) {
2971 m_itemSurfaceMap[m_hoveredItem]->setSelected(
false);
2972 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
2973 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(-1, 0);
2974 }
else if (m_itemDipoleMap.contains(m_hoveredItem)) {
2975 m_itemDipoleMap[m_hoveredItem]->setSelected(m_hoveredIndex,
false);
2978 if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
2979 m_surfaces[m_hoveredSurfaceKey]->setSelected(
false);
2980 m_surfaces[m_hoveredSurfaceKey]->setSelectedRegion(-1);
2981 m_surfaces[m_hoveredSurfaceKey]->setSelectedVertexRange(-1, 0);
2984 m_hoveredItem = hitItem;
2985 m_hoveredIndex = hitIndex;
2986 m_hoveredSurfaceKey = hoveredSurfaceKey;
2988 if (m_hoveredItem) {
2990 if (m_itemSurfaceMap.contains(m_hoveredItem)) {
2993 bool isDigitizer = absHitSel &&
2996 if (isDigitizer && m_hoveredIndex >= 0) {
2998 int sphereIdx = m_hoveredIndex / vertsPerSphere;
2999 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(
3000 sphereIdx * vertsPerSphere, vertsPerSphere);
3001 }
else if (currentRegionId != -1) {
3002 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(currentRegionId);
3006 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3008 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3009 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
3011 }
else if (m_itemDipoleMap.contains(m_hoveredItem)) {
3012 m_itemDipoleMap[m_hoveredItem]->setSelected(m_hoveredIndex,
true);
3014 }
else if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
3015 m_surfaces[m_hoveredSurfaceKey]->setSelected(
true);
3016 m_surfaces[m_hoveredSurfaceKey]->setSelectedRegion(-1);
3018 }
else if (m_hoveredItem && m_itemSurfaceMap.contains(m_hoveredItem)) {
3020 bool isDigitizer = absHitUpd &&
3023 if (isDigitizer && m_hoveredIndex >= 0) {
3025 int sphereIdx = m_hoveredIndex / vertsPerSphere;
3026 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(
3027 sphereIdx * vertsPerSphere, vertsPerSphere);
3028 }
else if (currentRegionId != -1) {
3029 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(currentRegionId);
3030 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3032 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
3033 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
3035 }
else if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
3036 m_surfaces[m_hoveredSurfaceKey]->setSelected(
true);
3038 m_sceneDirty =
true;
3044void BrainView::showViewportPresetMenu(
int viewport,
const QPoint& globalPos)
3046 if (viewport < 0 || viewport >= m_subViews.size()) {
3051 QAction* topAction = menu.addAction(
"Top");
3052 QAction* perspectiveAction = menu.addAction(
"Perspective");
3053 QAction* frontAction = menu.addAction(
"Front");
3054 QAction* leftAction = menu.addAction(
"Left");
3055 menu.addSeparator();
3056 QAction* bottomAction = menu.addAction(
"Bottom");
3057 QAction* backAction = menu.addAction(
"Back");
3058 QAction* rightAction = menu.addAction(
"Right");
3060 const int currentPreset = std::clamp(m_subViews[viewport].preset, 0, 6);
3061 topAction->setCheckable(
true);
3062 perspectiveAction->setCheckable(
true);
3063 frontAction->setCheckable(
true);
3064 leftAction->setCheckable(
true);
3065 bottomAction->setCheckable(
true);
3066 backAction->setCheckable(
true);
3067 rightAction->setCheckable(
true);
3069 topAction->setChecked(currentPreset == 0);
3070 perspectiveAction->setChecked(currentPreset == 1);
3071 frontAction->setChecked(currentPreset == 2);
3072 leftAction->setChecked(currentPreset == 3);
3073 bottomAction->setChecked(currentPreset == 4);
3074 backAction->setChecked(currentPreset == 5);
3075 rightAction->setChecked(currentPreset == 6);
3077 QAction* selected = menu.exec(globalPos);
3082 int newPreset = currentPreset;
3083 if (selected == topAction) {
3085 }
else if (selected == perspectiveAction) {
3087 }
else if (selected == frontAction) {
3089 }
else if (selected == leftAction) {
3091 }
else if (selected == bottomAction) {
3093 }
else if (selected == backAction) {
3095 }
else if (selected == rightAction) {
3099 if (newPreset == currentPreset) {
3103 m_subViews[viewport].preset = newPreset;
3104 saveMultiViewSettings();
3105 updateOverlayLayout();
3106 m_sceneDirty =
true;
3114void BrainView::removeSurfacesByPrefix(
const QStringList& prefixes)
3117 for (
auto it = m_itemSurfaceMap.begin(); it != m_itemSurfaceMap.end();) {
3118 const QString key = m_surfaces.key(it.value());
3119 const bool remove = std::any_of(prefixes.cbegin(), prefixes.cend(), [&key](
const QString& prefix) { return key.startsWith(prefix); });
3125 QStandardItem* item =
const_cast<QStandardItem*
>(it.key());
3127 item->parent()->removeRow(item->row());
3129 m_model->removeRow(item->row());
3131 it = m_itemSurfaceMap.erase(it);
3133 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3134 const QString& key = it.key();
3135 if (std::any_of(prefixes.cbegin(), prefixes.cend(), [&key](
const QString& prefix) { return key.startsWith(prefix); }))
3136 it = m_surfaces.erase(it);
3140 updateSceneBounds();
3141 m_sceneDirty =
true;
3149 m_activeSurface.reset();
3150 m_activeSurfaceType.clear();
3151 removeSurfacesByPrefix({QStringLiteral(
"lh_"), QStringLiteral(
"rh_")});
3158 removeSurfacesByPrefix({QStringLiteral(
"bem_")});
3167 m_electrodes->setContactValues(values);
3169 for (
const QString& hemi : {QStringLiteral(
"lh"), QStringLiteral(
"rh")}) {
3170 std::shared_ptr<BrainSurface> anatomy = m_surfaces.value(hemi + QStringLiteral(
"_pial"));
3172 anatomy = m_surfaces.value(hemi + QStringLiteral(
"_white"));
3173 const QVector<uint32_t> colors = anatomy ? m_electrodes->surfaceColors(anatomy->vertexPositions()) : QVector<uint32_t>();
3174 for (
auto it = m_surfaces.cbegin(); it != m_surfaces.cend(); ++it) {
3175 if (!it.key().startsWith(hemi + QLatin1Char(
'_')) || !it.value() || it.value()->tissueType() !=
BrainSurface::TissueBrain)
3177 if (colors.size() ==
static_cast<int>(it.value()->vertexCount()))
3178 it.value()->applySourceEstimateColors(colors);
3180 it.value()->clearSourceEstimateColors();
3183 m_sceneDirty =
true;
3191 m_sourceManager.stopStreaming();
3192 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
3193 if (it.key().startsWith(
"lh_") || it.key().startsWith(
"rh_")) {
3194 it.value()->clearSourceEstimateColors();
3197 m_sceneDirty =
true;
3208 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end();) {
3210 QStandardItem* mutableItem =
const_cast<QStandardItem*
>(it.key());
3211 if (mutableItem->parent())
3212 mutableItem->parent()->removeRow(mutableItem->row());
3214 m_model->removeRow(mutableItem->row());
3216 it = m_itemDipoleMap.erase(it);
3218 m_sceneDirty =
true;
3226 removeSurfacesByPrefix({QStringLiteral(
"srcsp_")});
3234 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3235 if (it.key().startsWith(QLatin1String(
"dig_live_")))
3236 it = m_surfaces.erase(it);
3241 for (
int i = 0; i < markers.size(); ++i) {
3243 {markers[i].position}, markers[i].radius, markers[i].color);
3244 surf->setVisible(
true);
3245 const QString prefix = markers[i].transparent
3246 ? QStringLiteral(
"dig_live_t_%1")
3247 : QStringLiteral(
"dig_live_%1");
3248 m_surfaces[prefix.arg(i)] = surf;
3252 m_sceneDirty =
true;
3258 bool removed =
false;
3259 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3260 if (it.key().startsWith(QLatin1String(
"dig_live_"))) {
3261 it = m_surfaces.erase(it);
3268 m_sceneDirty =
true;
3274 const QColor& color,
float radius)
3277 m_surfaces.remove(QLatin1String(
"dig_ray_0"));
3280 surf->setVisible(
true);
3281 m_surfaces[QStringLiteral(
"dig_ray_0")] = surf;
3283 m_sceneDirty =
true;
3289 if (m_surfaces.remove(QLatin1String(
"dig_ray_0"))) {
3290 m_sceneDirty =
true;
3296 float length,
const QColor& color,
3297 const QColor& glowColor,
3298 const QQuaternion& orientation)
3301 m_surfaces.remove(QLatin1String(
"dig_probe_shaft"));
3302 m_surfaces.remove(QLatin1String(
"dig_probe_tip"));
3303 m_surfaces.remove(QLatin1String(
"dig_probe_tipglow"));
3304 m_surfaces.remove(QLatin1String(
"dig_probe_axis_x"));
3305 m_surfaces.remove(QLatin1String(
"dig_probe_axis_y"));
3306 m_surfaces.remove(QLatin1String(
"dig_probe_axis_z"));
3307 m_surfaces.remove(QLatin1String(
"dig_probe_axis_x_tip"));
3308 m_surfaces.remove(QLatin1String(
"dig_probe_axis_y_tip"));
3309 m_surfaces.remove(QLatin1String(
"dig_probe_axis_z_tip"));
3310 m_surfaces.remove(QLatin1String(
"dig_probe_axis_xn_tip"));
3311 m_surfaces.remove(QLatin1String(
"dig_probe_axis_yn_tip"));
3312 m_surfaces.remove(QLatin1String(
"dig_probe_axis_zn_tip"));
3313 m_surfaces.remove(QLatin1String(
"dig_probe_cross_x"));
3314 m_surfaces.remove(QLatin1String(
"dig_probe_cross_y"));
3315 m_surfaces.remove(QLatin1String(
"dig_probe_cross_z"));
3318 if (length > 0.0f && !direction.isNull()) {
3319 const QVector3D shaftEnd = tip - direction * length;
3320 constexpr float kShaftRadius = 0.0015f;
3322 shaft->setVisible(
true);
3323 m_surfaces[QStringLiteral(
"dig_probe_shaft")] = shaft;
3327 constexpr float kTipRadius = 0.0010f;
3329 tipSurf->setVisible(
true);
3330 m_surfaces[QStringLiteral(
"dig_probe_tip")] = tipSurf;
3333 if (glowColor.alpha() > 0) {
3334 constexpr float kGlowTipRadius = 0.003f;
3336 tipGlow->setVisible(
true);
3337 m_surfaces[QStringLiteral(
"dig_probe_tipglow")] = tipGlow;
3341 if (!orientation.isNull()) {
3342 constexpr float kCrossLen = 0.008f;
3343 constexpr float kCrossRadius = 0.0002f;
3345 const QVector3D xDir = orientation.rotatedVector(QVector3D(1, 0, 0)).normalized();
3346 const QVector3D yDir = orientation.rotatedVector(QVector3D(0, 1, 0)).normalized();
3347 const QVector3D zDir = orientation.rotatedVector(QVector3D(0, 0, 1)).normalized();
3355 const CrossDef cross[] = {
3356 {xDir, QColor(255, 50, 50, 160), QStringLiteral(
"dig_probe_cross_x")},
3357 {yDir, QColor(50, 220, 50, 160), QStringLiteral(
"dig_probe_cross_y")},
3358 {zDir, QColor(80, 140, 255, 160), QStringLiteral(
"dig_probe_cross_z")},
3360 for (
const auto& c : cross) {
3361 const QVector3D from = tip - c.dir * kCrossLen;
3362 const QVector3D to = tip + c.dir * kCrossLen;
3364 cyl->setVisible(
true);
3365 m_surfaces[c.key] = cyl;
3370 if (!orientation.isNull()) {
3371 constexpr float kAxisLen = 0.012f;
3372 constexpr float kAxisRadius = 0.0004f;
3373 constexpr float kPosTipR = 0.0012f;
3374 constexpr float kNegTipR = 0.0006f;
3376 const QVector3D xDir = orientation.rotatedVector(QVector3D(1, 0, 0)).normalized();
3377 const QVector3D yDir = orientation.rotatedVector(QVector3D(0, 1, 0)).normalized();
3378 const QVector3D zDir = orientation.rotatedVector(QVector3D(0, 0, 1)).normalized();
3384 QString cylKey, posTipKey, negTipKey;
3386 const AxisDef axes[] = {
3387 {xDir, QColor(255, 50, 50), QStringLiteral(
"dig_probe_axis_x"),
3388 QStringLiteral(
"dig_probe_axis_x_tip"), QStringLiteral(
"dig_probe_axis_xn_tip")},
3389 {yDir, QColor(50, 220, 50), QStringLiteral(
"dig_probe_axis_y"),
3390 QStringLiteral(
"dig_probe_axis_y_tip"), QStringLiteral(
"dig_probe_axis_yn_tip")},
3391 {zDir, QColor(80, 140, 255), QStringLiteral(
"dig_probe_axis_z"),
3392 QStringLiteral(
"dig_probe_axis_z_tip"), QStringLiteral(
"dig_probe_axis_zn_tip")},
3395 for (
const auto& a : axes) {
3396 const QVector3D posEnd = tip + a.dir * kAxisLen;
3397 const QVector3D negEnd = tip - a.dir * kAxisLen;
3401 cyl->setVisible(
true);
3402 m_surfaces[a.cylKey] = cyl;
3406 posTip->setVisible(
true);
3407 m_surfaces[a.posTipKey] = posTip;
3410 QColor dimColor = a.color;
3411 dimColor.setAlpha(120);
3413 negTip->setVisible(
true);
3414 m_surfaces[a.negTipKey] = negTip;
3418 m_sceneDirty =
true;
3426 qsizetype nRemoved = 0;
3427 for (
const char* key : {
"dig_probe_shaft",
3429 "dig_probe_tipglow",
3433 "dig_probe_axis_x_tip",
3434 "dig_probe_axis_y_tip",
3435 "dig_probe_axis_z_tip",
3436 "dig_probe_axis_xn_tip",
3437 "dig_probe_axis_yn_tip",
3438 "dig_probe_axis_zn_tip",
3439 "dig_probe_cross_x",
3440 "dig_probe_cross_y",
3441 "dig_probe_cross_z"}) {
3442 nRemoved += m_surfaces.remove(QLatin1String(key));
3445 const bool removed = nRemoved > 0;
3447 m_sceneDirty =
true;
3454 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3455 if (it.key().startsWith(QLatin1String(
"dig_static_")))
3456 it = m_surfaces.erase(it);
3461 for (
int i = 0; i < markers.size(); ++i) {
3463 {markers[i].position}, markers[i].radius, markers[i].color);
3464 surf->setVisible(
true);
3465 m_surfaces[QStringLiteral(
"dig_static_%1").arg(i)] = surf;
3468 m_sceneDirty =
true;
3474 bool removed =
false;
3475 for (
auto it = m_surfaces.begin(); it != m_surfaces.end();) {
3476 if (it.key().startsWith(QLatin1String(
"dig_static_"))) {
3477 it = m_surfaces.erase(it);
3484 m_sceneDirty =
true;
3491 m_cameraFocusOverride =
true;
3492 m_cameraFocusCenter = center;
3493 m_cameraFocusSize = size;
3494 m_sceneDirty =
true;
3500 if (!m_cameraFocusOverride)
3502 m_cameraFocusOverride =
false;
3503 updateSceneBounds();
3504 m_sceneDirty =
true;
3510 m_devHeadTrans = QMatrix4x4();
3511 m_hasDevHead =
false;
3512 removeSurfacesByPrefix({QStringLiteral(
"sens_"), QStringLiteral(
"dig_")});
3520 m_sensorStreamManager.stopStreaming();
3521 m_sceneDirty =
true;
3530 refreshSensorTransforms();
3531 m_sceneDirty =
true;
3540 m_networkVisible =
false;
3544 for (
int r = m_model->rowCount() - 1; r >= 0; --r) {
3545 QStandardItem* item = m_model->item(r);
3546 if (item && item->text() ==
"Networks") {
3547 m_model->removeRow(r);
3552 m_sceneDirty =
true;
3560 m_sourceManager.cancelLoading();
3561 m_sourceManager.stopStreaming();
3562 m_sensorStreamManager.stopStreaming();
3571 if (!m_videoOverlay)
3573 if (m_videoOverlay->isEnabled() == enabled)
3575 m_videoOverlay->setEnabled(enabled);
3576 m_sceneDirty =
true;
3582 return m_videoOverlay && m_videoOverlay->isEnabled();
3587 if (!m_videoOverlay)
3589 m_videoOverlay->setFocusPosition(position);
3590 if (m_videoOverlay->isEnabled()) {
3591 m_sceneDirty =
true;
3598 if (!m_videoOverlay)
3600 m_videoOverlay->setUpHint(dir);
3601 if (m_videoOverlay->isEnabled()) {
3602 m_sceneDirty =
true;
3609 if (!m_videoOverlay)
3611 m_videoOverlay->setSizeMeters(std::max(0.001f, meters));
3612 if (m_videoOverlay->isEnabled()) {
3613 m_sceneDirty =
true;
3620 if (!m_videoOverlay)
3622 m_videoOverlay->setOpacity(std::clamp(opacity, 0.0f, 1.0f));
3623 if (m_videoOverlay->isEnabled()) {
3624 m_sceneDirty =
true;
3631 if (!m_videoOverlay)
3633 m_videoOverlay->setFrame(frame);
3634 if (m_videoOverlay->isEnabled()) {
3635 m_sceneDirty =
true;
3642 if (!m_videoOverlay)
3644 m_videoOverlay->setDepthEnabled(enabled);
3645 if (m_videoOverlay->isEnabled()) {
3646 m_sceneDirty =
true;
3653 if (!m_videoOverlay)
3655 m_videoOverlay->setDepthScale(std::clamp(scale, 0.0f, 1.0f));
3656 if (m_videoOverlay->isEnabled()) {
3657 m_sceneDirty =
true;
3664 if (!m_videoOverlay)
3666 m_videoOverlay->setDepthSteps(std::clamp(steps, 8, 64));
3667 if (m_videoOverlay->isEnabled()) {
3668 m_sceneDirty =
true;
3675 if (!m_videoOverlay)
3677 m_videoOverlay->setDepthFrame(depthFrame);
3678 if (m_videoOverlay->isEnabled() && m_videoOverlay->isDepthEnabled()) {
3679 m_sceneDirty =
true;
3689 float closestDist = std::numeric_limits<float>::max();
3694 auto headIt = m_surfaces.constFind(QStringLiteral(
"bem_head"));
3695 if (headIt != m_surfaces.cend() && headIt.value()) {
3698 if (headIt.value()->intersects(origin, direction, dist, vertexIdx) && dist < closestDist) {
3700 hitPoint = origin + dist * direction;
3707 for (
auto it = m_surfaces.cbegin(); it != m_surfaces.cend(); ++it) {
3708 const auto& surf = it.value();
3709 if (!surf || !surf->isVisible())
3713 if (surf->intersects(origin, direction, dist, vertexIdx) && dist < closestDist) {
3715 hitPoint = origin + dist * direction;
3729 if (slotIndex < 0 || slotIndex >= kMaxSliceSlots)
3731 m_slices[slotIndex] = slice;
3732 m_sceneDirty =
true;
3739 m_electrodes->setHoveredContact(-1);
3740 m_electrodes = electrodes;
3748 if (slotIndex < 0 || slotIndex >= kMaxSliceSlots)
3750 m_sliceVisible[slotIndex] = visible;
3751 m_sceneDirty =
true;
3759 auto& profile = visibilityProfileForTarget(m_visualizationEditTarget);
3760 profile.mriSlices = visible;
3761 saveMultiViewSettings();
3762 m_sceneDirty =
true;
Symbolic FIFF tag, block, value, unit and channel-type constants shared across FIFFLIB.
constexpr auto 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.
Renderable cortical / BEM mesh: CPU geometry, colour and annotation streams for the renderer.
Renderable view of an sEEG / ECoG implantation: contact spheres and lead bodies.
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.
float distance
Distance along ray to hit point.
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
Renderable sEEG / ECoG electrodes: contact spheres plus lead bodies.
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
Per-draw scene uniforms: camera matrices, light and overlay mode.
void setElectrodeValues(const QMap< QString, float > &values)
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 releaseResources() override
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)
void electrodeContactClicked(const QString &contactName, const QVector3D &position)
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 setElectrodes(DISP3DLIB::ElectrodeObject *electrodes)
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.