From 3d7c709c40ccedc228cbe39370d909590d34f7c4 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Thu, 25 Apr 2024 14:59:32 -0700 Subject: [PATCH 01/11] ENH: Add show hide menu --- mne_gui_addons/_core.py | 55 +++++++++++++++++++++++++++++-- mne_gui_addons/_ieeg_locate.py | 16 ++------- mne_gui_addons/tests/test_core.py | 4 +++ 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index 6dd5cfe..a656052 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -11,7 +11,7 @@ from functools import partial from qtpy import QtCore -from qtpy.QtCore import Slot, Qt +from qtpy.QtCore import Slot, Signal, Qt from qtpy.QtWidgets import ( QMainWindow, QGridLayout, @@ -21,6 +21,7 @@ QMessageBox, QWidget, QLineEdit, + QComboBox, ) from matplotlib import patheffects @@ -151,6 +152,17 @@ def make_label(name): return label +class ComboBox(QComboBox): + """Dropdown menu that emits a click when popped up.""" + + clicked = Signal() + + def showPopup(self): + """Override show popup method to emit click.""" + self.clicked.emit() + super(ComboBox, self).showPopup() + + class SliceBrowser(QMainWindow): """Navigate between slices of an MRI, CT, etc. image.""" @@ -416,7 +428,7 @@ def _plot_images(self): [1], )[0] rr = apply_trans(self._ras_vox_scan_ras_t, rr) # base image vox -> RAS - self._renderer.mesh( + self._mc_actor, _ = self._renderer.mesh( *rr.T, triangles=tris, color="gray", @@ -424,8 +436,9 @@ def _plot_images(self): reset_camera=False, render=False, ) + self._head_actor = None else: - self._renderer.mesh( + self._head_actor, _ = self._renderer.mesh( *self._head["rr"].T, triangles=self._head["tris"], color="gray", @@ -433,6 +446,7 @@ def _plot_images(self): reset_camera=False, render=False, ) + self._mc_actor = None if self._lh is not None and self._rh is not None and self._base_mr_aligned: self._lh_actor, _ = self._renderer.mesh( *self._lh["rr"].T, @@ -463,6 +477,24 @@ def _configure_status_bar(self, hbox=None): """Make a bar at the bottom with information in it.""" hbox = QHBoxLayout() if hbox is None else hbox + self._show_hide_selector = ComboBox() + + # add title, not selectable + self._show_hide_selector.addItem("Show/Hide") + model = self._show_hide_selector.model() + model.itemFromIndex(model.index(0, 0)).setSelectable(False) + + if self._head_actor is not None: + self._show_hide_selector.addItem("Hide head") + + if self._lh_actor is not None and self._rh_actor is not None: + self._show_hide_selector.addItem("Hide brain") + + if self._mc_actor is not None: + self._show_hide_selector.addItem("Hide rendering") + self._show_hide_selector.currentIndexChanged.connect(self._show_hide) + hbox.addWidget(self._show_hide_selector) + self._intensity_label = QLabel("") # update later hbox.addWidget(self._intensity_label) @@ -538,6 +570,23 @@ def _update_VOX(self, event): if ras is not None: self._set_ras(ras) + def _show_hide(self): + """Show or hide objects in the 3D rendering.""" + text = self._show_hide_selector.currentText() + if text == "Show/Hide": + return + idx = self._show_hide_selector.currentIndex() + show_hide, item = text.split(" ") + actors = dict(head=[self._head_actor], + brain=[self._lh_actor, self._rh_actor], + rendering=[self._mc_actor])[item] + show_hide_opp = "Show" if show_hide == "Hide" else "Hide" + self._show_hide_selector.setItemText(idx, f"{show_hide_opp} {item}") + for actor in actors: + actor.SetVisibility(show_hide == "Show") + self._show_hide_selector.setCurrentIndex(0) # back to title + self._renderer._update() + def _convert_text(self, text, text_kind): text = text.replace("\n", "") vals = text.split(",") diff --git a/mne_gui_addons/_ieeg_locate.py b/mne_gui_addons/_ieeg_locate.py index 764a1b9..8d3cd17 100644 --- a/mne_gui_addons/_ieeg_locate.py +++ b/mne_gui_addons/_ieeg_locate.py @@ -11,7 +11,7 @@ from scipy.ndimage import maximum_filter from qtpy import QtCore, QtGui -from qtpy.QtCore import Slot, Signal +from qtpy.QtCore import Slot from qtpy.QtWidgets import ( QVBoxLayout, QHBoxLayout, @@ -22,10 +22,9 @@ QListView, QSlider, QPushButton, - QComboBox, ) -from ._core import SliceBrowser, _CMAP, _N_COLORS +from ._core import SliceBrowser, ComboBox, _CMAP, _N_COLORS from mne.channels import make_dig_montage from mne.surface import _voxel_neighbors from mne.transforms import apply_trans, _get_trans, invert_transform @@ -43,17 +42,6 @@ _MISSING_PROP_OKAY = 0.25 -class ComboBox(QComboBox): - """Dropdown menu that emits a click when popped up.""" - - clicked = Signal() - - def showPopup(self): - """Override show popup method to emit click.""" - self.clicked.emit() - super(ComboBox, self).showPopup() - - class IntracranialElectrodeLocator(SliceBrowser): """Locate electrode contacts using a coregistered MRI and CT.""" diff --git a/mne_gui_addons/tests/test_core.py b/mne_gui_addons/tests/test_core.py index e61d631..5ee492a 100644 --- a/mne_gui_addons/tests/test_core.py +++ b/mne_gui_addons/tests/test_core.py @@ -56,6 +56,10 @@ def test_slice_browser_display(renderer_interactive_pyvistaqt): with pytest.warns(RuntimeWarning, match="`pial` surface not found"): gui = SliceBrowser(subject=subject, subjects_dir=subjects_dir) + # test show/hide + gui._show_hide_selector.setCurrentIndex(1) # hide + gui._show_hide_selector.setCurrentIndex(1) # show + # test RAS gui._RAS_textbox.setText("10 10 10") gui._RAS_textbox.focusOutEvent(event=None) From 982898ca4e6572c4c840934547eee150d023d580 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Thu, 25 Apr 2024 15:01:35 -0700 Subject: [PATCH 02/11] fix style --- mne_gui_addons/_core.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index a656052..d07a6e5 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -483,7 +483,7 @@ def _configure_status_bar(self, hbox=None): self._show_hide_selector.addItem("Show/Hide") model = self._show_hide_selector.model() model.itemFromIndex(model.index(0, 0)).setSelectable(False) - + if self._head_actor is not None: self._show_hide_selector.addItem("Hide head") @@ -577,9 +577,11 @@ def _show_hide(self): return idx = self._show_hide_selector.currentIndex() show_hide, item = text.split(" ") - actors = dict(head=[self._head_actor], - brain=[self._lh_actor, self._rh_actor], - rendering=[self._mc_actor])[item] + actors = dict( + head=[self._head_actor], + brain=[self._lh_actor, self._rh_actor], + rendering=[self._mc_actor], + )[item] show_hide_opp = "Show" if show_hide == "Hide" else "Hide" self._show_hide_selector.setItemText(idx, f"{show_hide_opp} {item}") for actor in actors: From 9cefaf132ed418e9bf556ece50cc46be91d59ea7 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 29 Apr 2024 12:40:01 -0700 Subject: [PATCH 03/11] run with fixed cis --- .circleci/config.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9f5a8a1..3183153 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,8 +42,7 @@ jobs: - run: name: Get Python running command: | - # TO DO: sphinx-gallery main -> stable on 0.17 release - pip install --upgrade PyQt6!=6.6.0 "PyQt6-Qt6!=6.6.0,!=6.7.0" git+https://github.com/sphinx-gallery/sphinx-gallery.git pydata-sphinx-theme numpydoc scikit-learn nilearn mne-bids autoreject pyvista memory_profiler sphinxcontrib.bibtex sphinxcontrib.youtube darkdetect qdarkstyle + pip install --upgrade PyQt6!=6.6.0 "PyQt6-Qt6!=6.6.0,!=6.7.0" sphinx-gallery pydata-sphinx-theme numpydoc scikit-learn nilearn mne-bids autoreject pyvista memory_profiler sphinxcontrib.bibtex sphinxcontrib.youtube darkdetect qdarkstyle pip install -ve ./mne-python . - run: name: Check Qt From ac6cdf9aba1c07a0c54657adcada602d1e11ba3d Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 29 Apr 2024 14:28:49 -0700 Subject: [PATCH 04/11] consolidate into show/hide menu, add atlas visualization --- mne_gui_addons/_core.py | 121 +++++++++++++++++++++++---------- mne_gui_addons/_ieeg_locate.py | 66 ++++++++---------- 2 files changed, 116 insertions(+), 71 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index d07a6e5..3f61659 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -10,7 +10,7 @@ import numpy as np from functools import partial -from qtpy import QtCore +from qtpy import QtCore, QtGui from qtpy.QtCore import Slot, Signal, Qt from qtpy.QtWidgets import ( QMainWindow, @@ -30,6 +30,7 @@ from matplotlib.patches import Rectangle from matplotlib.colors import LinearSegmentedColormap +from mne import read_freesurfer_lut from mne.viz.backends.renderer import _get_renderer from mne.viz.utils import safe_event from mne.surface import _read_mri_surface, _marching_cubes @@ -185,6 +186,10 @@ def __init__( super(SliceBrowser, self).__init__() self.setAttribute(Qt.WA_DeleteOnClose, True) + atlas_ids, colors = read_freesurfer_lut() + self._fs_lut = {atlas_id: colors[name] for name, atlas_id in atlas_ids.items()} + self._atlas_ids = {val: key for key, val in atlas_ids.items()} + self._verbose = verbose # if bad/None subject, will raise an informative error when loading MRI subject = os.environ.get("SUBJECT") if subject is None else subject @@ -231,25 +236,24 @@ def _configure_ui(self): def _load_image_data(self, base_image=None): """Get image data to display and transforms to/from vox/RAS.""" + self._using_atlas = False if self._subject_dir is None: # if the recon-all is not finished or the CT is not # downsampled to the MRI, the MRI can not be used - self._mr_data = None - self._head = None - self._lh = self._rh = None + self._mr_data = self._head = self._lh = self._rh = None + self._mr_scan_ras_ras_vox_t = None else: - mri_img = ( - "brain" - if op.isfile(op.join(self._subject_dir, "mri", "brain.mgz")) - else "T1" - ) + mr_base_fname = op.join(self._subject_dir, "mri", "{}.mgz") + mr_fname = mr_base_fname.format('brain') if \ + op.isfile(mr_base_fname.format('brain')) else mr_base_fname.format('T1') ( self._mr_data, mr_vox_mri_t, mr_vox_scan_ras_t, mr_ras_vox_scan_ras_t, self._mr_vol_info, - ) = _load_image(op.join(self._subject_dir, "mri", f"{mri_img}.mgz")) + ) = _load_image(mr_fname) + self._mr_scan_ras_ras_vox_t = np.linalg.inv(mr_ras_vox_scan_ras_t) # ready alternate base image if provided, otherwise use brain/T1 self._base_mr_aligned = True @@ -477,23 +481,39 @@ def _configure_status_bar(self, hbox=None): """Make a bar at the bottom with information in it.""" hbox = QHBoxLayout() if hbox is None else hbox - self._show_hide_selector = ComboBox() + self._toggle_show_selector = ComboBox() # add title, not selectable - self._show_hide_selector.addItem("Show/Hide") - model = self._show_hide_selector.model() + self._toggle_show_selector.addItem("Show/Hide") + model = self._toggle_show_selector.model() model.itemFromIndex(model.index(0, 0)).setSelectable(False) + # color differently + color = QtGui.QColor("gray") + brush = QtGui.QBrush(color) + brush.setStyle(QtCore.Qt.SolidPattern) + model.setData(model.index(0, 0), brush, QtCore.Qt.BackgroundRole) + + if self._base_mr_aligned and hasattr(self, '_toggle_show_brain'): + self._toggle_show_selector.addItem("Show brain slices") + self._toggle_show_selector.addItem("Show atlas slices") + + if hasattr(self, "_toggle_show_mip"): + self._toggle_show_selector.addItem("Show max intensity proj") + + if hasattr(self, "_toggle_show_max"): + self._toggle_show_selector.addItem("Show local maxima") if self._head_actor is not None: - self._show_hide_selector.addItem("Hide head") + self._toggle_show_selector.addItem("Hide 3D head") if self._lh_actor is not None and self._rh_actor is not None: - self._show_hide_selector.addItem("Hide brain") + self._toggle_show_selector.addItem("Hide 3D brain") if self._mc_actor is not None: - self._show_hide_selector.addItem("Hide rendering") - self._show_hide_selector.currentIndexChanged.connect(self._show_hide) - hbox.addWidget(self._show_hide_selector) + self._toggle_show_selector.addItem("Hide 3D rendering") + + self._toggle_show_selector.currentIndexChanged.connect(self._toggle_show) + hbox.addWidget(self._toggle_show_selector) self._intensity_label = QLabel("") # update later hbox.addWidget(self._intensity_label) @@ -570,24 +590,51 @@ def _update_VOX(self, event): if ras is not None: self._set_ras(ras) - def _show_hide(self): + def _toggle_show(self): """Show or hide objects in the 3D rendering.""" - text = self._show_hide_selector.currentText() + text = self._toggle_show_selector.currentText() if text == "Show/Hide": return - idx = self._show_hide_selector.currentIndex() - show_hide, item = text.split(" ") - actors = dict( - head=[self._head_actor], - brain=[self._lh_actor, self._rh_actor], - rendering=[self._mc_actor], - )[item] + idx = self._toggle_show_selector.currentIndex() + show_hide, item = text.split(" ")[0], " ".join(text.split(" ")[1:]) show_hide_opp = "Show" if show_hide == "Hide" else "Hide" - self._show_hide_selector.setItemText(idx, f"{show_hide_opp} {item}") - for actor in actors: - actor.SetVisibility(show_hide == "Show") - self._show_hide_selector.setCurrentIndex(0) # back to title - self._renderer._update() + if 'slices' in item: + # atlas shown and brain already on or brain already on and atlas shown + if show_hide == "Show" and "mri" in self._images: + self._toggle_show_brain() + if self._using_atlas: + self._toggle_show_selector.setItemText(1, f"Hide atlas slices") + else: + self._toggle_show_selector.setItemText(0, f"Hide brain slices") + mr_base_fname = op.join(self._subject_dir, "mri", "{}.mgz") + if show_hide == "Show" and "atlas" in item and not self._using_atlas: + if op.isfile(mr_base_fname.format("wmparc")): + self._mr_data = _load_image(mr_base_fname.format("wmparc"))[0] + else: + self._mr_data = _load_image(mr_base_fname.format("aparc+aseg"))[0] + self._using_atlas = True + if show_hide == "Show" and "brain" in item and self._using_atlas: + if op.isfile(mr_base_fname.format("brain")): + self._mr_data = _load_image(mr_base_fname.format("brain"))[0] + else: + self._mr_data = _load_image(mr_base_fname.format("T1"))[0] + self._using_atlas = False + self._toggle_show_brain() + elif item == "max intensity proj": + self._toggle_show_mip() + elif item == "local maxima": + self._toggle_show_max() + else: + actors = { + "3D head": [self._head_actor], + "3D brain": [self._lh_actor, self._rh_actor], + "3D rendering": [self._mc_actor], + }[item] + for actor in actors: + actor.SetVisibility(show_hide == "Show") + self._renderer._update() + self._toggle_show_selector.setCurrentIndex(0) # back to title + self._toggle_show_selector.setItemText(idx, f"{show_hide_opp} {item}") def _convert_text(self, text, text_kind): text = text.replace("\n", "") @@ -769,9 +816,13 @@ def _update_moved(self): self._VOX_textbox.setText( "{:3d}, {:3d}, {:3d}".format(*self._vox.round().astype(int)) ) - self._intensity_label.setText( - "intensity = {:.2f}".format(self._base_data[tuple(self._current_slice)]) - ) + intensity_text = "intensity = {:.2f}".format( + self._base_data[tuple(self._current_slice)]) + if self._using_atlas: + vox = apply_trans(self._mr_scan_ras_ras_vox_t, self._ras).round().astype(int) + label = self._atlas_ids[int(self._mr_data[tuple(vox)])] + intensity_text += f' ({label})' + self._intensity_label.setText(intensity_text) @safe_event def closeEvent(self, event): diff --git a/mne_gui_addons/_ieeg_locate.py b/mne_gui_addons/_ieeg_locate.py index 84d228f..f1df116 100644 --- a/mne_gui_addons/_ieeg_locate.py +++ b/mne_gui_addons/_ieeg_locate.py @@ -45,6 +45,9 @@ class IntracranialElectrodeLocator(SliceBrowser): """Locate electrode contacts using a coregistered MRI and CT.""" + _showing_max_intensity_proj = False + _showing_local_maxima = False + def __init__( self, info, @@ -313,13 +316,6 @@ def _configure_toolbar(self): hbox.addStretch(1) - if self._base_mr_aligned: - self._toggle_brain_button = QPushButton("Show Brain") - self._toggle_brain_button.released.connect(self._toggle_show_brain) - hbox.addWidget(self._toggle_brain_button) - - hbox.addStretch(1) - mark_button = QPushButton("Mark") hbox.addWidget(mark_button) mark_button.released.connect(self.mark_channel) @@ -411,14 +407,6 @@ def _configure_status_bar(self, hbox=None): hbox.addStretch(3) - self._toggle_show_mip_button = QPushButton("Show Max Intensity Proj") - self._toggle_show_mip_button.released.connect(self._toggle_show_mip) - hbox.addWidget(self._toggle_show_mip_button) - - self._toggle_show_max_button = QPushButton("Show Maxima") - self._toggle_show_max_button.released.connect(self._toggle_show_max) - hbox.addWidget(self._toggle_show_max_button) - self._intensity_label = QLabel("") # update later hbox.addWidget(self._intensity_label) @@ -785,7 +773,7 @@ def _update_lines(self, group, only_2D=False): line.remove() self._lines_2D.pop(group) if only_2D: # if not in projection, don't add 2D lines - if self._toggle_show_mip_button.text() == "Show Max Intensity Proj": + if not self._showing_max_intensity_proj: return elif group in self._lines: # if updating 3D, remove first self._renderer.plotter.remove_actor(self._lines[group], render=False) @@ -815,7 +803,7 @@ def _update_lines(self, group, only_2D=False): radius=self._radius * _TUBE_SCALAR, color=_CMAP(group)[:3], )[0] - if self._toggle_show_mip_button.text() == "Hide Max Intensity Proj": + if self._showing_max_intensity_proj: # add 2D lines on each slice plot if in max intensity projection target_vox = apply_trans(self._scan_ras_ras_vox_t, pos[target_idx]) insert_vox = apply_trans( @@ -974,7 +962,7 @@ def _update_ch_images(self, axis=None, draw=False): """Update the channel image(s).""" for axis in range(3) if axis is None else [axis]: self._images["chs"][axis].set_data(self._make_ch_image(axis)) - if self._toggle_show_mip_button.text() == "Hide Max Intensity Proj": + if self._showing_max_intensity_proj: self._images["mip_chs"][axis].set_data( self._make_ch_image(axis, proj=True) ) @@ -997,18 +985,29 @@ def _update_ct_images(self, axis=None, draw=False): if draw: self._draw(axis) + def _get_mr_slice(self, axis): + """Get the current MR slice.""" + mri_data = np.take( + self._mr_data, self._current_slice[axis], axis=axis + ).T + if self._using_atlas: + mri_slice = mri_data.copy().astype(int) + mri_data = np.zeros(mri_slice.shape + (3,), dtype=int) + for i in range(mri_slice.shape[0]): + for j in range(mri_slice.shape[1]): + mri_data[i, j] = self._fs_lut[mri_slice[i, j]][:3] + return mri_data + def _update_mri_images(self, axis=None, draw=False): - """Update the CT image(s).""" + """Update the MR image(s).""" if "mri" in self._images: for axis in range(3) if axis is None else [axis]: - self._images["mri"][axis].set_data( - np.take(self._mr_data, self._current_slice[axis], axis=axis).T - ) + self._images["mri"][axis].set_data(self._get_mr_slice(axis)) if draw: self._draw(axis) def _update_images(self, axis=None, draw=True): - """Update CT and channel images when general changes happen.""" + """Update CT, MR and channel images when general changes happen.""" self._update_ch_images(axis=axis) self._update_mri_images(axis=axis) self._update_ct_images(axis=axis) @@ -1027,7 +1026,7 @@ def _update_ct_scale(self): def _update_radius(self): """Update channel plot radius.""" self._radius = np.round(self._radius_slider.value()).astype(int) - if self._toggle_show_max_button.text() == "Hide Maxima": + if self._showing_local_maxima: self._update_ct_maxima() self._update_ct_images() else: @@ -1077,8 +1076,8 @@ def _update_ct_maxima(self, ct_thresh=0.95): def _toggle_show_mip(self): """Toggle whether the maximum-intensity projection is shown.""" - if self._toggle_show_mip_button.text() == "Show Max Intensity Proj": - self._toggle_show_mip_button.setText("Hide Max Intensity Proj") + self._showing_max_intensity_proj = not self._showing_max_intensity_proj + if self._showing_max_intensity_proj: self._images["mip"] = list() self._images["mip_chs"] = list() ct_min, ct_max = np.nanmin(self._ct_data), np.nanmax(self._ct_data) @@ -1124,15 +1123,14 @@ def _toggle_show_mip(self): img.remove() self._images.pop("mip") self._images.pop("mip_chs") - self._toggle_show_mip_button.setText("Show Max Intensity Proj") for group in set(self._groups.values()): # remove lines self._update_lines(group, only_2D=True) self._draw() def _toggle_show_max(self): """Toggle whether to color local maxima differently.""" - if self._toggle_show_max_button.text() == "Show Maxima": - self._toggle_show_max_button.setText("Hide Maxima") + self._showing_local_maxima = not self._showing_local_maxima + if self._showing_local_maxima: # happens on initiation or if the radius is changed with it off if self._ct_maxima is None: # otherwise don't recompute self._update_ct_maxima() @@ -1157,7 +1155,6 @@ def _toggle_show_max(self): for img in self._images["local_max"]: img.remove() self._images.pop("local_max") - self._toggle_show_max_button.setText("Show Maxima") self._draw() def _toggle_show_brain(self): @@ -1166,19 +1163,16 @@ def _toggle_show_brain(self): for img in self._images["mri"]: img.remove() self._images.pop("mri") - self._toggle_brain_button.setText("Show Brain") else: self._images["mri"] = list() for axis in range(3): - mri_data = np.take( - self._mr_data, self._current_slice[axis], axis=axis - ).T + cmap = None if self._using_atlas else "hot" + mri_data = self._get_mr_slice(axis) self._images["mri"].append( self._figs[axis] .axes[0] - .imshow(mri_data, cmap="hot", aspect="auto", alpha=0.25, zorder=2) + .imshow(mri_data, cmap=cmap, aspect="auto", alpha=0.25, zorder=2) ) - self._toggle_brain_button.setText("Hide Brain") self._draw() def keyPressEvent(self, event): From a7c59dfcaf286ef9b69da7db1ce0d22433c6d837 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 29 Apr 2024 14:30:54 -0700 Subject: [PATCH 05/11] fix style --- mne_gui_addons/_core.py | 24 +++++++++++++++--------- mne_gui_addons/_ieeg_locate.py | 4 +--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index 3f61659..c886e68 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -244,8 +244,11 @@ def _load_image_data(self, base_image=None): self._mr_scan_ras_ras_vox_t = None else: mr_base_fname = op.join(self._subject_dir, "mri", "{}.mgz") - mr_fname = mr_base_fname.format('brain') if \ - op.isfile(mr_base_fname.format('brain')) else mr_base_fname.format('T1') + mr_fname = ( + mr_base_fname.format("brain") + if op.isfile(mr_base_fname.format("brain")) + else mr_base_fname.format("T1") + ) ( self._mr_data, mr_vox_mri_t, @@ -493,7 +496,7 @@ def _configure_status_bar(self, hbox=None): brush.setStyle(QtCore.Qt.SolidPattern) model.setData(model.index(0, 0), brush, QtCore.Qt.BackgroundRole) - if self._base_mr_aligned and hasattr(self, '_toggle_show_brain'): + if self._base_mr_aligned and hasattr(self, "_toggle_show_brain"): self._toggle_show_selector.addItem("Show brain slices") self._toggle_show_selector.addItem("Show atlas slices") @@ -598,14 +601,14 @@ def _toggle_show(self): idx = self._toggle_show_selector.currentIndex() show_hide, item = text.split(" ")[0], " ".join(text.split(" ")[1:]) show_hide_opp = "Show" if show_hide == "Hide" else "Hide" - if 'slices' in item: + if "slices" in item: # atlas shown and brain already on or brain already on and atlas shown if show_hide == "Show" and "mri" in self._images: self._toggle_show_brain() if self._using_atlas: - self._toggle_show_selector.setItemText(1, f"Hide atlas slices") + self._toggle_show_selector.setItemText(1, "Hide atlas slices") else: - self._toggle_show_selector.setItemText(0, f"Hide brain slices") + self._toggle_show_selector.setItemText(0, "Hide brain slices") mr_base_fname = op.join(self._subject_dir, "mri", "{}.mgz") if show_hide == "Show" and "atlas" in item and not self._using_atlas: if op.isfile(mr_base_fname.format("wmparc")): @@ -817,11 +820,14 @@ def _update_moved(self): "{:3d}, {:3d}, {:3d}".format(*self._vox.round().astype(int)) ) intensity_text = "intensity = {:.2f}".format( - self._base_data[tuple(self._current_slice)]) + self._base_data[tuple(self._current_slice)] + ) if self._using_atlas: - vox = apply_trans(self._mr_scan_ras_ras_vox_t, self._ras).round().astype(int) + vox = ( + apply_trans(self._mr_scan_ras_ras_vox_t, self._ras).round().astype(int) + ) label = self._atlas_ids[int(self._mr_data[tuple(vox)])] - intensity_text += f' ({label})' + intensity_text += f" ({label})" self._intensity_label.setText(intensity_text) @safe_event diff --git a/mne_gui_addons/_ieeg_locate.py b/mne_gui_addons/_ieeg_locate.py index f1df116..7c3d707 100644 --- a/mne_gui_addons/_ieeg_locate.py +++ b/mne_gui_addons/_ieeg_locate.py @@ -987,9 +987,7 @@ def _update_ct_images(self, axis=None, draw=False): def _get_mr_slice(self, axis): """Get the current MR slice.""" - mri_data = np.take( - self._mr_data, self._current_slice[axis], axis=axis - ).T + mri_data = np.take(self._mr_data, self._current_slice[axis], axis=axis).T if self._using_atlas: mri_slice = mri_data.copy().astype(int) mri_data = np.zeros(mri_slice.shape + (3,), dtype=int) From 41e9a2d7390bc57454375de95f39f02c0c56fe2a Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 29 Apr 2024 14:36:25 -0700 Subject: [PATCH 06/11] fix edge case, fix tests --- mne_gui_addons/_core.py | 1 + mne_gui_addons/tests/test_core.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index c886e68..5cb674f 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -623,6 +623,7 @@ def _toggle_show(self): self._mr_data = _load_image(mr_base_fname.format("T1"))[0] self._using_atlas = False self._toggle_show_brain() + self._update_moved() elif item == "max intensity proj": self._toggle_show_mip() elif item == "local maxima": diff --git a/mne_gui_addons/tests/test_core.py b/mne_gui_addons/tests/test_core.py index 5ee492a..10bbf00 100644 --- a/mne_gui_addons/tests/test_core.py +++ b/mne_gui_addons/tests/test_core.py @@ -57,8 +57,8 @@ def test_slice_browser_display(renderer_interactive_pyvistaqt): gui = SliceBrowser(subject=subject, subjects_dir=subjects_dir) # test show/hide - gui._show_hide_selector.setCurrentIndex(1) # hide - gui._show_hide_selector.setCurrentIndex(1) # show + gui._toggle_show_selector.setCurrentIndex(1) # hide + gui._toggle_show_selector.setCurrentIndex(1) # show # test RAS gui._RAS_textbox.setText("10 10 10") From ead7e47dc2fc387094b62e68b3bba550dd409c5a Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 29 Apr 2024 15:02:45 -0700 Subject: [PATCH 07/11] move menu to top, move autocomplete button --- mne_gui_addons/_core.py | 18 ++++++++++++++++-- mne_gui_addons/_ieeg_locate.py | 21 +++++++++++---------- mne_gui_addons/_segment.py | 4 ++-- mne_gui_addons/_vol_stc.py | 9 ++++----- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index 5cb674f..37ac09e 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -22,6 +22,7 @@ QWidget, QLineEdit, QComboBox, + QPushButton, ) from matplotlib import patheffects @@ -220,6 +221,7 @@ def __init__( self._configure_ui() def _configure_ui(self): + toolbar = self._configure_toolbar() bottom_hbox = self._configure_status_bar() # Put everything together @@ -227,6 +229,7 @@ def _configure_ui(self): plot_ch_hbox.addLayout(self._plt_grid) main_vbox = QVBoxLayout() + main_vbox.addLayout(toolbar) main_vbox.addLayout(plot_ch_hbox) main_vbox.addLayout(bottom_hbox) @@ -480,10 +483,16 @@ def _plot_images(self): self._draw() self._renderer._update() - def _configure_status_bar(self, hbox=None): - """Make a bar at the bottom with information in it.""" + def _configure_toolbar(self, hbox=None): + """Make a bar at the top with tools on it.""" hbox = QHBoxLayout() if hbox is None else hbox + help_button = QPushButton("Help") + help_button.released.connect(self._show_help) + hbox.addWidget(help_button) + + hbox.addStretch(6) + self._toggle_show_selector = ComboBox() # add title, not selectable @@ -517,6 +526,11 @@ def _configure_status_bar(self, hbox=None): self._toggle_show_selector.currentIndexChanged.connect(self._toggle_show) hbox.addWidget(self._toggle_show_selector) + return hbox + + def _configure_status_bar(self, hbox=None): + """Make a bar at the bottom with information in it.""" + hbox = QHBoxLayout() if hbox is None else hbox self._intensity_label = QLabel("") # update later hbox.addWidget(self._intensity_label) diff --git a/mne_gui_addons/_ieeg_locate.py b/mne_gui_addons/_ieeg_locate.py index 7c3d707..33e9c5b 100644 --- a/mne_gui_addons/_ieeg_locate.py +++ b/mne_gui_addons/_ieeg_locate.py @@ -301,11 +301,10 @@ def _configure_toolbar(self): """Make a bar with buttons for user interactions.""" hbox = QHBoxLayout() - help_button = QPushButton("Help") - help_button.released.connect(self._show_help) - hbox.addWidget(help_button) - - hbox.addStretch(8) + # add help and show/hide + super(IntracranialElectrodeLocator, self)._configure_toolbar(hbox=hbox) + + hbox.addStretch(1) hbox.addWidget(QLabel("Snap to Center")) self._snap_button = QPushButton("Off") @@ -316,6 +315,12 @@ def _configure_toolbar(self): hbox.addStretch(1) + self._auto_complete_button = QPushButton("Auto Complete") + self._auto_complete_button.released.connect(self._auto_mark_group) + hbox.addWidget(self._auto_complete_button) + + hbox.addStretch(1) + mark_button = QPushButton("Mark") hbox.addWidget(mark_button) mark_button.released.connect(self.mark_channel) @@ -401,11 +406,7 @@ def make_slider(smin, smax, sval, sfun=None): def _configure_status_bar(self, hbox=None): hbox = QHBoxLayout() if hbox is None else hbox - self._auto_complete_button = QPushButton("Auto Complete") - self._auto_complete_button.released.connect(self._auto_mark_group) - hbox.addWidget(self._auto_complete_button) - - hbox.addStretch(3) + hbox.addStretch(1) self._intensity_label = QLabel("") # update later hbox.addWidget(self._intensity_label) diff --git a/mne_gui_addons/_segment.py b/mne_gui_addons/_segment.py index 0a79761..d6c06fa 100644 --- a/mne_gui_addons/_segment.py +++ b/mne_gui_addons/_segment.py @@ -112,7 +112,7 @@ def __init__( self.show() def _configure_ui(self): - # toolbar = self._configure_toolbar() + toolbar = self._configure_toolbar() slider_bar = self._configure_sliders() status_bar = self._configure_status_bar() @@ -120,7 +120,7 @@ def _configure_ui(self): plot_layout.addLayout(self._plt_grid) main_vbox = QVBoxLayout() - # main_vbox.addLayout(toolbar) + main_vbox.addLayout(toolbar) main_vbox.addLayout(slider_bar) main_vbox.addLayout(plot_layout) main_vbox.addLayout(status_bar) diff --git a/mne_gui_addons/_vol_stc.py b/mne_gui_addons/_vol_stc.py index 5a704b3..bb5053b 100644 --- a/mne_gui_addons/_vol_stc.py +++ b/mne_gui_addons/_vol_stc.py @@ -582,11 +582,10 @@ def _configure_toolbar(self): """Make a bar with buttons for user interactions.""" hbox = QHBoxLayout() - help_button = QPushButton("Help") - help_button.released.connect(self._show_help) - hbox.addWidget(help_button) - - hbox.addStretch(8) + # add help and show/hide + super(VolSourceEstimateViewer, self)._configure_toolbar(hbox=hbox) + + hbox.addStretch(1) if self._data.shape[0] > 1: self._epoch_selector = QComboBox() From 1f6ef67a6b89e09a5a7704650989b0897b4d6dbb Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Mon, 29 Apr 2024 17:05:47 -0700 Subject: [PATCH 08/11] fix style --- mne_gui_addons/_ieeg_locate.py | 2 +- mne_gui_addons/_vol_stc.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mne_gui_addons/_ieeg_locate.py b/mne_gui_addons/_ieeg_locate.py index 33e9c5b..6be3bbb 100644 --- a/mne_gui_addons/_ieeg_locate.py +++ b/mne_gui_addons/_ieeg_locate.py @@ -303,7 +303,7 @@ def _configure_toolbar(self): # add help and show/hide super(IntracranialElectrodeLocator, self)._configure_toolbar(hbox=hbox) - + hbox.addStretch(1) hbox.addWidget(QLabel("Snap to Center")) diff --git a/mne_gui_addons/_vol_stc.py b/mne_gui_addons/_vol_stc.py index bb5053b..c3da680 100644 --- a/mne_gui_addons/_vol_stc.py +++ b/mne_gui_addons/_vol_stc.py @@ -584,7 +584,7 @@ def _configure_toolbar(self): # add help and show/hide super(VolSourceEstimateViewer, self)._configure_toolbar(hbox=hbox) - + hbox.addStretch(1) if self._data.shape[0] > 1: From 2f0dce3154f916806e9d4c8e1dc80b0a6de86eda Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Tue, 30 Apr 2024 15:45:58 -0700 Subject: [PATCH 09/11] fix style --- mne_gui_addons/_ieeg_locate.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/mne_gui_addons/_ieeg_locate.py b/mne_gui_addons/_ieeg_locate.py index c372abf..f7f5664 100644 --- a/mne_gui_addons/_ieeg_locate.py +++ b/mne_gui_addons/_ieeg_locate.py @@ -993,9 +993,7 @@ def _update_ct_images(self, axis=None, draw=False): def _get_mr_slice(self, axis): """Get the current MR slice.""" - mri_data = self._mr_data[ - (slice(None),) * axis + (self._current_slice[axis],) - ].T + mri_data = self._mr_data[(slice(None),) * axis + (self._current_slice[axis],)].T if self._using_atlas: mri_slice = mri_data.copy().astype(int) mri_data = np.zeros(mri_slice.shape + (3,), dtype=int) @@ -1171,13 +1169,12 @@ def _toggle_show_brain(self): else: self._images["mri"] = list() for axis in range(3): - cmap = None if self._using_atlas else "hot" self._images["mri"].append( self._figs[axis] .axes[0] .imshow( self._get_mr_slice(axis), - cmap="hot", + cmap=None if self._using_atlas else "hot", aspect="auto", alpha=0.25, zorder=2, From 8f30904e52df668a2b20a0c27c63e1edb2d600d4 Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Tue, 30 Apr 2024 15:48:02 -0700 Subject: [PATCH 10/11] fix bug --- mne_gui_addons/_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index 57b6451..dd5e3f8 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -652,8 +652,8 @@ def _toggle_show(self): for actor in actors: actor.SetVisibility(show_hide == "Show") self._renderer._update() - self._toggle_show_selector.setCurrentIndex(0) # back to title self._toggle_show_selector.setItemText(idx, f"{show_hide_opp} {item}") + self._toggle_show_selector.setCurrentIndex(0) # back to title def _convert_text(self, text, text_kind): text = text.replace("\n", "") From 6aafa8f7012f26cdb9855a31060a9620231b2b7b Mon Sep 17 00:00:00 2001 From: Alex Rockhill Date: Tue, 30 Apr 2024 16:03:05 -0700 Subject: [PATCH 11/11] small toggling name bug --- mne_gui_addons/_core.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mne_gui_addons/_core.py b/mne_gui_addons/_core.py index dd5e3f8..bb9ed4d 100644 --- a/mne_gui_addons/_core.py +++ b/mne_gui_addons/_core.py @@ -619,11 +619,9 @@ def _toggle_show(self): if "slices" in item: # atlas shown and brain already on or brain already on and atlas shown if show_hide == "Show" and "mri" in self._images: + idx2, item2 = (2, "atlas") if self._using_atlas else (1, "brain") + self._toggle_show_selector.setItemText(idx2, f"Show {item2} slices") self._toggle_show_brain() - if self._using_atlas: - self._toggle_show_selector.setItemText(1, "Hide atlas slices") - else: - self._toggle_show_selector.setItemText(0, "Hide brain slices") mr_base_fname = op.join(self._subject_dir, "mri", "{}.mgz") if show_hide == "Show" and "atlas" in item and not self._using_atlas: if op.isfile(mr_base_fname.format("wmparc")):