70#include <QResizeEvent>
72#include <QCoreApplication>
74#include <QStandardItem>
94 setMinimumSize(800, 600);
97#if defined(WASMBUILD) || defined(__EMSCRIPTEN__)
99#elif defined(Q_OS_MACOS) || defined(Q_OS_IOS)
101#elif defined(Q_OS_WIN)
102 setApi(Api::Direct3D11);
107 setMouseTracking(
true);
109 m_updateTimer =
new QTimer(
this);
110 connect(m_updateTimer, &QTimer::timeout,
this, QOverload<>::of(&BrainView::update));
111 m_updateTimer->start(16);
113 m_fpsLabel =
new QLabel(
this);
114 m_fpsLabel->setStyleSheet(
"color: white; font-weight: bold; font-family: monospace; font-size: 13px; background: transparent; padding: 5px;");
115 m_fpsLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
116 m_fpsLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
117 m_fpsLabel->setText(
"FPS: --.-\nVertices: 0");
118 m_fpsLabel->adjustSize();
119 m_fpsLabel->move(width() - m_fpsLabel->width() - 10, 10);
122 m_singleViewInfoLabel =
new QLabel(
this);
123 m_singleViewInfoLabel->setStyleSheet(
"color: white; font-family: monospace; font-size: 10px; background: rgba(0,0,0,110); border-radius: 3px; padding: 2px 4px;");
124 m_singleViewInfoLabel->setAttribute(Qt::WA_TransparentForMouseEvents);
125 m_singleViewInfoLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
126 m_singleViewInfoLabel->setText(
"");
127 m_singleViewInfoLabel->adjustSize();
128 m_singleViewInfoLabel->hide();
132 m_regionLabel =
new QLabel(
this);
133 m_regionLabel->setStyleSheet(
"color: white; font-weight: bold; font-family: sans-serif; font-size: 16px; background: transparent; padding: 5px;");
134 m_regionLabel->setText(
"");
135 m_regionLabel->move(10, 10);
136 m_regionLabel->resize(300, 30);
137 m_regionLabel->hide();
140 m_subViews.resize(kDefaultViewportCount);
141 m_viewportNameLabels.resize(kDefaultViewportCount,
nullptr);
142 m_viewportInfoLabels.resize(kDefaultViewportCount,
nullptr);
143 for (
int i = 0; i < kDefaultViewportCount; ++i) {
146 m_viewportNameLabels[i] =
new QLabel(
this);
147 m_viewportNameLabels[i]->setStyleSheet(
"color: white; font-weight: bold; font-family: sans-serif; font-size: 12px; background: transparent; padding: 2px 4px;");
148 m_viewportNameLabels[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
150 m_viewportNameLabels[i]->adjustSize();
151 m_viewportNameLabels[i]->hide();
153 m_viewportInfoLabels[i] =
new QLabel(
this);
154 m_viewportInfoLabels[i]->setStyleSheet(
"color: white; font-family: monospace; font-size: 10px; background: rgba(0,0,0,110); border-radius: 3px; padding: 2px 4px;");
155 m_viewportInfoLabels[i]->setAttribute(Qt::WA_TransparentForMouseEvents);
156 m_viewportInfoLabels[i]->setAlignment(Qt::AlignLeft | Qt::AlignTop);
157 m_viewportInfoLabels[i]->setText(
"");
158 m_viewportInfoLabels[i]->adjustSize();
159 m_viewportInfoLabels[i]->hide();
162 m_verticalSeparator =
new QFrame(
this);
163 m_verticalSeparator->setFrameShape(QFrame::NoFrame);
164 m_verticalSeparator->setAttribute(Qt::WA_TransparentForMouseEvents);
165 m_verticalSeparator->hide();
167 m_horizontalSeparator =
new QFrame(
this);
168 m_horizontalSeparator->setFrameShape(QFrame::NoFrame);
169 m_horizontalSeparator->setAttribute(Qt::WA_TransparentForMouseEvents);
170 m_horizontalSeparator->hide();
172 QColor sepColor = palette().color(QPalette::Midlight);
173 if (sepColor.alpha() == 255) {
174 sepColor.setAlpha(180);
176 const QString sepStyle = QString(
"background-color: rgba(%1,%2,%3,%4);")
178 .arg(sepColor.green())
179 .arg(sepColor.blue())
180 .arg(sepColor.alpha());
181 m_verticalSeparator->setStyleSheet(sepStyle);
182 m_horizontalSeparator->setStyleSheet(sepStyle);
184 loadMultiViewSettings();
185 updateViewportSeparators();
186 updateOverlayLayout();
190 QColor(200, 255, 255, 160));
194 this, &BrainView::onSourceEstimateLoaded);
202 this, &BrainView::onRealtimeColorsAvailable);
206 this, &BrainView::onSensorStreamColorsAvailable);
213 saveMultiViewSettings();
232 m_cameraRotation = rotation;
233 saveMultiViewSettings();
240 if (!m_model)
return;
242 for (
int i = first; i <= last; ++i) {
243 QModelIndex index = m_model->index(i, 0, parent);
244 QStandardItem* item = m_model->itemFromIndex(index);
251 auto brainSurf = std::make_shared<BrainSurface>();
257 if (absItem->parent()) {
258 QString parentText = absItem->parent()->text();
259 if (parentText ==
"lh") brainSurf->setHemi(0);
260 else if (parentText ==
"rh") brainSurf->setHemi(1);
264 brainSurf->setVisible(surfItem->
isVisible());
269 m_itemSurfaceMap[item] = brainSurf;
273 if (absItem->parent()) {
274 key = absItem->parent()->text() +
"_" + surfItem->text();
276 key = surfItem->text();
278 m_surfaces[key] = brainSurf;
286 if (!m_activeSurface) {
287 m_activeSurface = brainSurf;
288 m_activeSurfaceType = surfItem->text();
296 auto brainSurf = std::make_shared<BrainSurface>();
299 brainSurf->fromBemSurface(bemSurfData, bemItem->
color());
301 brainSurf->setVisible(bemItem->
isVisible());
304 QString surfName = bemItem->text().toLower();
305 if (surfName.contains(
"head") || surfName.contains(
"skin") || surfName.contains(
"scalp")) {
307 }
else if (surfName.contains(
"outer") && surfName.contains(
"skull")) {
309 }
else if (surfName.contains(
"inner") && surfName.contains(
"skull")) {
311 }
else if (surfName.contains(
"skull")) {
313 }
else if (surfName.contains(
"brain")) {
317 m_itemSurfaceMap[item] = brainSurf;
320 m_surfaces[
"bem_" + bemItem->text()] = brainSurf;
327 std::shared_ptr<BrainSurface> brainSurf;
329 QString parentText =
"";
330 if (sensItem->parent()) parentText = sensItem->parent()->text();
332 if (parentText.contains(
"MEG/Grad") && sensItem->
hasOrientation()) {
335 }
else if (parentText.contains(
"MEG/Mag") && sensItem->
hasOrientation()) {
345 m_itemSurfaceMap[item] = brainSurf;
349 if (!m_headToMriTrans.isEmpty()) {
351 if (m_applySensorTrans) {
354 brainSurf->applyTransform(m);
360 QString key = keyPrefix + sensItem->text() +
"_" + QString::number((quintptr)sensItem);
361 m_surfaces[key] = brainSurf;
369 auto dipObject = std::make_shared<DipoleObject>();
370 dipObject->load(dipItem->
ecdSet());
371 dipObject->setVisible(dipItem->
isVisible());
373 m_itemDipoleMap[item] = dipObject;
379 const QVector<QVector3D>& positions = srcItem->
positions();
380 if (positions.isEmpty())
continue;
384 brainSurf->setVisible(srcItem->
isVisible());
385 m_itemSurfaceMap[item] = brainSurf;
387 QString key =
"srcsp_" + srcItem->text();
388 m_surfaces[key] = brainSurf;
394 const QVector<QVector3D>& positions = digItem->
positions();
395 if (positions.isEmpty())
continue;
399 brainSurf->setVisible(digItem->
isVisible());
402 if (!m_headToMriTrans.isEmpty()) {
404 if (m_applySensorTrans) {
407 brainSurf->applyTransform(m);
410 m_itemSurfaceMap[item] = brainSurf;
420 QString key =
"dig_" + catName;
421 m_surfaces[key] = brainSurf;
426 if (m_model->hasChildren(index)) {
430 updateInflatedSurfaceTransforms();
438 for (
int i = topLeft.row(); i <= bottomRight.row(); ++i) {
439 QModelIndex index = m_model->index(i, 0, topLeft.parent());
440 QStandardItem* item = m_model->itemFromIndex(index);
442 if (m_itemSurfaceMap.contains(item)) {
443 auto surf = m_itemSurfaceMap[item];
470 subViewForTarget(m_visualizationEditTarget).surfaceType = type;
472 m_activeSurfaceType = type;
475 QString key =
"lh_" + type;
476 if (m_surfaces.contains(key)) m_activeSurface = m_surfaces[key];
479 if (m_surfaces.contains(key)) m_activeSurface = m_surfaces[key];
482 updateInflatedSurfaceTransforms();
483 saveMultiViewSettings();
489void BrainView::updateSceneBounds()
491 QVector3D min(std::numeric_limits<float>::max(), std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
492 QVector3D max(std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest(), std::numeric_limits<float>::lowest());
493 bool hasContent =
false;
496 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
497 if (it.value()->isVisible()) {
498 QVector3D sMin, sMax;
499 it.value()->boundingBox(sMin, sMax);
501 min.setX(std::min(min.x(), sMin.x()));
502 min.setY(std::min(min.y(), sMin.y()));
503 min.setZ(std::min(min.z(), sMin.z()));
505 max.setX(std::max(max.x(), sMax.x()));
506 max.setY(std::max(max.y(), sMax.y()));
507 max.setZ(std::max(max.z(), sMax.z()));
513 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end(); ++it) {
514 if (it.value()->isVisible()) {
523 m_sceneCenter = (min + max) * 0.5f;
525 QVector3D diag = max - min;
526 m_sceneSize = std::max(diag.x(), std::max(diag.y(), diag.z()));
529 if (m_sceneSize < 0.01f) m_sceneSize = 0.3f;
533 m_sceneCenter = QVector3D(0,0,0);
543 subViewForTarget(m_visualizationEditTarget).brainShader = mode;
545 m_brainShaderMode = mode;
546 saveMultiViewSettings();
554 const int prev = m_visualizationEditTarget;
557 const SubView &sv = subViewForTarget(m_visualizationEditTarget);
564 const bool remapMegSurface = (m_fieldMapper.megFieldMapOnHead() != visibility.
megFieldMapOnHead);
566 m_dipolesVisible = visibility.
dipoles;
567 m_networkVisible = visibility.
network;
575 if (m_fieldMapper.isLoaded()) {
576 if (remapMegSurface) {
577 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
579 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
583 updateViewportLabelHighlight();
585 saveMultiViewSettings();
587 if (prev != m_visualizationEditTarget) {
596 return m_visualizationEditTarget;
603 return subViewForTarget(target).surfaceType;
638 return subViewForTarget(target).visibility;
643SubView& BrainView::subViewForTarget(
int target)
646 return (normalized < 0) ? m_singleView : m_subViews[normalized];
651const SubView& BrainView::subViewForTarget(
int target)
const
654 return (normalized < 0) ? m_singleView : m_subViews[normalized];
666 return visibilityProfileForTarget(target).isObjectVisible(
object);
673 return visibilityProfileForTarget(target).megFieldMapOnHead;
678void BrainView::updateInflatedSurfaceTransforms()
680 const bool needsInflated = (m_singleView.
surfaceType ==
"inflated")
681 || std::any_of(m_subViews.cbegin(), m_subViews.cend(),
682 [](
const SubView &sv) { return sv.surfaceType ==
"inflated"; });
684 const QString lhKey =
"lh_inflated";
685 const QString rhKey =
"rh_inflated";
687 if (!m_surfaces.contains(lhKey) || !m_surfaces.contains(rhKey)) {
691 auto lhSurf = m_surfaces[lhKey];
692 auto rhSurf = m_surfaces[rhKey];
695 lhSurf->applyTransform(identity);
696 rhSurf->applyTransform(identity);
698 if (!needsInflated) {
702 const float lhMaxX = lhSurf->maxX();
703 const float rhMinX = rhSurf->minX();
705 const float gap = 0.005f;
706 const float lhOffset = -gap / 2.0f - lhMaxX;
707 const float rhOffset = gap / 2.0f - rhMinX;
709 lhSurf->translateX(lhOffset);
710 rhSurf->translateX(rhOffset);
717 subViewForTarget(m_visualizationEditTarget).bemShader = mode;
719 m_bemShaderMode = mode;
720 saveMultiViewSettings();
728 m_singleView.bemShader = m_singleView.brainShader;
729 for (
int i = 0; i < m_subViews.size(); ++i) {
730 m_subViews[i].bemShader = m_subViews[i].brainShader;
733 m_bemShaderMode = subViewForTarget(m_visualizationEditTarget).bemShader;
735 saveMultiViewSettings();
742 if (
object.isEmpty())
return;
744 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
745 profile.setObjectVisible(
object, visible);
750 if (type == QLatin1String(
"MEG")) {
751 profile.sensMegGrad = visible;
752 profile.sensMegMag = visible;
753 }
else if (type == QLatin1String(
"EEG")) {
755 }
else if (type == QLatin1String(
"Digitizer")) {
756 profile.digCardinal = visible;
757 profile.digHpi = visible;
758 profile.digEeg = visible;
759 profile.digExtra = visible;
762 saveMultiViewSettings();
768 if (m_applySensorTrans != enabled) {
769 m_applySensorTrans = enabled;
770 refreshSensorTransforms();
779 m_megHelmetOverridePath = path;
784 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
785 profile.dipoles = visible;
786 m_dipolesVisible = visible;
787 saveMultiViewSettings();
796 SubView &sv = subViewForTarget(m_visualizationEditTarget);
799 m_currentVisMode = mode;
805 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
806 const QString &key = it.key();
807 if (key.startsWith(
"lh_") || key.startsWith(
"rh_")) {
808 it.value()->setVisualizationMode(mode);
812 saveMultiViewSettings();
820 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
822 profile.lh = visible;
823 }
else if (hemiIdx == 1) {
824 profile.rh = visible;
826 saveMultiViewSettings();
834 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
835 profile.setObjectVisible(
"bem_" + name, visible);
836 saveMultiViewSettings();
842 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
843 if (it.key().startsWith(
"bem_")) {
844 it.value()->setUseDefaultColor(enabled);
854 m_lightingEnabled = enabled;
862 QImage img = grabFramebuffer();
863 QString fileName = QString(
"snapshot_refactor_%1.png").arg(m_snapshotCounter++, 4, 10, QChar(
'0'));
873 m_isDraggingSplitter =
false;
876 saveMultiViewSettings();
877 updateViewportSeparators();
878 updateOverlayLayout();
887 saveMultiViewSettings();
888 updateViewportSeparators();
889 updateOverlayLayout();
897 count = std::clamp(count, 1,
static_cast<int>(m_subViews.size()));
902 m_isDraggingSplitter =
false;
909 if (m_visualizationEditTarget < 0)
914 for (
int i = 0; i < m_subViews.size(); ++i)
915 m_subViews[i].enabled = (i < count);
917 saveMultiViewSettings();
918 updateViewportSeparators();
919 updateOverlayLayout();
927 m_layout.resetSplits();
928 m_multiSplitX = m_layout.splitX();
929 m_multiSplitY = m_layout.splitY();
930 saveMultiViewSettings();
931 updateViewportSeparators();
932 updateOverlayLayout();
938 if (index < 0 || index >= m_subViews.size()) {
942 return m_subViews[index].enabled;
947int BrainView::enabledViewportCount()
const
954 for (
int i = 0; i < m_subViews.size(); ++i) {
955 if (m_subViews[i].enabled) {
960 return numEnabled > 0 ? numEnabled : 1;
965QVector<int> BrainView::enabledViewportIndices()
const
969 for (
int i = 0; i < m_subViews.size(); ++i) {
970 if (m_subViews[i].enabled)
983int BrainView::viewportIndexAt(
const QPoint& pos)
const
989 const auto enabledViewports = enabledViewportIndices();
990 return m_layout.viewportIndexAt(pos, enabledViewports, size());
995QRect BrainView::multiViewSlotRect(
int slot,
int numEnabled,
const QSize& outputSize)
const
997 return m_layout.slotRect(slot, numEnabled, outputSize);
1002SplitterHit BrainView::hitTestSplitter(
const QPoint& pos,
int numEnabled,
const QSize& outputSize)
const
1004 if (m_viewMode !=
MultiView || numEnabled <= 1) {
1007 return m_layout.hitTestSplitter(pos, numEnabled, outputSize);
1012void BrainView::updateSplitterCursor(
const QPoint& pos)
1014 const SplitterHit hit = hitTestSplitter(pos, enabledViewportCount(), size());
1016 if (shape == Qt::ArrowCursor) {
1025void BrainView::updateViewportSeparators()
1027 if (!m_verticalSeparator || !m_horizontalSeparator) {
1031 m_verticalSeparator->hide();
1032 m_horizontalSeparator->hide();
1034 const int numEnabled = enabledViewportCount();
1035 if (m_viewMode !=
MultiView || numEnabled <= 1) {
1040 m_layout.separatorGeometries(numEnabled, size(), vRect, hRect);
1042 if (!vRect.isEmpty()) {
1043 m_verticalSeparator->setGeometry(vRect);
1044 m_verticalSeparator->show();
1045 m_verticalSeparator->raise();
1047 if (!hRect.isEmpty()) {
1048 m_horizontalSeparator->setGeometry(hRect);
1049 m_horizontalSeparator->show();
1050 m_horizontalSeparator->raise();
1053 updateOverlayLayout();
1058void BrainView::updateOverlayLayout()
1060 const auto enabledViewports = enabledViewportIndices();
1063 m_fpsLabel->setVisible(m_infoPanelVisible);
1064 m_fpsLabel->adjustSize();
1065 const int perfBottomMargin = 2;
1068 m_fpsLabel->move(width() - m_fpsLabel->width() - 10,
1069 height() - m_fpsLabel->height() - perfBottomMargin);
1071 m_fpsLabel->move(width() - m_fpsLabel->width() - 10,
1072 height() - m_fpsLabel->height() - perfBottomMargin);
1075 m_fpsLabel->raise();
1078 if (m_singleViewInfoLabel) {
1079 const bool showSingleInfo = (m_viewMode ==
SingleView) && m_infoPanelVisible;
1080 m_singleViewInfoLabel->setVisible(showSingleInfo);
1081 if (showSingleInfo) {
1082 m_singleViewInfoLabel->adjustSize();
1083 m_singleViewInfoLabel->move(width() - m_singleViewInfoLabel->width() - 8, 8);
1084 m_singleViewInfoLabel->raise();
1088 if (m_regionLabel) {
1089 const int regionY = (m_viewMode ==
MultiView) ? 38 : 10;
1090 m_regionLabel->move(10, regionY);
1091 if (!m_regionLabel->text().isEmpty()) {
1092 m_regionLabel->raise();
1096 for (
int i = 0; i < m_viewportNameLabels.size(); ++i) {
1097 if (m_viewportNameLabels[i]) {
1098 m_viewportNameLabels[i]->hide();
1100 if (m_viewportInfoLabels[i]) {
1101 m_viewportInfoLabels[i]->hide();
1109 const int numEnabled = enabledViewports.size();
1110 const QSize overlaySize = size();
1111 for (
int slot = 0; slot < numEnabled; ++slot) {
1112 const int vp = enabledViewports[slot];
1113 QLabel* label = m_viewportNameLabels[vp];
1114 QLabel* infoLabel = m_viewportInfoLabels[vp];
1119 const int preset = std::clamp(m_subViews[vp].preset, 0, 6);
1122 const QRect pane = multiViewSlotRect(slot, numEnabled, overlaySize);
1123 label->adjustSize();
1124 label->move(pane.x() + 8, pane.y() + 8);
1125 label->setVisible(
true);
1129 infoLabel->adjustSize();
1130 infoLabel->move(pane.x() + pane.width() - infoLabel->width() - 8,
1132 infoLabel->setVisible(m_infoPanelVisible);
1137 updateViewportLabelHighlight();
1142void BrainView::updateViewportLabelHighlight()
1144 static const QString normalStyle =
1145 QStringLiteral(
"color: white; font-weight: bold; font-family: sans-serif; "
1146 "font-size: 12px; background: transparent; padding: 2px 4px;");
1147 static const QString selectedStyle =
1148 QStringLiteral(
"color: #FFD54F; font-weight: bold; font-family: sans-serif; "
1149 "font-size: 13px; background: rgba(255,213,79,40); "
1150 "border: 1px solid #FFD54F; border-radius: 3px; padding: 2px 6px;");
1152 for (
int i = 0; i < m_viewportNameLabels.size(); ++i) {
1153 if (!m_viewportNameLabels[i])
continue;
1154 const bool selected = (m_viewMode ==
MultiView && m_visualizationEditTarget == i);
1155 m_viewportNameLabels[i]->setStyleSheet(selected ? selectedStyle : normalStyle);
1156 m_viewportNameLabels[i]->adjustSize();
1162void BrainView::logPerspectiveRotation(
const QString& context)
const
1169void BrainView::loadMultiViewSettings()
1171 QSettings settings(
"MNECPP");
1172 settings.beginGroup(
"ex_brain_view/BrainView");
1174 m_multiSplitX = settings.value(
"multiSplitX", 0.5f).toFloat();
1175 m_multiSplitY = settings.value(
"multiSplitY", 0.5f).toFloat();
1177 const int savedViewMode = settings.value(
"viewMode",
static_cast<int>(
SingleView)).toInt();
1179 m_viewCount = std::clamp(settings.value(
"viewCount", 1).toInt(), 1,
static_cast<int>(m_subViews.size()));
1181 if (m_viewCount > 1) m_viewMode =
MultiView;
1184 const bool hasCameraQuat = settings.contains(
"cameraRotW")
1185 && settings.contains(
"cameraRotX")
1186 && settings.contains(
"cameraRotY")
1187 && settings.contains(
"cameraRotZ");
1188 if (hasCameraQuat) {
1189 const float w = settings.value(
"cameraRotW", 1.0f).toFloat();
1190 const float x = settings.value(
"cameraRotX", 0.0f).toFloat();
1191 const float y = settings.value(
"cameraRotY", 0.0f).toFloat();
1192 const float z = settings.value(
"cameraRotZ", 0.0f).toFloat();
1193 m_cameraRotation = QQuaternion(w, x, y, z);
1194 if (m_cameraRotation.lengthSquared() <= std::numeric_limits<float>::epsilon()) {
1195 m_cameraRotation = QQuaternion();
1197 m_cameraRotation.normalize();
1202 for (
int i = 0; i < m_subViews.size(); ++i) {
1204 m_subViews[i].enabled = (i < m_viewCount);
1208 m_singleView.load(settings,
"single_", m_cameraRotation);
1209 for (
int i = 0; i < m_subViews.size(); ++i)
1210 m_subViews[i].load(settings, QStringLiteral(
"multi%1_").arg(i), m_cameraRotation);
1212 const int maxIdx =
static_cast<int>(m_subViews.size()) - 1;
1214 settings.value(
"visualizationEditTarget", -1).toInt(), maxIdx);
1216 m_infoPanelVisible = settings.value(
"infoPanelVisible",
true).toBool();
1218 settings.endGroup();
1220 m_multiSplitX = std::clamp(m_multiSplitX, 0.15f, 0.85f);
1221 m_multiSplitY = std::clamp(m_multiSplitY, 0.15f, 0.85f);
1222 m_layout.setSplitX(m_multiSplitX);
1223 m_layout.setSplitY(m_multiSplitY);
1230void BrainView::saveMultiViewSettings()
const
1232 QSettings settings(
"MNECPP");
1233 settings.beginGroup(
"ex_brain_view/BrainView");
1234 settings.setValue(
"multiSplitX", m_multiSplitX);
1235 settings.setValue(
"multiSplitY", m_multiSplitY);
1236 settings.setValue(
"viewMode",
static_cast<int>(m_viewMode));
1237 settings.setValue(
"viewCount", m_viewCount);
1238 settings.setValue(
"cameraRotW", m_cameraRotation.scalar());
1239 settings.setValue(
"cameraRotX", m_cameraRotation.x());
1240 settings.setValue(
"cameraRotY", m_cameraRotation.y());
1241 settings.setValue(
"cameraRotZ", m_cameraRotation.z());
1242 for (
int i = 0; i < m_subViews.size(); ++i)
1243 settings.setValue(QStringLiteral(
"viewportEnabled%1").arg(i), m_subViews[i].enabled);
1244 settings.setValue(
"visualizationEditTarget", m_visualizationEditTarget);
1245 settings.setValue(
"infoPanelVisible", m_infoPanelVisible);
1248 m_singleView.save(settings,
"single_");
1249 for (
int i = 0; i < m_subViews.size(); ++i)
1250 m_subViews[i].save(settings, QStringLiteral(
"multi%1_").arg(i));
1252 settings.endGroup();
1259 if (index >= 0 && index < m_subViews.size()) {
1260 m_subViews[index].enabled = enabled;
1261 saveMultiViewSettings();
1262 updateViewportSeparators();
1263 updateOverlayLayout();
1272 if (index < 0 || index >=
static_cast<int>(m_subViews.size()))
1274 preset = std::clamp(preset, 0, 6);
1275 if (m_subViews[index].preset == preset)
1277 m_subViews[index].preset = preset;
1278 saveMultiViewSettings();
1279 updateOverlayLayout();
1287 if (index < 0 || index >=
static_cast<int>(m_subViews.size()))
1289 return std::clamp(m_subViews[index].preset, 0, 6);
1296 m_infoPanelVisible = visible;
1297 saveMultiViewSettings();
1298 updateOverlayLayout();
1305 QRhiWidget::resizeEvent(event);
1306 updateViewportSeparators();
1307 updateOverlayLayout();
1316 m_renderer = std::make_unique<BrainRenderer>();
1324 bool hasSurfaces = !m_surfaces.isEmpty();
1325 bool hasDipoles = !m_itemDipoleMap.isEmpty() || m_dipoles;
1328 if (!hasSurfaces && !hasDipoles) {
1331 m_renderer = std::make_unique<BrainRenderer>();
1333 m_renderer->initialize(rhi(), renderTarget()->renderPassDescriptor(), sampleCount());
1334 m_renderer->beginFrame(cb, renderTarget());
1335 m_renderer->endFrame(cb);
1340 if (!m_activeSurface && !m_surfaces.isEmpty()) {
1341 m_activeSurface = m_surfaces.begin().value();
1346 if (m_fpsTimer.elapsed() >= 500) {
1347 float fps = m_frameCount / (m_fpsTimer.elapsed() / 1000.0f);
1348 auto countVerticesForSubView = [
this](
const SubView &sv) -> qint64 {
1351 for (
auto it = m_surfaces.cbegin(); it != m_surfaces.cend(); ++it) {
1352 const QString &key = it.key();
1353 auto surface = it.value();
1367 if (!surface->isVisible()) {
1372 total += surface->vertexCount();
1380 for (
int vp : enabledViewportIndices()) {
1381 vCount += countVerticesForSubView(m_subViews[vp]);
1384 vCount = countVerticesForSubView(m_singleView);
1387 m_fpsLabel->setText(QString(
"FPS: %1\nVertices: %2").arg(fps, 0,
'f', 1).arg(vCount));
1388 updateOverlayLayout();
1389 m_fpsLabel->raise();
1391 m_fpsTimer.restart();
1395 m_renderer->initialize(rhi(), renderTarget()->renderPassDescriptor(), sampleCount());
1398 QSize outputSize = renderTarget()->pixelSize();
1401 const auto enabledViewports = enabledViewportIndices();
1402 int numEnabled = enabledViewports.size();
1421 QRhiResourceUpdateBatch *preUpload = rhi()->nextResourceUpdateBatch();
1422 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1423 it.value()->updateBuffers(rhi(), preUpload);
1425 if (m_debugPointerSurface) {
1426 m_debugPointerSurface->updateBuffers(rhi(), preUpload);
1428 for (
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end(); ++it) {
1429 it.value()->updateBuffers(rhi(), preUpload);
1432 m_dipoles->updateBuffers(rhi(), preUpload);
1435 cb->resourceUpdate(preUpload);
1439 m_renderer->beginFrame(cb, renderTarget());
1441 for (
int slot = 0; slot < numEnabled; ++slot) {
1442 int vp = (m_viewMode ==
MultiView) ? enabledViewports[slot] : 0;
1443 const SubView &sv = (m_viewMode ==
MultiView) ? m_subViews[vp] : m_singleView;
1444 const int preset = (m_viewMode ==
MultiView) ? std::clamp(sv.
preset, 0, 6) : 1;
1446 const QRect paneRect = (m_viewMode ==
MultiView)
1447 ? multiViewSlotRect(slot, numEnabled, outputSize)
1448 : QRect(0, 0, outputSize.width(), outputSize.height());
1450 QRect renderRect = paneRect;
1451 if (m_viewMode ==
MultiView && numEnabled > 1) {
1452 constexpr int separatorPx = 2;
1454 if (numEnabled == 2) {
1456 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1458 }
else if (numEnabled == 3) {
1462 renderRect.setHeight(std::max(1, renderRect.height() - separatorPx));
1463 }
else if (slot == 1) {
1465 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1469 const int col = slot % 2;
1470 const int row = slot / 2;
1472 const bool hasRightNeighbor = (col == 0)
1473 && (slot + 1 < numEnabled)
1474 && ((slot / 2) == ((slot + 1) / 2));
1475 const bool hasBottomNeighbor = (row == 0)
1476 && (slot + 2 < numEnabled);
1478 if (hasRightNeighbor) {
1479 renderRect.setWidth(std::max(1, renderRect.width() - separatorPx));
1481 if (hasBottomNeighbor) {
1482 renderRect.setHeight(std::max(1, renderRect.height() - separatorPx));
1487 const int viewX = renderRect.x();
1488 const int viewY = outputSize.height() - (renderRect.y() + renderRect.height());
1489 const int viewW = std::max(1, renderRect.width());
1490 const int viewH = std::max(1, renderRect.height());
1492 QRhiViewport viewport(viewX, viewY, viewW, viewH);
1493 QRhiScissor scissor(viewX, viewY, viewW, viewH);
1494 const float aspectRatio = float(viewW) / float(viewH);
1497 cb->setViewport(viewport);
1498 cb->setScissor(scissor);
1501 m_camera.setSceneCenter(m_sceneCenter);
1502 m_camera.setSceneSize(m_sceneSize);
1503 m_camera.setRotation(m_cameraRotation);
1504 m_camera.setZoom(m_zoom);
1506 ? m_camera.computeMultiView(sv, aspectRatio)
1507 : m_camera.computeSingleView(aspectRatio);
1510 sceneData.
mvp = rhi()->clipSpaceCorrMatrix();
1539 QStringList drawnKeys;
1540 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1543 drawnKeys << it.key();
1545 const QString drawnInfo = drawnKeys.isEmpty() ?
"none" : drawnKeys.join(
", ");
1547 if (m_viewMode ==
MultiView && m_viewportInfoLabels[vp]) {
1548 m_viewportInfoLabels[vp]->setText(
1549 QString(
"Shader: %1\nSurface: %2\nOverlay: %3\nDrawn: %4")
1551 }
else if (m_viewMode ==
SingleView && m_singleViewInfoLabel) {
1552 m_singleViewInfoLabel->setText(
1553 QString(
"Shader: %1\nSurface: %2\nOverlay: %3\nDrawn: %4")
1557 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1561 m_renderer->renderSurface(cb, rhi(), sceneData, it.value().get(), currentShader);
1569 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1570 if (!it.key().startsWith(
"srcsp_"))
continue;
1572 if (!it.value()->isVisible())
continue;
1573 m_renderer->renderSurface(cb, rhi(), nonBrainSceneData, it.value().get(), currentShader);
1577 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1578 if (!it.key().startsWith(
"dig_"))
continue;
1580 if (!it.value()->isVisible())
continue;
1581 m_renderer->renderSurface(cb, rhi(), nonBrainSceneData, it.value().get(), currentShader);
1591 QVector<RenderItem> transparentItems;
1596 const QString &megFieldKey = m_fieldMapper.megSurfaceKey();
1597 const QString &eegFieldKey = m_fieldMapper.eegSurfaceKey();
1599 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1600 bool isSensor = it.key().startsWith(
"sens_");
1601 bool isBem = it.key().startsWith(
"bem_");
1603 if (!isSensor && !isBem)
continue;
1605 if (!it.value()->isVisible())
continue;
1608 it.value()->boundingBox(min, max);
1609 QVector3D center = (min + max) * 0.5f;
1610 float d = (sceneData.
cameraPos - center).lengthSquared();
1613 if (isBem) mode = currentBemShader;
1620 if (it.key() == megFieldKey && !megFieldVisible) {
1622 }
else if (it.key() == eegFieldKey && !eegFieldVisible) {
1626 transparentItems.append({it.value().get(), d, mode, itemOverlay});
1629 std::sort(transparentItems.begin(), transparentItems.end(), [](
const RenderItem &a,
const RenderItem &b) {
1630 return a.dist > b.dist;
1638 for (
const auto &item : transparentItems) {
1640 m_renderer->renderSurface(cb, rhi(), bemSceneData, item.surf, item.mode);
1644 for(
auto it = m_itemDipoleMap.begin(); it != m_itemDipoleMap.end(); ++it) {
1646 m_renderer->renderDipoles(cb, rhi(), sceneData, it.value().get());
1651 m_renderer->renderDipoles(cb, rhi(), sceneData, m_dipoles.get());
1656 m_renderer->renderNetwork(cb, rhi(), sceneData, m_network.get());
1660 if (m_hasIntersection && m_debugPointerSurface) {
1664 QMatrix4x4 translation;
1665 translation.translate(m_lastIntersectionPoint);
1674 m_renderer->endFrame(cb);
1681 if (e->button() == Qt::LeftButton) {
1682 m_perspectiveRotatedSincePress =
false;
1685 if (e->button() == Qt::LeftButton && m_viewMode ==
MultiView) {
1686 const int clickedVp = viewportIndexAt(e->pos());
1687 if (clickedVp >= 0 && m_viewportNameLabels[clickedVp] && m_viewportNameLabels[clickedVp]->isVisible()) {
1688 if (m_viewportNameLabels[clickedVp]->geometry().contains(e->pos())) {
1689 if (clickedVp != m_visualizationEditTarget) {
1692 showViewportPresetMenu(clickedVp, mapToGlobal(e->pos()));
1693 m_lastMousePos = e->pos();
1698 const int numEnabled = enabledViewportCount();
1699 const SplitterHit hit = hitTestSplitter(e->pos(), numEnabled, size());
1701 m_isDraggingSplitter =
true;
1702 m_activeSplitter = hit;
1703 m_lastMousePos = e->pos();
1704 updateSplitterCursor(e->pos());
1709 const int clickedVpForSelection = viewportIndexAt(e->pos());
1710 if (clickedVpForSelection >= 0 && clickedVpForSelection != m_visualizationEditTarget) {
1715 m_lastMousePos = e->pos();
1722 if (m_isDraggingSplitter && (event->buttons() & Qt::LeftButton)) {
1723 m_layout.dragSplitter(event->pos(), m_activeSplitter, size());
1724 m_multiSplitX = m_layout.splitX();
1725 m_multiSplitY = m_layout.splitY();
1727 m_lastMousePos =
event->pos();
1728 updateViewportSeparators();
1733 if (event->buttons() & Qt::LeftButton) {
1735 const int activeVp = viewportIndexAt(event->pos());
1736 const int activePreset = (activeVp >= 0 && activeVp < m_subViews.size())
1737 ? std::clamp(m_subViews[activeVp].preset, 0, 6)
1742 const QPoint diff =
event->pos() - m_lastMousePos;
1744 m_lastMousePos =
event->pos();
1751 QPoint diff =
event->pos() - m_lastMousePos;
1754 m_perspectiveRotatedSincePress =
true;
1755 m_lastMousePos =
event->pos();
1760 m_lastMousePos =
event->pos();
1765 QPoint diff =
event->pos() - m_lastMousePos;
1768 m_lastMousePos =
event->pos();
1772 updateSplitterCursor(event->pos());
1784 if (event->button() == Qt::LeftButton && m_isDraggingSplitter) {
1785 m_isDraggingSplitter =
false;
1787 saveMultiViewSettings();
1788 updateSplitterCursor(event->pos());
1792 if (event->button() == Qt::LeftButton && m_viewMode ==
MultiView && m_perspectiveRotatedSincePress) {
1793 m_perspectiveRotatedSincePress =
false;
1794 saveMultiViewSettings();
1798 if (event->button() == Qt::LeftButton && m_viewMode ==
MultiView && !m_perspectiveRotatedSincePress) {
1799 saveMultiViewSettings();
1803 updateSplitterCursor(event->pos());
1813 const float delta =
event->angleDelta().y() / 120.0f;
1816 const int vp = viewportIndexAt(event->position().toPoint());
1817 if (vp >= 0 && vp < m_subViews.size()) {
1818 m_subViews[vp].zoom += delta;
1819 saveMultiViewSettings();
1831 if (event->key() == Qt::Key_S) {
1833 }
else if (event->key() == Qt::Key_R) {
1834 m_cameraRotation = QQuaternion();
1835 logPerspectiveRotation(
"reset-initial");
1836 saveMultiViewSettings();
1845 return m_sourceManager.load(lhPath, rhPath, m_surfaces, m_activeSurfaceType);
1850void BrainView::onSourceEstimateLoaded(
int numTimePoints)
1861 m_sourceManager.setTimePoint(index, m_surfaces, m_singleView, m_subViews);
1869 m_sourceManager.setColormap(name);
1877 m_sourceManager.setThresholds(min, mid, max);
1886 m_sourceManager.startStreaming(m_surfaces, m_singleView, m_subViews);
1893 m_sourceManager.stopStreaming();
1900 return m_sourceManager.isStreaming();
1907 m_sourceManager.pushData(data);
1914 m_sourceManager.setInterval(msec);
1921 m_sourceManager.setLooping(enabled);
1926void BrainView::onRealtimeColorsAvailable(
const QVector<uint32_t> &colorsLh,
1927 const QVector<uint32_t> &colorsRh)
1930 QSet<QString> activeTypes;
1932 for (
int i = 0; i < m_subViews.size(); ++i) {
1933 activeTypes.insert(m_subViews[i].surfaceType);
1936 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
1940 for (
const QString &type : activeTypes) {
1941 if (it.key().endsWith(type)) {
1942 int hemi = it.value()->hemi();
1943 const QVector<uint32_t> &colors = (hemi == 0) ? colorsLh : colorsRh;
1944 if (!colors.isEmpty()) {
1945 it.value()->applySourceEstimateColors(colors);
1960 if (evoked.isEmpty())
return false;
1964 const int previousTimePoint = m_fieldMapper.timePoint();
1965 const bool canReuse = m_fieldMapper.hasMappingFor(evoked);
1967 m_fieldMapper.setEvoked(evoked);
1971 if (!m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans)) {
1977 m_fieldMapper.computeNormRange();
1981 const int numTimes =
static_cast<int>(m_fieldMapper.evoked().times.size());
1982 const int tp = qBound(0, previousTimePoint, numTimes - 1);
2000 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().isEmpty()) {
2004 int maxIdx =
static_cast<int>(m_fieldMapper.evoked().times.size()) - 1;
2009 m_fieldMapper.setTimePoint(qBound(0, index, maxIdx));
2010 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2019 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
2020 if (type ==
"MEG") {
2021 profile.megFieldMap = visible;
2022 }
else if (type ==
"EEG") {
2023 profile.eegFieldMap = visible;
2028 saveMultiViewSettings();
2029 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2037 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
2038 if (type ==
"MEG") {
2039 profile.megFieldContours = visible;
2040 }
else if (type ==
"EEG") {
2041 profile.eegFieldContours = visible;
2046 saveMultiViewSettings();
2047 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2055 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
2056 if (profile.megFieldMapOnHead == useHead && m_fieldMapper.megFieldMapOnHead() == useHead) {
2060 profile.megFieldMapOnHead = useHead;
2061 m_fieldMapper.setMegFieldMapOnHead(useHead);
2062 saveMultiViewSettings();
2063 if (m_fieldMapper.isLoaded()) {
2064 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
2065 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2074 if (m_fieldMapper.colormap() == name) {
2077 m_fieldMapper.setColormap(name);
2078 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2086 return m_sourceManager.tstep();
2093 return m_sourceManager.tmin();
2100 return m_sourceManager.numTimePoints();
2107 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().nave == -1 || m_fieldMapper.evoked().times.size() == 0) {
2112 float bestDist = std::abs(m_fieldMapper.evoked().times(0) - timeSec);
2113 for (
int i = 1; i < m_fieldMapper.evoked().times.size(); ++i) {
2114 float dist = std::abs(m_fieldMapper.evoked().times(i) - timeSec);
2115 if (dist < bestDist) {
2127 return m_sourceManager.closestIndex(timeSec);
2134 if (!m_fieldMapper.isLoaded() || m_fieldMapper.evoked().nave == -1 || m_fieldMapper.evoked().times.size() == 0) {
2137 tmin = m_fieldMapper.evoked().times(0);
2138 tmax = m_fieldMapper.evoked().times(m_fieldMapper.evoked().times.size() - 1);
2148 m_sensorStreamManager.startStreaming(modality, m_fieldMapper, m_surfaces);
2155 m_sensorStreamManager.stopStreaming();
2162 return m_sensorStreamManager.isStreaming();
2169 m_sensorStreamManager.pushData(data);
2176 m_sensorStreamManager.setInterval(msec);
2183 m_sensorStreamManager.setLooping(enabled);
2190 m_sensorStreamManager.setAverages(numAvr);
2197 m_sensorStreamManager.setColormap(name);
2202void BrainView::onSensorStreamColorsAvailable(
const QString &surfaceKey,
2203 const QVector<uint32_t> &colors)
2205 if (surfaceKey.isEmpty() || !m_surfaces.contains(surfaceKey)) {
2209 auto surface = m_surfaces[surfaceKey];
2210 if (surface && !colors.isEmpty()) {
2211 surface->applySourceEstimateColors(colors);
2221 if (!r.hasInfo && !r.hasDigitizer)
return false;
2224 m_devHeadTrans = r.devHeadTrans;
2225 m_hasDevHead = r.hasDevHead;
2227 if (!r.megGradItems.isEmpty()) m_model->addSensors(
"MEG/Grad", r.megGradItems);
2228 if (!r.megMagItems.isEmpty()) m_model->addSensors(
"MEG/Mag", r.megMagItems);
2229 if (!r.eegItems.isEmpty()) m_model->addSensors(
"EEG", r.eegItems);
2231 if (r.helmetSurface) {
2232 m_surfaces[
"sens_surface_meg"] = r.helmetSurface;
2234 qWarning() <<
"BrainView::loadSensors: NO helmet surface returned from DataLoader!";
2237 if (!r.digitizerPoints.isEmpty())
2238 m_model->addDigitizerData(r.digitizerPoints);
2248 qWarning() <<
"BrainView::loadMegHelmetSurface: DataLoader returned nullptr!";
2252 m_surfaces[
"sens_surface_meg"] = surface;
2253 refreshSensorTransforms();
2254 updateSceneBounds();
2264 if (ecdSet.size() == 0)
return false;
2265 m_model->addDipoles(ecdSet);
2273 if (network.
getNodes().isEmpty())
return false;
2275 m_network = std::make_unique<NetworkObject>();
2276 m_network->load(network);
2277 m_network->setVisible(
true);
2280 m_model->addNetwork(network, name);
2290 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
2291 profile.network = visible;
2292 m_networkVisible = visible;
2293 if (m_network) m_network->setVisible(visible);
2294 saveMultiViewSettings();
2303 m_network->setThreshold(threshold);
2313 m_network->setColormap(name);
2323 if (srcSpace.isEmpty())
return false;
2324 m_model->addSourceSpace(srcSpace);
2332 auto &profile = visibilityProfileForTarget(m_visualizationEditTarget);
2333 profile.sourceSpace = visible;
2334 saveMultiViewSettings();
2346 m_headToMriTrans = trans;
2347 refreshSensorTransforms();
2351void BrainView::refreshSensorTransforms()
2354 if (m_applySensorTrans && !m_headToMriTrans.
isEmpty()) {
2359 for (
auto it = m_surfaces.begin(); it != m_surfaces.end(); ++it) {
2360 if ((it.key().startsWith(
"sens_") || it.key().startsWith(
"dig_")) && it.value()) {
2361 it.value()->applyTransform(qmat);
2366 if (m_fieldMapper.isLoaded()) {
2367 m_fieldMapper.buildMapping(m_surfaces, m_headToMriTrans, m_applySensorTrans);
2368 m_fieldMapper.apply(m_surfaces, m_singleView, m_subViews);
2377 const QSize outputSize = size();
2379 const auto enabledViewports = enabledViewportIndices();
2381 const int numEnabled = enabledViewports.size();
2383 QRect activePane(0, 0, outputSize.width(), outputSize.height());
2385 bool hasValidPane =
true;
2386 if (m_viewMode ==
MultiView && numEnabled > 1) {
2387 bool foundSlot =
false;
2388 for (
int slot = 0; slot < numEnabled; ++slot) {
2389 const QRect pane = multiViewSlotRect(slot, numEnabled, outputSize);
2390 if (pane.contains(pos)) {
2398 hasValidPane = foundSlot;
2401 const int vp = (m_viewMode ==
MultiView) ? enabledViewports[activeSlot] : 0;
2402 const SubView &sv = (m_viewMode ==
MultiView) ? m_subViews[vp] : m_singleView;
2404 m_camera.setSceneCenter(m_sceneCenter);
2405 m_camera.setSceneSize(m_sceneSize);
2406 m_camera.setRotation(m_cameraRotation);
2407 m_camera.setZoom(m_zoom);
2408 const float aspect = float(std::max(1, activePane.width())) / float(std::max(1, activePane.height()));
2410 ? m_camera.computeMultiView(sv, aspect)
2411 : m_camera.computeSingleView(aspect);
2415 QVector3D rayOrigin, rayDir;
2422 pickResult =
RayPicker::pick(rayOrigin, rayDir, sv, m_surfaces, m_itemSurfaceMap, m_itemDipoleMap);
2424 m_hasIntersection = pickResult.
hit;
2425 if (pickResult.
hit) {
2426 m_lastIntersectionPoint = pickResult.
hitPoint;
2429 QStandardItem *hitItem = pickResult.
item;
2434 const QString &hitKey = pickResult.
surfaceKey;
2435 int currentRegionId = pickResult.
regionId;
2437 if (displayLabel != m_hoveredRegion) {
2438 m_hoveredRegion = displayLabel;
2440 if (m_regionLabel) {
2441 if (m_hoveredRegion.isEmpty()) {
2442 m_regionLabel->hide();
2444 m_regionLabel->setText(m_hoveredRegion);
2445 m_regionLabel->show();
2450 QString hoveredSurfaceKey;
2451 if (hitKey.startsWith(
"sens_surface_meg")) {
2452 hoveredSurfaceKey = hitKey;
2455 if (hitItem != m_hoveredItem || hitIndex != m_hoveredIndex || hoveredSurfaceKey != m_hoveredSurfaceKey) {
2457 if (m_hoveredItem) {
2458 if (m_itemSurfaceMap.contains(m_hoveredItem)) {
2459 m_itemSurfaceMap[m_hoveredItem]->setSelected(
false);
2460 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
2461 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(-1, 0);
2462 }
else if (m_itemDipoleMap.contains(m_hoveredItem)) {
2463 m_itemDipoleMap[m_hoveredItem]->setSelected(m_hoveredIndex,
false);
2466 if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
2467 m_surfaces[m_hoveredSurfaceKey]->setSelected(
false);
2468 m_surfaces[m_hoveredSurfaceKey]->setSelectedRegion(-1);
2469 m_surfaces[m_hoveredSurfaceKey]->setSelectedVertexRange(-1, 0);
2472 m_hoveredItem = hitItem;
2473 m_hoveredIndex = hitIndex;
2474 m_hoveredSurfaceKey = hoveredSurfaceKey;
2476 if (m_hoveredItem) {
2478 if (m_itemSurfaceMap.contains(m_hoveredItem)) {
2481 bool isDigitizer = absHitSel &&
2484 if (isDigitizer && m_hoveredIndex >= 0) {
2486 int sphereIdx = m_hoveredIndex / vertsPerSphere;
2487 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(
2488 sphereIdx * vertsPerSphere, vertsPerSphere);
2489 }
else if (currentRegionId != -1) {
2490 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(currentRegionId);
2491 m_itemSurfaceMap[m_hoveredItem]->setSelected(
false);
2493 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
2494 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
2496 }
else if (m_itemDipoleMap.contains(m_hoveredItem)) {
2497 m_itemDipoleMap[m_hoveredItem]->setSelected(m_hoveredIndex,
true);
2499 }
else if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
2500 m_surfaces[m_hoveredSurfaceKey]->setSelected(
true);
2501 m_surfaces[m_hoveredSurfaceKey]->setSelectedRegion(-1);
2503 }
else if (m_hoveredItem && m_itemSurfaceMap.contains(m_hoveredItem)) {
2505 bool isDigitizer = absHitUpd &&
2508 if (isDigitizer && m_hoveredIndex >= 0) {
2510 int sphereIdx = m_hoveredIndex / vertsPerSphere;
2511 m_itemSurfaceMap[m_hoveredItem]->setSelectedVertexRange(
2512 sphereIdx * vertsPerSphere, vertsPerSphere);
2513 }
else if (currentRegionId != -1) {
2514 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(currentRegionId);
2515 m_itemSurfaceMap[m_hoveredItem]->setSelected(
false);
2517 m_itemSurfaceMap[m_hoveredItem]->setSelectedRegion(-1);
2518 m_itemSurfaceMap[m_hoveredItem]->setSelected(
true);
2520 }
else if (!m_hoveredSurfaceKey.isEmpty() && m_surfaces.contains(m_hoveredSurfaceKey)) {
2521 m_surfaces[m_hoveredSurfaceKey]->setSelected(
true);
2528void BrainView::showViewportPresetMenu(
int viewport,
const QPoint &globalPos)
2530 if (viewport < 0 || viewport >= m_subViews.size()) {
2535 QAction *topAction = menu.addAction(
"Top");
2536 QAction *perspectiveAction = menu.addAction(
"Perspective");
2537 QAction *frontAction = menu.addAction(
"Front");
2538 QAction *leftAction = menu.addAction(
"Left");
2539 menu.addSeparator();
2540 QAction *bottomAction = menu.addAction(
"Bottom");
2541 QAction *backAction = menu.addAction(
"Back");
2542 QAction *rightAction = menu.addAction(
"Right");
2544 const int currentPreset = std::clamp(m_subViews[viewport].preset, 0, 6);
2545 topAction->setCheckable(
true);
2546 perspectiveAction->setCheckable(
true);
2547 frontAction->setCheckable(
true);
2548 leftAction->setCheckable(
true);
2549 bottomAction->setCheckable(
true);
2550 backAction->setCheckable(
true);
2551 rightAction->setCheckable(
true);
2553 topAction->setChecked(currentPreset == 0);
2554 perspectiveAction->setChecked(currentPreset == 1);
2555 frontAction->setChecked(currentPreset == 2);
2556 leftAction->setChecked(currentPreset == 3);
2557 bottomAction->setChecked(currentPreset == 4);
2558 backAction->setChecked(currentPreset == 5);
2559 rightAction->setChecked(currentPreset == 6);
2561 QAction *selected = menu.exec(globalPos);
2566 int newPreset = currentPreset;
2567 if (selected == topAction) {
2569 }
else if (selected == perspectiveAction) {
2571 }
else if (selected == frontAction) {
2573 }
else if (selected == leftAction) {
2575 }
else if (selected == bottomAction) {
2577 }
else if (selected == backAction) {
2579 }
else if (selected == rightAction) {
2583 if (newPreset == currentPreset) {
2587 m_subViews[viewport].preset = newPreset;
2588 saveMultiViewSettings();
2589 updateOverlayLayout();
FiffEvokedSet class declaration.
RayPicker class declaration — ray casting and intersection testing.
MeshFactory class declaration — static utilities for generating primitive meshes (spheres,...
NetworkObject class declaration.
BrainSurface class declaration.
DipoleObject class declaration.
BrainRenderer class declaration.
BrainView class declaration.
DataLoader — static helpers for loading MNE data files.
QString shaderModeName(ShaderMode mode)
VisualizationMode visualizationModeFromName(const QString &name)
QString visualizationModeName(VisualizationMode mode)
bool multiViewPresetIsPerspective(int preset)
ShaderMode shaderModeFromName(const QString &name)
int normalizedVisualizationTarget(int target, int maxIndex)
QString multiViewPresetName(int preset)
Surface key constants and type-to-key mappings.
BrainTreeModel class declaration.
SurfaceTreeItem class declaration.
BemTreeItem class declaration.
SourceSpaceTreeItem class declaration.
DipoleTreeItem class declaration.
SensorTreeItem class declaration.
DigitizerTreeItem class declaration.
Network class declaration.
MNESourceSpaces class declaration.
MNEBem class declaration.
FIFF file I/O and data structures (raw, epochs, evoked, covariance, forward).
QString sensorParentToKeyPrefix(const QString &parentText)
QString sensorTypeToObjectKey(const QString &uiType)
QMatrix4x4 toQMatrix4x4(const Eigen::Matrix4f &m)
This class holds information about a network, can compute a distance table and provide network metric...
const QList< QSharedPointer< NetworkNode > > & getNodes() const
static SensorLoadResult loadSensors(const QString &fifPath, const QString &megHelmetOverridePath={})
static QStringList probeEvokedSets(const QString &evokedPath)
static INVERSELIB::ECDSet loadDipoles(const QString &dipPath)
static MNELIB::MNESourceSpaces loadSourceSpace(const QString &fwdPath)
static bool loadHeadToMriTransform(const QString &transPath, FIFFLIB::FiffCoordTrans &trans)
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)
Per-view toggle flags controlling which data layers (brain, sensors, sources, network) are visible.
Viewport subdivision holding its own camera, projection, and scissor rectangle.
bool matchesSurfaceType(const QString &key) const
ViewVisibilityProfile visibility
static bool isBrainSurfaceKey(const QString &key)
bool shouldRenderSurface(const QString &key) const
VisualizationMode overlayMode
static SubView defaultForIndex(int index)
static std::shared_ptr< BrainSurface > createPlate(const QVector3D ¢er, const QMatrix4x4 &orientation, const QColor &color, float size)
static std::shared_ptr< BrainSurface > createBatchedSpheres(const QVector< QVector3D > &positions, float radius, const QColor &color, int subdivisions=1)
static std::shared_ptr< BrainSurface > createBarbell(const QVector3D ¢er, const QMatrix4x4 &orientation, const QColor &color, float size)
static int sphereVertexCount(int subdivisions=1)
static std::shared_ptr< BrainSurface > createSphere(const QVector3D ¢er, float radius, const QColor &color, int subdivisions=1)
Computed camera matrices (projection, view, model) and vectors for a single viewport.
static void applyMouseRotation(const QPoint &delta, QQuaternion &rotation, float speed=0.5f)
static void applyMousePan(const QPoint &delta, QVector2D &pan, float sceneSize)
Result of a ray–mesh intersection test containing the hit point, triangle index, and distance.
int vertexIndex
Vertex or element index at hit.
bool hit
True if something was hit.
QString surfaceKey
Surface map key of the hit surface.
QVector3D hitPoint
World-space intersection point.
QStandardItem * item
Tree item that was hit (nullable).
int regionId
Annotation label ID.
static bool unproject(const QPoint &screenPos, const QRect &paneRect, const QMatrix4x4 &pvm, QVector3D &rayOrigin, QVector3D &rayDir)
static QString buildLabel(const PickResult &result, const QMap< const QStandardItem *, std::shared_ptr< BrainSurface > > &itemSurfaceMap, const QMap< QString, std::shared_ptr< BrainSurface > > &surfaces)
static PickResult 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)
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.
void setVisible(bool visible)
int type() const override
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 INVERSELIB::ECDSet & 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::Surface surfaceData() const
FSLIB::Annotation annotationData() const
Renderable cortical surface mesh with per-vertex color, curvature data, and GPU buffer management.
static constexpr VisualizationMode ModeScientific
::VisualizationMode VisualizationMode
static constexpr VisualizationMode ModeSurface
void colorsAvailable(const QString &surfaceKey, const QVector< uint32_t > &colors)
void loadingProgress(int percent, const QString &message)
void timePointChanged(int index, float time)
void loaded(int numTimePoints)
void thresholdsUpdated(float min, float mid, float max)
void realtimeColorsAvailable(const QVector< uint32_t > &colorsLh, const QVector< uint32_t > &colorsRh)
static constexpr ShaderMode Holographic
Aggregated GPU resources and render state for the 3-D brain visualization scene.
void setBemHighContrast(bool enabled)
void setSourceColormap(const QString &name)
bool loadMegHelmetSurface(const QString &helmetFilePath)
void setHemiVisible(int hemiIdx, bool visible)
void setSensorFieldTimePoint(int index)
void sourceThresholdsUpdated(float min, float mid, float max)
void setInfoPanelVisible(bool visible)
int stcNumTimePoints() const
bool loadTransformation(const QString &transPath)
bool loadDipoles(const QString &dipPath)
void wheelEvent(QWheelEvent *event) override
bool sensorFieldTimeRange(float &tmin, float &tmax) const
void sourceEstimateLoaded(int numTimePoints)
void setSensorFieldContourVisible(const QString &type, bool visible)
bool megFieldMapOnHeadForTarget(int target) const
void setShaderMode(const QString &mode)
void setNetworkColormap(const QString &name)
int closestSensorFieldIndex(float timeSec) const
bool isRealtimeSensorStreaming() const
void resizeEvent(QResizeEvent *event) override
int closestStcIndex(float timeSec) const
QString bemShaderModeForTarget(int target) const
void keyPressEvent(QKeyEvent *event) override
void setRealtimeLooping(bool enabled)
void setRealtimeInterval(int msec)
void setSensorFieldVisible(const QString &type, bool visible)
int visualizationEditTarget() const
void startRealtimeSensorStreaming(const QString &modality=QStringLiteral("MEG"))
QString overlayModeForTarget(int target) const
void onRowsInserted(const QModelIndex &parent, int first, int last)
void stopRealtimeSensorStreaming()
QString shaderModeForTarget(int target) const
bool objectVisibleForTarget(const QString &object, int target) const
void setRealtimeSensorAverages(int numAvr)
void setSourceThresholds(float min, float mid, float max)
void mouseMoveEvent(QMouseEvent *event) override
void pushRealtimeSourceData(const Eigen::VectorXd &data)
void visualizationEditTargetChanged(int target)
void setVisualizationEditTarget(int target)
void timePointChanged(int index, float time)
void setSourceSpaceVisible(bool visible)
void render(QRhiCommandBuffer *cb) override
void setInitialCameraRotation(const QQuaternion &rotation)
bool loadSensors(const QString &fifPath)
void setVisualizationMode(const QString &mode)
void initialize(QRhiCommandBuffer *cb) override
void setModel(BrainTreeModel *model)
void mouseReleaseEvent(QMouseEvent *event) override
BrainView(QWidget *parent=nullptr)
void setSensorVisible(const QString &type, bool visible)
void setSensorFieldColormap(const QString &name)
void syncBemShadersToBrainShaders()
void setRealtimeSensorLooping(bool enabled)
void setSensorTransEnabled(bool enabled)
void startRealtimeStreaming()
bool loadSourceEstimate(const QString &lhPath, const QString &rhPath)
void stopRealtimeStreaming()
void setViewportCameraPreset(int index, int preset)
void setBemVisible(const QString &name, bool visible)
static QStringList probeEvokedSets(const QString &evokedPath)
void castRay(const QPoint &pos)
void setViewportEnabled(int index, bool enabled)
void setBemShaderMode(const QString &mode)
void mousePressEvent(QMouseEvent *event) override
bool loadSourceSpace(const QString &fwdPath)
void setTimePoint(int index)
QString activeSurfaceForTarget(int target) const
bool isViewportEnabled(int index) const
bool loadNetwork(const CONNECTIVITYLIB::Network &network, const QString &name="Network")
void setActiveSurface(const QString &type)
void sensorFieldTimePointChanged(int index, float time)
bool isRealtimeStreaming() const
void setViewCount(int count)
void setMegHelmetOverride(const QString &path)
int viewportCameraPreset(int index) const
void setDipoleVisible(bool visible)
void setRealtimeSensorInterval(int msec)
void onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
void hoveredRegionChanged(const QString ®ionName)
void setLightingEnabled(bool enabled)
void setNetworkVisible(bool visible)
void sensorFieldLoaded(int numTimePoints, int initialTimePoint=0)
void setMegFieldMapOnHead(bool useHead)
bool loadSensorField(const QString &evokedPath, int aveIndex=0)
void setRealtimeSensorColormap(const QString &name)
void resetMultiViewLayout()
void stcLoadingProgress(int percent, const QString &message)
void pushRealtimeSensorData(const Eigen::VectorXf &data)
void setNetworkThreshold(double threshold)
static Qt::CursorShape cursorForHit(SplitterHit hit)
Coordinate transformation description.
Eigen::Matrix< float, 4, 4, Eigen::DontAlign > trans
BEM surface provides geometry information.