From 9c09a497aaecb0398a4d4e46309307a8e9770da3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 10:41:15 +0200 Subject: [PATCH 001/225] add gui --- mne/coreg.py | 2 +- mne/viz/__init__.py | 1 + mne/viz/_coreg/__init__.py | 1 + mne/viz/_coreg/_coreg.py | 161 ++++++++++++++++++++++++++++++++++ mne/viz/backends/_abstract.py | 18 +++- mne/viz/backends/_notebook.py | 58 +++++++++++- mne/viz/backends/_qt.py | 57 ++++++++++-- 7 files changed, 287 insertions(+), 11 deletions(-) create mode 100644 mne/viz/_coreg/__init__.py create mode 100644 mne/viz/_coreg/_coreg.py diff --git a/mne/coreg.py b/mne/coreg.py index 60a852062b3..990e66fae8d 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -26,7 +26,6 @@ # namespace, too) from ._freesurfer import (_read_mri_info, get_mni_fiducials, # noqa: F401 estimate_head_mri_t) # noqa: F401 -from .label import read_label, Label from .source_space import (add_source_space_distances, read_source_spaces, # noqa: E501,F401 write_source_spaces) from .surface import (read_surface, write_surface, _normalize_vectors, @@ -897,6 +896,7 @@ def scale_labels(subject_to, pattern=None, overwrite=False, subject_from=None, subjects_dir : None | str Override the SUBJECTS_DIR environment variable. """ + from .label import read_label, Label subjects_dir, subject_from, scale, _ = _scale_params( subject_to, subject_from, scale, subjects_dir) diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index 0c5cf1ff919..c6ded4a1a40 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -33,3 +33,4 @@ get_brain_class) from . import backends from ._brain import Brain +from ._coreg import CoregistrationUI diff --git a/mne/viz/_coreg/__init__.py b/mne/viz/_coreg/__init__.py new file mode 100644 index 00000000000..5f6e26049de --- /dev/null +++ b/mne/viz/_coreg/__init__.py @@ -0,0 +1 @@ +from ._coreg import CoregistrationUI diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py new file mode 100644 index 00000000000..653e619e457 --- /dev/null +++ b/mne/viz/_coreg/_coreg.py @@ -0,0 +1,161 @@ +from ...coreg import Coregistration +from ...viz import plot_alignment + + +class CoregistrationUI(object): + def __init__(self, info, subject, subjects_dir, fids='auto'): + from ..backends.renderer import _get_renderer + self._info = info + self._subject = subject + self._subjects_dir = subjects_dir + self._fids = fids + + self._widgets = dict() + self._coreg = Coregistration(info, subject, subjects_dir, fids) + self._renderer = _get_renderer() + self._renderer._window_close_connect(self._clean) + self._configure_dock() + self._renderer.show() + + self._plot() + + def _plot(self): + plot_alignment(self._info, trans=self._coreg.trans, + subject=self._subject, + subjects_dir=self._subjects_dir, + surfaces=dict(head=0.4), + dig=True, eeg=[], meg=False, + coord_frame='meg', fig=self._renderer.figure) + + def _configure_dock(self): + def noop(x): + del x + + self._renderer._dock_initialize(name="Parameters", area="left") + layout = self._renderer._dock_add_group_box("MRI Subject") + self._widgets["subjects_dir"] = self._renderer._dock_add_file_button( + name="subjects_dir", + desc="Subjects Directory", + func=noop, + layout=layout, + ) + self._widgets["subject"] = self._renderer._dock_add_combo_box( + name="Subject", + value="sample", + rng=["sample"], + callback=noop, + compact=True, + layout=layout + ) + + layout = self._renderer._dock_add_group_box("MRI Fiducials") + hlayout = self._renderer._dock_add_layout(vertical=False) + self._widgets["show_hsp"] = self._renderer._dock_add_check_box( + name="Show Head Shape Points", + value=False, + callback=noop, + layout=layout + ) + digs = ["LPA", "Nasion", "RPA"] + self._renderer._dock_add_radio_buttons( + value=digs[0], + rng=digs, + callback=noop, + layout=layout, + ) + for coord in ("X", "Y", "Z"): + name = f"dig_{coord}" + self._widgets[name] = self._renderer._dock_add_spin_box( + name=coord, + value=0., + rng=[0., 1.], + callback=noop, + compact=True, + double=True, + layout=hlayout + ) + self._renderer._layout_add_widget(layout, hlayout) + + layout = self._renderer._dock_add_group_box("Digitization Source") + # self._widgets["fids_file"] = self._renderer._dock_file_button() + self._widgets["grow_hair"] = self._renderer._dock_add_spin_box( + name="Grow Hair", + value=0.0, + rng=[0.0, 10.0], + callback=noop, + layout=layout, + ) + hlayout = self._renderer._dock_add_layout(vertical=False) + self._widgets["omit_distance"] = self._renderer._dock_add_spin_box( + name="Omit Distance", + value=0.0, + rng=[0.0, 10.0], + callback=noop, + layout=hlayout, + ) + self._widgets["omit"] = self._renderer._dock_add_button( + name="Omit", + callback=noop, + layout=hlayout, + ) + self._renderer._layout_add_widget(layout, hlayout) + self._renderer._dock_add_stretch() + + self._renderer._dock_initialize( + name="Fitting Parameters", area="right") + layout = self._renderer._dock_add_group_box("Scaling") + self._widgets["scaling_mode"] = self._renderer._dock_add_combo_box( + name="Scaling Mode", + value="0", + rng=["0", "1", "3"], + callback=noop, + compact=True, + layout=layout, + ) + hlayout = self._renderer._dock_add_group_box( + name="Scaling Parameters", + layout=layout + ) + for coord in ("X", "Y", "Z"): + name = f"scaling_{coord}" + self._widgets[name] = self._renderer._dock_add_spin_box( + name=coord, + value=0., + rng=[0., 1.], + callback=noop, + compact=True, + double=True, + layout=hlayout + ) + + hlayout = self._renderer._dock_add_group_box( + "Translation (t) and Rotation (r)") + for mode in ("t", "r"): + for coord in ("X", "Y", "Z"): + name = f"{mode}{coord}" + self._widgets[name] = self._renderer._dock_add_spin_box( + name=name, + value=0., + rng=[0., 1.], + callback=noop, + compact=True, + double=True, + layout=hlayout + ) + hlayout = self._renderer._dock_add_layout(vertical=False) + self._renderer._dock_add_button( + name="Fit Fiducials", + callback=noop, + layout=hlayout, + ) + self._renderer._dock_add_button( + name="Fit ICP", + callback=noop, + layout=hlayout, + ) + layout = self._renderer._dock_layout + self._renderer._layout_add_widget(layout, hlayout) + self._renderer._dock_add_stretch() + + def _clean(self): + self._renderer = None diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index 11f4c79ecf1..b49fb3cf9b3 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -488,7 +488,8 @@ def _tool_bar_set_theme(self, theme): class _AbstractDock(ABC): @abstractmethod - def _dock_initialize(self, window=None): + def _dock_initialize(self, window=None, name="Controls", + area="left"): pass @abstractmethod @@ -504,7 +505,7 @@ def _dock_hide(self): pass @abstractmethod - def _dock_add_stretch(self, layout): + def _dock_add_stretch(self, layout=None): pass @abstractmethod @@ -528,6 +529,10 @@ def _dock_add_slider(self, name, value, rng, callback, compact=True, double=False, layout=None): pass + @abstractmethod + def _dock_add_check_box(self, name, value, callback, layout=None): + pass + @abstractmethod def _dock_add_spin_box(self, name, value, rng, callback, compact=True, double=True, layout=None): @@ -538,10 +543,19 @@ def _dock_add_combo_box(self, name, value, rng, callback, compact=True, layout=None): pass + @abstractmethod + def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, + layout=None): + pass + @abstractmethod def _dock_add_group_box(self, name, layout=None): pass + @abstractmethod + def _dock_add_file_button(self, name, desc, func, layout=None): + pass + class _AbstractMenuBar(ABC): @abstractmethod diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 5eb2ae10bdc..42f2eb7f3c5 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -11,7 +11,7 @@ from IPython.display import display from ipywidgets import (Button, Dropdown, FloatSlider, FloatText, HBox, IntSlider, IntText, Text, VBox, IntProgress, Play, - jsdlink) + Checkbox, RadioButtons, jsdlink) from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar, _AbstractStatusBar, _AbstractLayout, _AbstractWidget, @@ -40,8 +40,12 @@ def _layout_add_widget(self, layout, widget, stretch=0): class _IpyDock(_AbstractDock, _IpyLayout): - def _dock_initialize(self, window=None): + def _dock_initialize(self, window=None, name="Controls", + area="left"): self._dock_width = 300 + if hasattr(self, "_dock") and hasattr(self, "_dock_layout"): + self._dock2 = self._dock + self._dock_layout2 = self._dock_layout self._dock = self._dock_layout = VBox() self._dock.layout.width = f"{self._dock_width}px" self._layout_initialize(self._dock_width) @@ -55,7 +59,7 @@ def _dock_show(self): def _dock_hide(self): self._dock_layout.layout.visibility = "hidden" - def _dock_add_stretch(self, layout): + def _dock_add_stretch(self, layout=None): pass def _dock_add_layout(self, vertical=True): @@ -97,6 +101,17 @@ def _dock_add_slider(self, name, value, rng, callback, self._layout_add_widget(layout, widget) return _IpyWidget(widget) + def _dock_add_check_box(self, name, value, callback, layout=None): + layout = self._dock_layout if layout is None else layout + widget = Checkbox( + value=value, + description=name, + disabled=False + ) + widget.observe(_generate_callback(callback), names='value') + self._layout_add_widget(layout, widget) + return _IpyWidget(widget) + def _dock_add_spin_box(self, name, value, rng, callback, compact=True, double=True, layout=None): layout = self._dock_named_layout(name, layout, compact) @@ -122,12 +137,47 @@ def _dock_add_combo_box(self, name, value, rng, self._layout_add_widget(layout, widget) return _IpyWidget(widget) + def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, + layout=None): + # XXX: vertical=False is not supported yet + layout = self._dock_layout if layout is None else layout + widget = RadioButtons( + options=rng, + value=value, + disabled=False, + ) + self._layout_add_widget(layout, widget) + return _IpyWidget(widget) + def _dock_add_group_box(self, name, layout=None): layout = self._dock_layout if layout is None else layout hlayout = VBox() self._layout_add_widget(layout, hlayout) return hlayout + def _dock_add_text(self, name, value, placeholder, layout=None): + layout = self._dock_layout if layout is None else layout + widget = Text(value=value, placeholder=placeholder) + self._layout_add_widget(layout, widget) + + def _dock_add_file_button(self, name, desc, func, layout=None): + layout = self._dock_layout if layout is None else layout + + def callback(): + fname = self.actions[f"{name}_field"].value + func(None if len(fname) == 0 else fname) + self._dock_add_text( + name=f"{name}_field", + value=None, + placeholder="Type a file name", + layout=layout, + ) + self._dock_add_button( + name=name, + callback=callback, + layout=layout, + ) + def _generate_callback(callback, to_float=False): def func(data): @@ -368,6 +418,8 @@ def show(self): # main widget if self._dock is None: main_widget = viewer + elif hasattr(self, "_dock2"): + main_widget = HBox([self._dock2, viewer, self._dock]) else: main_widget = HBox([self._dock, viewer]) display(main_widget) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 79bffa17195..36f5ce83dd5 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -16,7 +16,8 @@ QHBoxLayout, QLabel, QToolButton, QMenuBar, QSlider, QSpinBox, QVBoxLayout, QWidget, QSizePolicy, QScrollArea, QStyle, QProgressBar, - QStyleOptionSlider, QLayout) + QStyleOptionSlider, QLayout, QCheckBox, + QButtonGroup, QRadioButton) from ._pyvista import _PyVistaRenderer from ._pyvista import (_close_all, _close_3d_figure, _check_3d_figure, # noqa: F401,E501 analysis:ignore @@ -41,11 +42,17 @@ def _layout_add_widget(self, layout, widget, stretch=0): class _QtDock(_AbstractDock, _QtLayout): - def _dock_initialize(self, window=None): + def _dock_initialize(self, window=None, name="Controls", + area="left"): window = self._window if window is None else window + qt_area = Qt.LeftDockWidgetArea if area == "left" \ + else Qt.RightDockWidgetArea self._dock, self._dock_layout = _create_dock_widget( - self._window, "Controls", Qt.LeftDockWidgetArea) - window.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) + self._window, name, qt_area) + if area == "left": + window.setCorner(Qt.BottomLeftCorner, Qt.LeftDockWidgetArea) + else: + window.setCorner(Qt.BottomRightCorner, Qt.RightDockWidgetArea) def _dock_finalize(self): self._dock.setMinimumSize(self._dock.sizeHint().width(), 0) @@ -57,7 +64,8 @@ def _dock_show(self): def _dock_hide(self): self._dock.hide() - def _dock_add_stretch(self, layout): + def _dock_add_stretch(self, layout=None): + layout = self._dock_layout if layout is None else layout layout.addStretch() def _dock_add_layout(self, vertical=True): @@ -106,6 +114,14 @@ def _dock_add_slider(self, name, value, rng, callback, self._layout_add_widget(layout, widget) return _QtWidget(widget) + def _dock_add_check_box(self, name, value, callback, layout=None): + layout = self._dock_layout if layout is None else layout + widget = QCheckBox(name) + widget.setCheckState(value) + widget.stateChanged.connect(callback) + self._layout_add_widget(layout, widget) + return _QtWidget(widget) + def _dock_add_spin_box(self, name, value, rng, callback, compact=True, double=True, layout=None): layout = self._dock_named_layout(name, layout, compact) @@ -134,6 +150,22 @@ def _dock_add_combo_box(self, name, value, rng, self._layout_add_widget(layout, widget) return _QtWidget(widget) + def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, + layout=None): + layout = self._dock_layout if layout is None else layout + group_layout = QVBoxLayout() if vertical else QHBoxLayout() + group = QButtonGroup() + for val in rng: + button = QRadioButton(val) + button.toggled.connect(callback) + if val == value: + button.setChecked(True) + group.addButton(button) + self._layout_add_widget(group_layout, button) + self._layout_add_widget(layout, group_layout) + # XXX: the following should be wrapped by _QtWidget + return group.buttons() + def _dock_add_group_box(self, name, layout=None): layout = self._dock_layout if layout is None else layout hlayout = QVBoxLayout() @@ -142,6 +174,21 @@ def _dock_add_group_box(self, name, layout=None): self._layout_add_widget(layout, widget) return hlayout + def _dock_add_file_button(self, name, desc, func, layout=None): + layout = self._dock_layout if layout is None else layout + + def callback(): + return FileDialog( + self.plotter.app_window, + callback=func, + ) + + return self._dock_add_button( + name=name, + callback=callback, + layout=layout, + ) + class QFloatSlider(QSlider): """Slider that handles float values.""" From 3683ba2a833e47d0f02d0d0a56f01936e4497660 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 10:47:01 +0200 Subject: [PATCH 002/225] Do not trigger CIs [ci skip] From 74d6973393ee6db9e47d701739e69436948ee508 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 13:45:35 +0200 Subject: [PATCH 003/225] add widgets [ci skip] --- mne/viz/_coreg/_coreg.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 653e619e457..ccb419c92ae 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -49,20 +49,35 @@ def noop(x): ) layout = self._renderer._dock_add_group_box("MRI Fiducials") - hlayout = self._renderer._dock_add_layout(vertical=False) + digs_states = ["Lock", "Edit"] + self._renderer._dock_add_radio_buttons( + value=digs_states[0], + rng=digs_states, + callback=noop, + vertical=False, + layout=layout, + ) self._widgets["show_hsp"] = self._renderer._dock_add_check_box( name="Show Head Shape Points", value=False, callback=noop, layout=layout ) + self._widgets["fid_file"] = self._renderer._dock_add_file_button( + name="fid_file", + desc="fid_file", + func=noop, + layout=layout, + ) digs = ["LPA", "Nasion", "RPA"] self._renderer._dock_add_radio_buttons( value=digs[0], rng=digs, callback=noop, + vertical=False, layout=layout, ) + hlayout = self._renderer._dock_add_layout(vertical=False) for coord in ("X", "Y", "Z"): name = f"dig_{coord}" self._widgets[name] = self._renderer._dock_add_spin_box( @@ -77,7 +92,12 @@ def noop(x): self._renderer._layout_add_widget(layout, hlayout) layout = self._renderer._dock_add_group_box("Digitization Source") - # self._widgets["fids_file"] = self._renderer._dock_file_button() + self._widgets["info_file"] = self._renderer._dock_add_file_button( + name="info_file", + desc="info_file", + func=noop, + layout=layout, + ) self._widgets["grow_hair"] = self._renderer._dock_add_spin_box( name="Grow Hair", value=0.0, @@ -117,9 +137,9 @@ def noop(x): layout=layout ) for coord in ("X", "Y", "Z"): - name = f"scaling_{coord}" + name = f"s{coord}" self._widgets[name] = self._renderer._dock_add_spin_box( - name=coord, + name=name, value=0., rng=[0., 1.], callback=noop, From 21fd559f3d6c0a284ba94a52f9aea33cb989c38b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 14:02:01 +0200 Subject: [PATCH 004/225] improve dock file button [ci skip] --- mne/viz/backends/_abstract.py | 7 ++++++- mne/viz/backends/_notebook.py | 12 ++++++++---- mne/viz/backends/_qt.py | 24 ++++++++++++++++++++---- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index b49fb3cf9b3..29199cc931c 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -553,7 +553,12 @@ def _dock_add_group_box(self, name, layout=None): pass @abstractmethod - def _dock_add_file_button(self, name, desc, func, layout=None): + def _dock_add_text(self, name, value, placeholder, layout=None): + pass + + @abstractmethod + def _dock_add_file_button(self, name, desc, func, + placeholder="Type a file name", layout=None): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 42f2eb7f3c5..2a601ca2be6 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -159,24 +159,28 @@ def _dock_add_text(self, name, value, placeholder, layout=None): layout = self._dock_layout if layout is None else layout widget = Text(value=value, placeholder=placeholder) self._layout_add_widget(layout, widget) + return _IpyWidget(widget) - def _dock_add_file_button(self, name, desc, func, layout=None): + def _dock_add_file_button(self, name, desc, func, + placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout def callback(): fname = self.actions[f"{name}_field"].value func(None if len(fname) == 0 else fname) + hlayout = self._dock_add_layout(vertical=False) self._dock_add_text( name=f"{name}_field", value=None, - placeholder="Type a file name", - layout=layout, + placeholder=placeholder, + layout=hlayout, ) self._dock_add_button( name=name, callback=callback, - layout=layout, + layout=hlayout, ) + self._layout_add_widget(layout, hlayout) def _generate_callback(callback, to_float=False): diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 36f5ce83dd5..0e5820dbd7e 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -17,7 +17,7 @@ QSlider, QSpinBox, QVBoxLayout, QWidget, QSizePolicy, QScrollArea, QStyle, QProgressBar, QStyleOptionSlider, QLayout, QCheckBox, - QButtonGroup, QRadioButton) + QButtonGroup, QRadioButton, QLineEdit) from ._pyvista import _PyVistaRenderer from ._pyvista import (_close_all, _close_3d_figure, _check_3d_figure, # noqa: F401,E501 analysis:ignore @@ -174,7 +174,15 @@ def _dock_add_group_box(self, name, layout=None): self._layout_add_widget(layout, widget) return hlayout - def _dock_add_file_button(self, name, desc, func, layout=None): + def _dock_add_text(self, name, value, placeholder, layout=None): + layout = self._dock_layout if layout is None else layout + widget = QLineEdit(value) + widget.setPlaceholderText(placeholder) + self._layout_add_widget(layout, widget) + return _QtWidget(widget) + + def _dock_add_file_button(self, name, desc, func, + placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout def callback(): @@ -183,11 +191,19 @@ def callback(): callback=func, ) - return self._dock_add_button( + hlayout = self._dock_add_layout(vertical=False) + self._dock_add_text( + name=f"{name}_field", + value=None, + placeholder=placeholder, + layout=hlayout, + ) + self._dock_add_button( name=name, callback=callback, - layout=layout, + layout=hlayout, ) + self._layout_add_widget(layout, hlayout) class QFloatSlider(QSlider): From df55c5d442edb5b2829b9d6fca9167a161f8473d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 14:03:54 +0200 Subject: [PATCH 005/225] comment --- mne/viz/backends/_notebook.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 2a601ca2be6..89bfdea82d1 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -43,6 +43,7 @@ class _IpyDock(_AbstractDock, _IpyLayout): def _dock_initialize(self, window=None, name="Controls", area="left"): self._dock_width = 300 + # XXX: this can be improved if hasattr(self, "_dock") and hasattr(self, "_dock_layout"): self._dock2 = self._dock self._dock_layout2 = self._dock_layout @@ -422,6 +423,7 @@ def show(self): # main widget if self._dock is None: main_widget = viewer + # XXX: this can be improved elif hasattr(self, "_dock2"): main_widget = HBox([self._dock2, viewer, self._dock]) else: From 7f8b474c28b8555f50c0ec23e9a57ce3a8161889 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 14:11:05 +0200 Subject: [PATCH 006/225] update desc --- mne/viz/_coreg/_coreg.py | 9 ++++++--- mne/viz/backends/_notebook.py | 2 +- mne/viz/backends/_qt.py | 2 +- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ccb419c92ae..72634e878b6 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -35,8 +35,9 @@ def noop(x): layout = self._renderer._dock_add_group_box("MRI Subject") self._widgets["subjects_dir"] = self._renderer._dock_add_file_button( name="subjects_dir", - desc="Subjects Directory", + desc="Load", func=noop, + placeholder="Subjects Directory", layout=layout, ) self._widgets["subject"] = self._renderer._dock_add_combo_box( @@ -65,8 +66,9 @@ def noop(x): ) self._widgets["fid_file"] = self._renderer._dock_add_file_button( name="fid_file", - desc="fid_file", + desc="Load", func=noop, + placeholder="Path to the fiducials file", layout=layout, ) digs = ["LPA", "Nasion", "RPA"] @@ -94,8 +96,9 @@ def noop(x): layout = self._renderer._dock_add_group_box("Digitization Source") self._widgets["info_file"] = self._renderer._dock_add_file_button( name="info_file", - desc="info_file", + desc="Load", func=noop, + placeholder="Path to the info file", layout=layout, ) self._widgets["grow_hair"] = self._renderer._dock_add_spin_box( diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 89bfdea82d1..eb24b077ce7 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -177,7 +177,7 @@ def callback(): layout=hlayout, ) self._dock_add_button( - name=name, + name=desc, callback=callback, layout=hlayout, ) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 0e5820dbd7e..0df2f589514 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -199,7 +199,7 @@ def callback(): layout=hlayout, ) self._dock_add_button( - name=name, + name=desc, callback=callback, layout=hlayout, ) From 26f09d0bc77112bf8b77293b7e2d5a8420915d9a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 14:12:12 +0200 Subject: [PATCH 007/225] simplify [ci skip] --- mne/viz/_coreg/_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 72634e878b6..e597de600bf 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -68,7 +68,7 @@ def noop(x): name="fid_file", desc="Load", func=noop, - placeholder="Path to the fiducials file", + placeholder="Path to fiducials", layout=layout, ) digs = ["LPA", "Nasion", "RPA"] @@ -98,7 +98,7 @@ def noop(x): name="info_file", desc="Load", func=noop, - placeholder="Path to the info file", + placeholder="Path to info", layout=layout, ) self._widgets["grow_hair"] = self._renderer._dock_add_spin_box( From 662cbf32de48fd2c77ad804dc32304dbd3892296 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 14:33:44 +0200 Subject: [PATCH 008/225] reorder [ci skip] --- mne/viz/_coreg/_coreg.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index e597de600bf..d808bc34836 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -58,12 +58,6 @@ def noop(x): vertical=False, layout=layout, ) - self._widgets["show_hsp"] = self._renderer._dock_add_check_box( - name="Show Head Shape Points", - value=False, - callback=noop, - layout=layout - ) self._widgets["fid_file"] = self._renderer._dock_add_file_button( name="fid_file", desc="Load", @@ -79,7 +73,7 @@ def noop(x): vertical=False, layout=layout, ) - hlayout = self._renderer._dock_add_layout(vertical=False) + hlayout = self._renderer._dock_add_layout() for coord in ("X", "Y", "Z"): name = f"dig_{coord}" self._widgets[name] = self._renderer._dock_add_spin_box( @@ -122,6 +116,20 @@ def noop(x): layout=hlayout, ) self._renderer._layout_add_widget(layout, hlayout) + + layout = self._renderer._dock_add_group_box("View") + self._widgets["show_hsp"] = self._renderer._dock_add_check_box( + name="Show Head Shape Points", + value=False, + callback=noop, + layout=layout + ) + self._widgets["make_transparent"] = self._renderer._dock_add_check_box( + name="Make skin surface transparent", + value=False, + callback=noop, + layout=layout + ) self._renderer._dock_add_stretch() self._renderer._dock_initialize( @@ -151,9 +159,9 @@ def noop(x): layout=hlayout ) - hlayout = self._renderer._dock_add_group_box( - "Translation (t) and Rotation (r)") - for mode in ("t", "r"): + for mode, mode_name in (("t", "Translation"), ("r", "Rotation")): + hlayout = self._renderer._dock_add_group_box( + f"{mode_name} ({mode})") for coord in ("X", "Y", "Z"): name = f"{mode}{coord}" self._widgets[name] = self._renderer._dock_add_spin_box( From 45e0e6b5efb0b2d0d3a8dcbdce4892f95f7e17b0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 15:51:25 +0200 Subject: [PATCH 009/225] add directory filter --- mne/viz/backends/_abstract.py | 2 +- mne/viz/backends/_notebook.py | 2 +- mne/viz/backends/_qt.py | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index 29199cc931c..912bb08a647 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -557,7 +557,7 @@ def _dock_add_text(self, name, value, placeholder, layout=None): pass @abstractmethod - def _dock_add_file_button(self, name, desc, func, + def _dock_add_file_button(self, name, desc, func, directory=False, placeholder="Type a file name", layout=None): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index eb24b077ce7..41947a7b155 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -162,7 +162,7 @@ def _dock_add_text(self, name, value, placeholder, layout=None): self._layout_add_widget(layout, widget) return _IpyWidget(widget) - def _dock_add_file_button(self, name, desc, func, + def _dock_add_file_button(self, name, desc, func, directory=False, placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 0df2f589514..7e750b786be 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -181,7 +181,7 @@ def _dock_add_text(self, name, value, placeholder, layout=None): self._layout_add_widget(layout, widget) return _QtWidget(widget) - def _dock_add_file_button(self, name, desc, func, + def _dock_add_file_button(self, name, desc, func, directory=False, placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout @@ -189,6 +189,7 @@ def callback(): return FileDialog( self.plotter.app_window, callback=func, + directory=directory, ) hlayout = self._dock_add_layout(vertical=False) From e89c45b328690db664e3977c8e97c46a8e774a4c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 16:25:07 +0200 Subject: [PATCH 010/225] add support for directory [ci skip] --- mne/viz/_coreg/_coreg.py | 1 + mne/viz/backends/_qt.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index d808bc34836..b82235bae90 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -38,6 +38,7 @@ def noop(x): desc="Load", func=noop, placeholder="Subjects Directory", + directory=True, layout=layout, ) self._widgets["subject"] = self._renderer._dock_add_combo_box( diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 7e750b786be..aab8752bd64 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -10,14 +10,15 @@ import pyvista from pyvistaqt.plotting import FileDialog -from PyQt5.QtCore import Qt, pyqtSignal, QLocale +from PyQt5.QtCore import Qt, pyqtSignal, QLocale, QDir from PyQt5.QtGui import QIcon, QImage, QPixmap, QCursor from PyQt5.QtWidgets import (QComboBox, QDockWidget, QDoubleSpinBox, QGroupBox, QHBoxLayout, QLabel, QToolButton, QMenuBar, QSlider, QSpinBox, QVBoxLayout, QWidget, QSizePolicy, QScrollArea, QStyle, QProgressBar, QStyleOptionSlider, QLayout, QCheckBox, - QButtonGroup, QRadioButton, QLineEdit) + QButtonGroup, QRadioButton, QLineEdit, + QFileDialog) from ._pyvista import _PyVistaRenderer from ._pyvista import (_close_all, _close_3d_figure, _check_3d_figure, # noqa: F401,E501 analysis:ignore @@ -186,11 +187,15 @@ def _dock_add_file_button(self, name, desc, func, directory=False, layout = self._dock_layout if layout is None else layout def callback(): - return FileDialog( - self.plotter.app_window, - callback=func, - directory=directory, - ) + if directory: + folder = QFileDialog.getExistingDirectory( + None, ("Select Directory"), QDir.currentPath()) + func(folder) + else: + return FileDialog( + self.plotter.app_window, + callback=func, + ) hlayout = self._dock_add_layout(vertical=False) self._dock_add_text( From b9b91a883efed9eeff283072728c9c24e71eb3b3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 16:29:09 +0200 Subject: [PATCH 011/225] set minimal [ci skip] --- mne/viz/backends/_qt.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index aab8752bd64..c813996c8be 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -10,7 +10,7 @@ import pyvista from pyvistaqt.plotting import FileDialog -from PyQt5.QtCore import Qt, pyqtSignal, QLocale, QDir +from PyQt5.QtCore import Qt, pyqtSignal, QLocale from PyQt5.QtGui import QIcon, QImage, QPixmap, QCursor from PyQt5.QtWidgets import (QComboBox, QDockWidget, QDoubleSpinBox, QGroupBox, QHBoxLayout, QLabel, QToolButton, QMenuBar, @@ -188,9 +188,7 @@ def _dock_add_file_button(self, name, desc, func, directory=False, def callback(): if directory: - folder = QFileDialog.getExistingDirectory( - None, ("Select Directory"), QDir.currentPath()) - func(folder) + func(QFileDialog.getExistingDirectory()) else: return FileDialog( self.plotter.app_window, From 069d87c434d656715894a62da595cb7cbf75a3ff Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 23 Aug 2021 17:02:12 +0200 Subject: [PATCH 012/225] sync file button widget -> file text widget [ci skip] --- mne/viz/backends/_qt.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index c813996c8be..cc08cbf50f2 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -185,23 +185,30 @@ def _dock_add_text(self, name, value, placeholder, layout=None): def _dock_add_file_button(self, name, desc, func, directory=False, placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout + hlayout = self._dock_add_layout(vertical=False) + text_widget = self._dock_add_text( + name=f"{name}_field", + value=None, + placeholder=placeholder, + layout=hlayout, + ) + + def sync_text_widget(s): + text_widget.set_value(s) def callback(): if directory: - func(QFileDialog.getExistingDirectory()) + dname = QFileDialog.getExistingDirectory() + sync_text_widget(dname) + func(dname) else: - return FileDialog( + dialog = FileDialog( self.plotter.app_window, callback=func, ) + dialog.dlg_accepted.connect(sync_text_widget) + return dialog - hlayout = self._dock_add_layout(vertical=False) - self._dock_add_text( - name=f"{name}_field", - value=None, - placeholder=placeholder, - layout=hlayout, - ) self._dock_add_button( name=desc, callback=callback, From 474c704036013945a8ddd0f64fc03732b380d8c6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 10:47:57 +0200 Subject: [PATCH 013/225] proto: transparent skin surface [ci skip] --- mne/viz/_coreg/_coreg.py | 17 +++++++++++++++-- mne/viz/backends/_pyvista.py | 21 +-------------------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b82235bae90..ebca0262fe3 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -10,6 +10,9 @@ def __init__(self, info, subject, subjects_dir, fids='auto'): self._subjects_dir = subjects_dir self._fids = fids + self._first_time = True + self._opacity = 1.0 + self._widgets = dict() self._coreg = Coregistration(info, subject, subjects_dir, fids) self._renderer = _get_renderer() @@ -20,12 +23,17 @@ def __init__(self, info, subject, subjects_dir, fids='auto'): self._plot() def _plot(self): + if self._first_time: + self._first_time = False + else: + self._renderer.figure.plotter.clear() plot_alignment(self._info, trans=self._coreg.trans, subject=self._subject, subjects_dir=self._subjects_dir, - surfaces=dict(head=0.4), + surfaces=dict(head=self._opacity), dig=True, eeg=[], meg=False, coord_frame='meg', fig=self._renderer.figure) + self._renderer.reset_camera() def _configure_dock(self): def noop(x): @@ -125,10 +133,15 @@ def noop(x): callback=noop, layout=layout ) + + def _toggle_transparent(state): + self._opacity = 0.4 if state else 1.0 + self._plot() + self._widgets["make_transparent"] = self._renderer._dock_add_check_box( name="Make skin surface transparent", value=False, - callback=noop, + callback=_toggle_transparent, layout=layout ) self._renderer._dock_add_stretch() diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index a5a7f1e3ae7..e40d6ef1060 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -191,7 +191,7 @@ def __init__(self, fig=None, size=(600, 600), bgcolor='black', if not hasattr(self.plotter, "iren"): self.plotter.iren = None - self.update_lighting() + self.plotter.enable_3_lights() @property def _all_plotters(self): @@ -235,25 +235,6 @@ def subplot(self, x, y): def scene(self): return self.figure - def update_lighting(self): - # Inspired from Mayavi's version of Raymond Maple 3-lights illumination - for renderer in self._all_renderers: - lights = list(renderer.GetLights()) - headlight = lights.pop(0) - headlight.SetSwitch(False) - # below and centered, left and above, right and above - az_el_in = ((0, -45, 0.7), (-60, 30, 0.7), (60, 30, 0.7)) - for li, light in enumerate(lights): - if li < len(az_el_in): - light.SetSwitch(True) - light.SetPosition(_to_pos(*az_el_in[li][:2])) - light.SetIntensity(az_el_in[li][2]) - else: - light.SetSwitch(False) - light.SetPosition(_to_pos(0.0, 0.0)) - light.SetIntensity(0.0) - light.SetColor(1.0, 1.0, 1.0) - def set_interaction(self, interaction): if not hasattr(self.plotter, "iren") or self.plotter.iren is None: return From 1138729e5ced34d3c3ef25156baf7360fbcb9df0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 10:51:34 +0200 Subject: [PATCH 014/225] workaround for plot_alignment --- mne/viz/_3d.py | 5 +++-- mne/viz/_coreg/_coreg.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 01b25892f1c..848a2512a92 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -423,7 +423,7 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, meg=None, eeg='original', fwd=None, dig=False, ecog=True, src=None, mri_fiducials=False, bem=None, seeg=True, fnirs=True, show_axes=False, dbs=True, - fig=None, interaction='trackball', verbose=None): + fig=None, interaction='trackball', show=True, verbose=None): """Plot head, sensor, and source space alignment in 3D. Parameters @@ -864,7 +864,8 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, renderer.set_camera(azimuth=90, elevation=90, distance=0.6, focalpoint=(0., 0., 0.)) - renderer.show() + if show: + renderer.show() return renderer.scene() diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ebca0262fe3..6797d6cd738 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -32,7 +32,8 @@ def _plot(self): subjects_dir=self._subjects_dir, surfaces=dict(head=self._opacity), dig=True, eeg=[], meg=False, - coord_frame='meg', fig=self._renderer.figure) + coord_frame='meg', fig=self._renderer.figure, + show=False) self._renderer.reset_camera() def _configure_dock(self): From 8fcf70120c27fa21087dc7184f5e982a82c9b97d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 10:52:25 +0200 Subject: [PATCH 015/225] refactor --- mne/viz/_coreg/_coreg.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 6797d6cd738..6d9b74d19b2 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -36,6 +36,10 @@ def _plot(self): show=False) self._renderer.reset_camera() + def _toggle_transparent(self, state): + self._opacity = 0.4 if state else 1.0 + self._plot() + def _configure_dock(self): def noop(x): del x @@ -134,15 +138,10 @@ def noop(x): callback=noop, layout=layout ) - - def _toggle_transparent(state): - self._opacity = 0.4 if state else 1.0 - self._plot() - self._widgets["make_transparent"] = self._renderer._dock_add_check_box( name="Make skin surface transparent", value=False, - callback=_toggle_transparent, + callback=self._toggle_transparent, layout=layout ) self._renderer._dock_add_stretch() From 52d098e1bf33b5f88a5aec34e2c448751c6709d0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 11:10:22 +0200 Subject: [PATCH 016/225] proto: switch subject [ci skip] --- mne/viz/_coreg/_coreg.py | 39 +++++++++++++++++++++++++++-------- mne/viz/backends/_abstract.py | 5 +++-- mne/viz/backends/_notebook.py | 7 ++++--- mne/viz/backends/_qt.py | 9 ++++---- 4 files changed, 42 insertions(+), 18 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 6d9b74d19b2..ffc8672fa0b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,13 +1,17 @@ -from ...coreg import Coregistration +import os +import os.path as op +from ...coreg import Coregistration, _is_mri_subject from ...viz import plot_alignment +from ...utils import get_subjects_dir class CoregistrationUI(object): - def __init__(self, info, subject, subjects_dir, fids='auto'): + def __init__(self, info, subject, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._info = info self._subject = subject - self._subjects_dir = subjects_dir + self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, + raise_error=True) self._fids = fids self._first_time = True @@ -20,9 +24,9 @@ def __init__(self, info, subject, subjects_dir, fids='auto'): self._configure_dock() self._renderer.show() - self._plot() + self._update() - def _plot(self): + def _update(self): if self._first_time: self._first_time = False else: @@ -38,7 +42,23 @@ def _plot(self): def _toggle_transparent(self, state): self._opacity = 0.4 if state else 1.0 - self._plot() + self._update() + + def _switch_subject(self, subject): + self._subject = subject + self._update() + + def _get_subjects(self): + sdir = self._subjects_dir + is_dir = sdir and op.isdir(sdir) + if is_dir: + dir_content = os.listdir(sdir) + subjects = [s for s in dir_content if _is_mri_subject(s, sdir)] + if len(subjects) == 0: + subjects.append('') + else: + subjects = [''] + return sorted(subjects) def _configure_dock(self): def noop(x): @@ -50,15 +70,16 @@ def noop(x): name="subjects_dir", desc="Load", func=noop, + value=self._subjects_dir, placeholder="Subjects Directory", directory=True, layout=layout, ) self._widgets["subject"] = self._renderer._dock_add_combo_box( name="Subject", - value="sample", - rng=["sample"], - callback=noop, + value=self._subject, + rng=self._get_subjects(), + callback=self._switch_subject, compact=True, layout=layout ) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index 912bb08a647..38893de2cf8 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -557,8 +557,9 @@ def _dock_add_text(self, name, value, placeholder, layout=None): pass @abstractmethod - def _dock_add_file_button(self, name, desc, func, directory=False, - placeholder="Type a file name", layout=None): + def _dock_add_file_button(self, name, desc, func, value=None, + directory=False, placeholder="Type a file name", + layout=None): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 41947a7b155..982aed03ee1 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -162,8 +162,9 @@ def _dock_add_text(self, name, value, placeholder, layout=None): self._layout_add_widget(layout, widget) return _IpyWidget(widget) - def _dock_add_file_button(self, name, desc, func, directory=False, - placeholder="Type a file name", layout=None): + def _dock_add_file_button(self, name, desc, func, value=None, + directory=False, placeholder="Type a file name", + layout=None): layout = self._dock_layout if layout is None else layout def callback(): @@ -172,7 +173,7 @@ def callback(): hlayout = self._dock_add_layout(vertical=False) self._dock_add_text( name=f"{name}_field", - value=None, + value=value, placeholder=placeholder, layout=hlayout, ) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index cc08cbf50f2..f2138a057b3 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -177,18 +177,19 @@ def _dock_add_group_box(self, name, layout=None): def _dock_add_text(self, name, value, placeholder, layout=None): layout = self._dock_layout if layout is None else layout - widget = QLineEdit(value) + widget = QLineEdit(value if value is not None else placeholder) widget.setPlaceholderText(placeholder) self._layout_add_widget(layout, widget) return _QtWidget(widget) - def _dock_add_file_button(self, name, desc, func, directory=False, - placeholder="Type a file name", layout=None): + def _dock_add_file_button(self, name, desc, func, value=None, + directory=False, placeholder="Type a file name", + layout=None): layout = self._dock_layout if layout is None else layout hlayout = self._dock_add_layout(vertical=False) text_widget = self._dock_add_text( name=f"{name}_field", - value=None, + value=value, placeholder=placeholder, layout=hlayout, ) From 9524602dca393f79e51a2dbcccf63c459d7ede04 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 11:12:14 +0200 Subject: [PATCH 017/225] plot(verbose=False) --- mne/viz/_coreg/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ffc8672fa0b..dfe6563bff7 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -37,7 +37,7 @@ def _update(self): surfaces=dict(head=self._opacity), dig=True, eeg=[], meg=False, coord_frame='meg', fig=self._renderer.figure, - show=False) + show=False, verbose=False) self._renderer.reset_camera() def _toggle_transparent(self, state): From 2bbbb8cf6176588d54db14252dfb26ad19ca9c6b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 11:29:10 +0200 Subject: [PATCH 018/225] proto: switch subjects dir [ci skip] --- mne/viz/_coreg/_coreg.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index dfe6563bff7..c4f229627d1 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -6,13 +6,14 @@ class CoregistrationUI(object): - def __init__(self, info, subject, subjects_dir=None, fids='auto'): + def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer - self._info = info - self._subject = subject + self._fids = fids self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) - self._fids = fids + self._subjects = self._get_subjects() + self._subject = subject if subject is not None else self._subjects[0] + self._info = info self._first_time = True self._opacity = 1.0 @@ -44,11 +45,31 @@ def _toggle_transparent(self, state): self._opacity = 0.4 if state else 1.0 self._update() + def _switch_subjects_dir(self, subjects_dir): + self._subjects_dir = subjects_dir + self._subjects = self._get_subjects() + self._subject = self._subjects[0] + + # XXX: add coreg.set_subjects_dir + self._coreg._subjects_dir = subjects_dir + self._coreg._subject = self._subject + self._coreg.reset() + self._update() + def _switch_subject(self, subject): self._subject = subject + + # XXX: add coreg.set_subject() + self._coreg._subject = subject + self._coreg.reset() + self._update() + + def _fit_fiducials(self): + self._coreg.fit_fiducials() self._update() def _get_subjects(self): + # XXX: would be nice to move this function to util sdir = self._subjects_dir is_dir = sdir and op.isdir(sdir) if is_dir: @@ -69,7 +90,7 @@ def noop(x): self._widgets["subjects_dir"] = self._renderer._dock_add_file_button( name="subjects_dir", desc="Load", - func=noop, + func=self._switch_subjects_dir, value=self._subjects_dir, placeholder="Subjects Directory", directory=True, @@ -78,7 +99,7 @@ def noop(x): self._widgets["subject"] = self._renderer._dock_add_combo_box( name="Subject", value=self._subject, - rng=self._get_subjects(), + rng=self._subjects, callback=self._switch_subject, compact=True, layout=layout @@ -211,7 +232,7 @@ def noop(x): hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( name="Fit Fiducials", - callback=noop, + callback=self._fit_fiducials, layout=hlayout, ) self._renderer._dock_add_button( From 6b2021d7835a6779752484adb330b9041287ce1a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 15:29:36 +0200 Subject: [PATCH 019/225] remove groupbox --- mne/viz/_coreg/_coreg.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index c4f229627d1..106d371d012 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -190,18 +190,15 @@ def noop(x): self._renderer._dock_initialize( name="Fitting Parameters", area="right") - layout = self._renderer._dock_add_group_box("Scaling") self._widgets["scaling_mode"] = self._renderer._dock_add_combo_box( name="Scaling Mode", value="0", rng=["0", "1", "3"], callback=noop, compact=True, - layout=layout, ) hlayout = self._renderer._dock_add_group_box( name="Scaling Parameters", - layout=layout ) for coord in ("X", "Y", "Z"): name = f"s{coord}" From 93bb9a6bf1254b0c6b387dc858f609da2f445604 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 15:30:48 +0200 Subject: [PATCH 020/225] comment --- mne/viz/_3d.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 848a2512a92..91dc5e7a929 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -864,6 +864,7 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, renderer.set_camera(azimuth=90, elevation=90, distance=0.6, focalpoint=(0., 0., 0.)) + # XXX: temporary workaround if show: renderer.show() return renderer.scene() From a0a1ae41185df83deffdcec110d17b74b23db35c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 15:43:33 +0200 Subject: [PATCH 021/225] proto: fit_icp [ci skip] --- mne/viz/_coreg/_coreg.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 106d371d012..9a54e5428d5 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -17,6 +17,8 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._first_time = True self._opacity = 1.0 + self._default_n_iterations = 6 + self._icp_n_iterations = self._default_n_iterations self._widgets = dict() self._coreg = Coregistration(info, subject, subjects_dir, fids) @@ -64,10 +66,19 @@ def _switch_subject(self, subject): self._coreg.reset() self._update() + def _set_icp_n_iterations(self, n_iterations): + self._icp_n_iterations = n_iterations + def _fit_fiducials(self): self._coreg.fit_fiducials() self._update() + def _fit_icp(self): + self._coreg.fit_icp( + n_iterations=self._icp_n_iterations, + ) + self._update() + def _get_subjects(self): # XXX: would be nice to move this function to util sdir = self._subjects_dir @@ -85,7 +96,7 @@ def _configure_dock(self): def noop(x): del x - self._renderer._dock_initialize(name="Parameters", area="left") + self._renderer._dock_initialize(name="Input", area="left") layout = self._renderer._dock_add_group_box("MRI Subject") self._widgets["subjects_dir"] = self._renderer._dock_add_file_button( name="subjects_dir", @@ -188,8 +199,7 @@ def noop(x): ) self._renderer._dock_add_stretch() - self._renderer._dock_initialize( - name="Fitting Parameters", area="right") + self._renderer._dock_initialize(name="Parameters", area="right") self._widgets["scaling_mode"] = self._renderer._dock_add_combo_box( name="Scaling Mode", value="0", @@ -226,6 +236,16 @@ def noop(x): double=True, layout=hlayout ) + layout = self._renderer._dock_add_group_box("Fitting Options") + self._renderer._dock_add_spin_box( + name="Number Of ICP Iterations", + value=self._default_n_iterations, + rng=[1, 100], + callback=self._set_icp_n_iterations, + compact=True, + double=False, + layout=layout, + ) hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( name="Fit Fiducials", @@ -234,10 +254,9 @@ def noop(x): ) self._renderer._dock_add_button( name="Fit ICP", - callback=noop, + callback=self._fit_icp, layout=hlayout, ) - layout = self._renderer._dock_layout self._renderer._layout_add_widget(layout, hlayout) self._renderer._dock_add_stretch() From e0feee79acd4c198e24b16af0473a51dc43336be Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 15:46:15 +0200 Subject: [PATCH 022/225] refactor --- mne/viz/_coreg/_coreg.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9a54e5428d5..954aeaf6c9f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -29,6 +29,9 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._update() + def _reset(self): + self._coreg.reset() + def _update(self): if self._first_time: self._first_time = False @@ -51,19 +54,17 @@ def _switch_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir self._subjects = self._get_subjects() self._subject = self._subjects[0] - # XXX: add coreg.set_subjects_dir - self._coreg._subjects_dir = subjects_dir + self._coreg._subjects_dir = self._subjects_dir self._coreg._subject = self._subject - self._coreg.reset() + self._reset() self._update() def _switch_subject(self, subject): self._subject = subject - # XXX: add coreg.set_subject() self._coreg._subject = subject - self._coreg.reset() + self._reset() self._update() def _set_icp_n_iterations(self, n_iterations): From 118af69488ca03329a4da8ccd41e46f31479f78e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 16:12:25 +0200 Subject: [PATCH 023/225] proto: dig weights [ci skip] --- mne/viz/_coreg/_coreg.py | 64 +++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 954aeaf6c9f..5159e150038 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -8,6 +8,17 @@ class CoregistrationUI(object): def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer + self._widgets = dict() + self._first_time = True + self._opacity = 1.0 + self._default_icp_n_iterations = 20 + self._default_weights = { + "lpa": 1.0, + "nasion": 10.0, + "rpa": 1.0, + } + self._reset_fitting_parameters() + self._fids = fids self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) @@ -15,12 +26,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._subject = subject if subject is not None else self._subjects[0] self._info = info - self._first_time = True - self._opacity = 1.0 - self._default_n_iterations = 6 - self._icp_n_iterations = self._default_n_iterations - - self._widgets = dict() self._coreg = Coregistration(info, subject, subjects_dir, fids) self._renderer = _get_renderer() self._renderer._window_close_connect(self._clean) @@ -29,6 +34,16 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._update() + def _reset_fitting_parameters(self): + self._icp_n_iterations = self._default_icp_n_iterations + for dig in ("lpa", "nasion", "rpa"): + widget_name = f"{dig}_weight" + if widget_name in self._widgets: + self._widgets[widget_name].set_value( + self._default_weights[dig]) + else: + setattr(self, f"_{dig}_weight", self._default_weights[dig]) + def _reset(self): self._coreg.reset() @@ -70,13 +85,29 @@ def _switch_subject(self, subject): def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations + def _set_lpa_weight(self, value): + self._lpa_weight = value + + def _set_nasion_weight(self, value): + self._nasion_weight = value + + def _set_rpa_weight(self, value): + self._rpa_weight = value + def _fit_fiducials(self): - self._coreg.fit_fiducials() + self._coreg.fit_fiducials( + lpa_weight=self._lpa_weight, + nasion_weight=self._nasion_weight, + rpa_weight=self._rpa_weight, + ) self._update() def _fit_icp(self): self._coreg.fit_icp( n_iterations=self._icp_n_iterations, + lpa_weight=self._lpa_weight, + nasion_weight=self._nasion_weight, + rpa_weight=self._rpa_weight, ) self._update() @@ -240,14 +271,31 @@ def noop(x): layout = self._renderer._dock_add_group_box("Fitting Options") self._renderer._dock_add_spin_box( name="Number Of ICP Iterations", - value=self._default_n_iterations, + value=self._default_icp_n_iterations, rng=[1, 100], callback=self._set_icp_n_iterations, compact=True, double=False, layout=layout, ) + for dig in digs: + dig = dig.lower() + name = f"{dig}_weight" + self._widgets[name] = self._renderer._dock_add_spin_box( + name=name, + value=getattr(self, f"_{dig}_weight"), + rng=[1., 100.], + callback=getattr(self, f"_set_{dig}_weight"), + compact=True, + double=True, + layout=layout + ) hlayout = self._renderer._dock_add_layout(vertical=False) + self._renderer._dock_add_button( + name="Reset", + callback=self._reset_fitting_parameters, + layout=hlayout, + ) self._renderer._dock_add_button( name="Fit Fiducials", callback=self._fit_fiducials, From 20b807030f713d20256eab7016b6f4360745fb86 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 16:19:02 +0200 Subject: [PATCH 024/225] comment --- mne/viz/_coreg/_coreg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 5159e150038..670d1e561f7 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -285,6 +285,7 @@ def noop(x): name=name, value=getattr(self, f"_{dig}_weight"), rng=[1., 100.], + # XXX: does not work with lambda+setattr? callback=getattr(self, f"_set_{dig}_weight"), compact=True, double=True, From 46be57c233d6645f4acab6830731b6d4907a9b62 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 24 Aug 2021 16:21:41 +0200 Subject: [PATCH 025/225] add self._verbose --- mne/viz/_coreg/_coreg.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 670d1e561f7..0b386ab9357 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -9,6 +9,7 @@ class CoregistrationUI(object): def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._widgets = dict() + self._verbose = False self._first_time = True self._opacity = 1.0 self._default_icp_n_iterations = 20 @@ -58,7 +59,7 @@ def _update(self): surfaces=dict(head=self._opacity), dig=True, eeg=[], meg=False, coord_frame='meg', fig=self._renderer.figure, - show=False, verbose=False) + show=False, verbose=self._verbose) self._renderer.reset_camera() def _toggle_transparent(self, state): @@ -99,6 +100,7 @@ def _fit_fiducials(self): lpa_weight=self._lpa_weight, nasion_weight=self._nasion_weight, rpa_weight=self._rpa_weight, + verbose=self._verbose, ) self._update() @@ -108,6 +110,7 @@ def _fit_icp(self): lpa_weight=self._lpa_weight, nasion_weight=self._nasion_weight, rpa_weight=self._rpa_weight, + verbose=self._verbose, ) self._update() From 1c078a1dd493cf01d0935d353183dab68bd30ef0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 11:04:28 +0200 Subject: [PATCH 026/225] proto: display parameters with 4 decimals [ci skip] --- mne/viz/_coreg/_coreg.py | 27 +++++++++++++++++++++------ mne/viz/backends/_abstract.py | 3 ++- mne/viz/backends/_notebook.py | 3 ++- mne/viz/backends/_qt.py | 5 ++++- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0b386ab9357..5414b3c886f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -37,6 +37,10 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): def _reset_fitting_parameters(self): self._icp_n_iterations = self._default_icp_n_iterations + if "icp_n_iterations" in self._widgets: + self._widgets["icp_n_iterations"].set_value( + self._default_icp_n_iterations) + for dig in ("lpa", "nasion", "rpa"): widget_name = f"{dig}_weight" if widget_name in self._widgets: @@ -61,6 +65,14 @@ def _update(self): coord_frame='meg', fig=self._renderer.figure, show=False, verbose=self._verbose) self._renderer.reset_camera() + coords = ["X", "Y", "Z"] + for tr in ("translation", "rotation", "scale"): + for coord in coords: + widget_name = tr[0] + coord + if widget_name in self._widgets: + idx = coords.index(coord) + val = getattr(self._coreg, f"_{tr}") + self._widgets[widget_name].set_value(val[idx]) def _toggle_transparent(self, state): self._opacity = 0.4 if state else 1.0 @@ -250,10 +262,11 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=name, value=0., - rng=[0., 1.], + rng=[-100., 100.], callback=noop, compact=True, double=True, + decimals=4, layout=hlayout ) @@ -265,14 +278,15 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=name, value=0., - rng=[0., 1.], + rng=[-100., 100.], callback=noop, compact=True, double=True, + decimals=4, layout=hlayout ) layout = self._renderer._dock_add_group_box("Fitting Options") - self._renderer._dock_add_spin_box( + self._widgets["icp_n_iterations"] = self._renderer._dock_add_spin_box( name="Number Of ICP Iterations", value=self._default_icp_n_iterations, rng=[1, 100], @@ -294,12 +308,13 @@ def noop(x): double=True, layout=layout ) - hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( - name="Reset", + name="Reset Fitting Options", callback=self._reset_fitting_parameters, - layout=hlayout, + layout=layout, ) + layout = self._renderer._dock_layout + hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( name="Fit Fiducials", callback=self._fit_fiducials, diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index 38893de2cf8..cc01be43c35 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -535,7 +535,8 @@ def _dock_add_check_box(self, name, value, callback, layout=None): @abstractmethod def _dock_add_spin_box(self, name, value, rng, callback, - compact=True, double=True, layout=None): + compact=True, double=True, decimals=2, + layout=None): pass @abstractmethod diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 982aed03ee1..7fcec25c0e8 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -114,7 +114,8 @@ def _dock_add_check_box(self, name, value, callback, layout=None): return _IpyWidget(widget) def _dock_add_spin_box(self, name, value, rng, callback, - compact=True, double=True, layout=None): + compact=True, double=True, decimals=2, + layout=None): layout = self._dock_named_layout(name, layout, compact) klass = FloatText if double else IntText widget = klass( diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index f2138a057b3..6f98ae7fa12 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -124,13 +124,16 @@ def _dock_add_check_box(self, name, value, callback, layout=None): return _QtWidget(widget) def _dock_add_spin_box(self, name, value, rng, callback, - compact=True, double=True, layout=None): + compact=True, double=True, decimals=2, + layout=None): layout = self._dock_named_layout(name, layout, compact) value = value if double else int(value) widget = QDoubleSpinBox() if double else QSpinBox() widget.setAlignment(Qt.AlignCenter) widget.setMinimum(rng[0]) widget.setMaximum(rng[1]) + if double: + widget.setDecimals(decimals) inc = (rng[1] - rng[0]) / 20. inc = max(int(round(inc)), 1) if not double else inc widget.setKeyboardTracking(False) From adf824984159d526f522febdd44246cbb323ee14 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 14:17:43 +0200 Subject: [PATCH 027/225] proto: omit hsp --- mne/viz/_coreg/_coreg.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 5414b3c886f..92bbac3bab9 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -11,6 +11,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._widgets = dict() self._verbose = False self._first_time = True + self._omit_hsp_distance = 0.0 self._opacity = 1.0 self._default_icp_n_iterations = 20 self._default_weights = { @@ -49,6 +50,12 @@ def _reset_fitting_parameters(self): else: setattr(self, f"_{dig}_weight", self._default_weights[dig]) + def _set_omit_hsp_distance(self, distance): + self._omit_hsp_distance = distance + + def _omit_hsp(self): + self._coreg.omit_head_shape_points(self._omit_hsp_distance) + def _reset(self): self._coreg.reset() @@ -220,13 +227,14 @@ def noop(x): self._widgets["omit_distance"] = self._renderer._dock_add_spin_box( name="Omit Distance", value=0.0, - rng=[0.0, 10.0], - callback=noop, + rng=[0.0, 100.0], + callback=self._set_omit_hsp_distance, + decimals=4, layout=hlayout, ) self._widgets["omit"] = self._renderer._dock_add_button( name="Omit", - callback=noop, + callback=self._omit_hsp, layout=hlayout, ) self._renderer._layout_add_widget(layout, hlayout) From 7253431b1e55dc17016f9b05d64d1a0b895428ac Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 14:29:52 +0200 Subject: [PATCH 028/225] fix [ci skip] --- mne/viz/backends/_qt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 6f98ae7fa12..1174531e697 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -180,7 +180,7 @@ def _dock_add_group_box(self, name, layout=None): def _dock_add_text(self, name, value, placeholder, layout=None): layout = self._dock_layout if layout is None else layout - widget = QLineEdit(value if value is not None else placeholder) + widget = QLineEdit(value) widget.setPlaceholderText(placeholder) self._layout_add_widget(layout, widget) return _QtWidget(widget) From 6ade890450d12f0edac8cae539a500c349fa9568 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 14:46:32 +0200 Subject: [PATCH 029/225] proto: reset [ci skip] --- mne/viz/_coreg/_coreg.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 92bbac3bab9..a608a9427f3 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -57,7 +57,9 @@ def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance) def _reset(self): + self._reset_fitting_parameters() self._coreg.reset() + self._update() def _update(self): if self._first_time: @@ -93,14 +95,12 @@ def _switch_subjects_dir(self, subjects_dir): self._coreg._subjects_dir = self._subjects_dir self._coreg._subject = self._subject self._reset() - self._update() def _switch_subject(self, subject): self._subject = subject # XXX: add coreg.set_subject() self._coreg._subject = subject self._reset() - self._update() def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations @@ -293,6 +293,19 @@ def noop(x): decimals=4, layout=hlayout ) + layout = self._renderer._dock_layout + hlayout = self._renderer._dock_add_layout(vertical=False) + self._renderer._dock_add_button( + name="Fit Fiducials", + callback=self._fit_fiducials, + layout=hlayout, + ) + self._renderer._dock_add_button( + name="Fit ICP", + callback=self._fit_icp, + layout=hlayout, + ) + self._renderer._layout_add_widget(layout, hlayout) layout = self._renderer._dock_add_group_box("Fitting Options") self._widgets["icp_n_iterations"] = self._renderer._dock_add_spin_box( name="Number Of ICP Iterations", @@ -324,13 +337,18 @@ def noop(x): layout = self._renderer._dock_layout hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( - name="Fit Fiducials", - callback=self._fit_fiducials, + name="Reset", + callback=self._reset, layout=hlayout, ) self._renderer._dock_add_button( - name="Fit ICP", - callback=self._fit_icp, + name="Save...", + callback=noop, + layout=hlayout, + ) + self._renderer._dock_add_button( + name="Load...", + callback=noop, layout=hlayout, ) self._renderer._layout_add_widget(layout, hlayout) From c9391817c9c2a980799accce35aa92ff15d16155 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 14:57:06 +0200 Subject: [PATCH 030/225] proto: set scale mode --- mne/viz/_coreg/_coreg.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index a608a9427f3..df71a5ca27a 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -50,6 +50,10 @@ def _reset_fitting_parameters(self): else: setattr(self, f"_{dig}_weight", self._default_weights[dig]) + def _set_scale_mode(self, mode): + mode = None if mode == "None" else mode + self._coreg.set_scale_mode(mode) + def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance @@ -258,8 +262,8 @@ def noop(x): self._widgets["scaling_mode"] = self._renderer._dock_add_combo_box( name="Scaling Mode", value="0", - rng=["0", "1", "3"], - callback=noop, + rng=["None", "uniform", "3-axis"], + callback=self._set_scale_mode, compact=True, ) hlayout = self._renderer._dock_add_group_box( From 5f26bfe4ad550a1ca8462ca27a557d3c3a23094c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 15:06:36 +0200 Subject: [PATCH 031/225] refactor to fix --- mne/coreg.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 990e66fae8d..8877f18f17a 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -1317,6 +1317,9 @@ def __init__(self, info, subject, subjects_dir=None, fiducials='auto'): self._default_parameters = \ np.array([0., 0., 0., 0., 0., 0., 1., 1., 1.]) + self._rotation = self._default_parameters[:3] + self._translation = self._default_parameters[3:6] + self._scale = self._default_parameters[6:9] self._icp_iterations = 20 self._icp_angle = 0.2 self._icp_distance = 0.2 @@ -1343,11 +1346,6 @@ def __init__(self, info, subject, subjects_dir=None, fiducials='auto'): self._setup_bem() self._setup_fiducials(fiducials) self.reset() - self._update_params( - rot=self._default_parameters[:3], - tra=self._default_parameters[3:6], - sca=self._default_parameters[6:9], - ) def _setup_bem(self): # find high-res head model (if possible) @@ -1894,9 +1892,11 @@ def reset(self): The modified Coregistration object. """ self._grow_hair = 0. - self._rotation = self._default_parameters[:3] - self._translation = self._default_parameters[3:6] - self._scale = self._default_parameters[6:9] + self._update_params( + rot=self._default_parameters[:3], + tra=self._default_parameters[3:6], + sca=self._default_parameters[6:9], + ) self._last_rotation = self._rotation.copy() self._last_translation = self._translation.copy() self._last_scale = self._scale.copy() From dfdde9f1f390e0de9f8ea3f93fc24df113504901 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 15:10:59 +0200 Subject: [PATCH 032/225] refactor [ci skip] --- mne/coreg.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 8877f18f17a..9f56d952769 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -1421,14 +1421,14 @@ def _update_params(self, rot=None, tra=None, sca=None, rot_changed = False if rot is not None: rot_changed = True - self._last_rotation = self._rotation + self._last_rotation = self._rotation.copy() self._rotation = rot tra_changed = False if rot_changed or tra is not None: if tra is None: tra = self._translation tra_changed = True - self._last_translation = self._translation + self._last_translation = self._translation.copy() self._translation = tra self._head_mri_t = rotation(*self._rotation).T self._head_mri_t[:3, 3] = \ @@ -1448,7 +1448,7 @@ def _update_params(self, rot=None, tra=None, sca=None, if tra_changed or sca is not None: if sca is None: sca = self._scale - self._last_scale = self._scale + self._last_scale = self._scale.copy() self._scale = sca self._mri_trans = np.eye(4) self._mri_trans[:, :3] *= sca @@ -1897,9 +1897,6 @@ def reset(self): tra=self._default_parameters[3:6], sca=self._default_parameters[6:9], ) - self._last_rotation = self._rotation.copy() - self._last_translation = self._translation.copy() - self._last_scale = self._scale.copy() self._extra_points_filter = None self._update_nearest_calc() return self From d5f683c4cc3cd256b7375f6ecbef1bd1b430dac0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 25 Aug 2021 17:24:04 +0200 Subject: [PATCH 033/225] proto: lock fids [ci skip] --- mne/viz/_coreg/_coreg.py | 27 ++++++++++++++++++--------- mne/viz/backends/_abstract.py | 13 +++++++++++++ mne/viz/backends/_notebook.py | 22 +++++++++++++++++++--- mne/viz/backends/_qt.py | 27 ++++++++++++++++++++++----- 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index df71a5ca27a..896f67142ed 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -57,6 +57,16 @@ def _set_scale_mode(self, mode): def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance + def _set_lock_fids(self, state): + if "fid_file" in self._widgets: + self._widgets["fid_file"].set_enabled(not state) + if "digs" in self._widgets: + self._widgets["digs"].set_enabled(not state) + for coord in ("X", "Y", "Z"): + name = f"dig_{coord}" + if name in self._widgets: + self._widgets[name].set_enabled(not state) + def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance) @@ -175,13 +185,11 @@ def noop(x): ) layout = self._renderer._dock_add_group_box("MRI Fiducials") - digs_states = ["Lock", "Edit"] - self._renderer._dock_add_radio_buttons( - value=digs_states[0], - rng=digs_states, - callback=noop, - vertical=False, - layout=layout, + self._widgets["lock_fids"] = self._renderer._dock_add_check_box( + name="Lock fiducials", + value=False, + callback=self._set_lock_fids, + layout=layout ) self._widgets["fid_file"] = self._renderer._dock_add_file_button( name="fid_file", @@ -191,7 +199,7 @@ def noop(x): layout=layout, ) digs = ["LPA", "Nasion", "RPA"] - self._renderer._dock_add_radio_buttons( + self._widgets["digs"] = self._renderer._dock_add_radio_buttons( value=digs[0], rng=digs, callback=noop, @@ -211,6 +219,7 @@ def noop(x): layout=hlayout ) self._renderer._layout_add_widget(layout, hlayout) + self._widgets["lock_fids"].set_value(True) layout = self._renderer._dock_add_group_box("Digitization Source") self._widgets["info_file"] = self._renderer._dock_add_file_button( @@ -261,7 +270,7 @@ def noop(x): self._renderer._dock_initialize(name="Parameters", area="right") self._widgets["scaling_mode"] = self._renderer._dock_add_combo_box( name="Scaling Mode", - value="0", + value="None", rng=["None", "uniform", "3-axis"], callback=self._set_scale_mode, compact=True, diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index cc01be43c35..a959ab6bec5 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -613,6 +613,15 @@ def _layout_add_widget(self, layout, widget, stretch=0): pass +class _AbstractWidgetList(ABC): + def set_enabled(self, state): + for widget in self._widgets: + widget.set_enabled(state) + + def widget(self, idx): + return self._widgets[idx] + + class _AbstractWidget(ABC): def __init__(self, widget): self._widget = widget @@ -641,6 +650,10 @@ def show(self): def hide(self): pass + @abstractmethod + def set_enabled(self, state): + pass + @abstractmethod def update(self, repaint=True): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 7fcec25c0e8..04610260739 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -16,7 +16,8 @@ from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar, _AbstractStatusBar, _AbstractLayout, _AbstractWidget, _AbstractWindow, _AbstractMplCanvas, _AbstractPlayback, - _AbstractBrainMplCanvas, _AbstractMplInterface) + _AbstractBrainMplCanvas, _AbstractMplInterface, + _AbstractWidgetList) from ._pyvista import _PyVistaRenderer, _close_all, _set_3d_view, _set_3d_title # noqa: F401,E501, analysis:ignore @@ -149,6 +150,7 @@ def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, disabled=False, ) self._layout_add_widget(layout, widget) + # XXX: works but would be nice to use _IpyWidgetList for consistency return _IpyWidget(widget) def _dock_add_group_box(self, name, layout=None): @@ -172,18 +174,19 @@ def callback(): fname = self.actions[f"{name}_field"].value func(None if len(fname) == 0 else fname) hlayout = self._dock_add_layout(vertical=False) - self._dock_add_text( + text_widget = self._dock_add_text( name=f"{name}_field", value=value, placeholder=placeholder, layout=hlayout, ) - self._dock_add_button( + button_widget = self._dock_add_button( name=desc, callback=callback, layout=hlayout, ) self._layout_add_widget(layout, hlayout) + return _IpyWidgetList([text_widget, button_widget]) def _generate_callback(callback, to_float=False): @@ -366,6 +369,16 @@ def _window_set_theme(self, theme): pass +class _IpyWidgetList(_AbstractWidgetList): + def __init__(self, src): + self._src = src + self._widgets = list() + for widget in src: + if not isinstance(widget, _IpyWidget): + widget = _IpyWidget(widget) + self._widgets.append(widget) + + class _IpyWidget(_AbstractWidget): def set_value(self, value): self._widget.value = value @@ -383,6 +396,9 @@ def show(self): def hide(self): self._widget.layout.visibility = "hidden" + def set_enabled(self, state): + self._widget.disabled = not state + def update(self, repaint=True): pass diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 1174531e697..ccbd99bc135 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -26,7 +26,8 @@ from ._abstract import (_AbstractDock, _AbstractToolBar, _AbstractMenuBar, _AbstractStatusBar, _AbstractLayout, _AbstractWidget, _AbstractWindow, _AbstractMplCanvas, _AbstractPlayback, - _AbstractBrainMplCanvas, _AbstractMplInterface) + _AbstractBrainMplCanvas, _AbstractMplInterface, + _AbstractWidgetList) from ._utils import _init_qt_resources, _qt_disable_paint from ..utils import logger @@ -161,14 +162,13 @@ def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, group = QButtonGroup() for val in rng: button = QRadioButton(val) - button.toggled.connect(callback) if val == value: button.setChecked(True) group.addButton(button) self._layout_add_widget(group_layout, button) + group.buttonClicked.connect(callback) self._layout_add_widget(layout, group_layout) - # XXX: the following should be wrapped by _QtWidget - return group.buttons() + return _QtWidgetList(group) def _dock_add_group_box(self, name, layout=None): layout = self._dock_layout if layout is None else layout @@ -213,12 +213,13 @@ def callback(): dialog.dlg_accepted.connect(sync_text_widget) return dialog - self._dock_add_button( + button_widget = self._dock_add_button( name=desc, callback=callback, layout=hlayout, ) self._layout_add_widget(layout, hlayout) + return _QtWidgetList([text_widget, button_widget]) class QFloatSlider(QSlider): @@ -556,12 +557,25 @@ def _window_set_theme(self, theme): self._window.setStyleSheet(stylesheet) +class _QtWidgetList(_AbstractWidgetList): + def __init__(self, src): + self._src = src + self._widgets = list() + widgets = src.buttons() if isinstance(src, QButtonGroup) else src + for widget in widgets: + if not isinstance(widget, _QtWidget): + widget = _QtWidget(widget) + self._widgets.append(widget) + + class _QtWidget(_AbstractWidget): def set_value(self, value): if hasattr(self._widget, "setValue"): self._widget.setValue(value) elif hasattr(self._widget, "setCurrentText"): self._widget.setCurrentText(value) + elif hasattr(self._widget, "setChecked"): + self._widget.setChecked(value) else: assert hasattr(self._widget, "setText") self._widget.setText(value) @@ -583,6 +597,9 @@ def show(self): def hide(self): self._widget.hide() + def set_enabled(self, state): + self._widget.setEnabled(state) + def update(self, repaint=True): self._widget.update() if repaint: From 4af8d3aadc61071ae7adc8e92a2a198952769389 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 10:19:23 +0200 Subject: [PATCH 034/225] move ui elements [ci skip] --- mne/viz/_coreg/_coreg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 896f67142ed..c9e0e7289b3 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -306,7 +306,7 @@ def noop(x): decimals=4, layout=hlayout ) - layout = self._renderer._dock_layout + layout = self._renderer._dock_add_group_box("Fitting") hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( name="Fit Fiducials", @@ -319,7 +319,6 @@ def noop(x): layout=hlayout, ) self._renderer._layout_add_widget(layout, hlayout) - layout = self._renderer._dock_add_group_box("Fitting Options") self._widgets["icp_n_iterations"] = self._renderer._dock_add_spin_box( name="Number Of ICP Iterations", value=self._default_icp_n_iterations, From 22fdc465f1016220a76c320b2d293147f1968881 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 10:34:37 +0200 Subject: [PATCH 035/225] move ui elements --- mne/viz/_coreg/_coreg.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index c9e0e7289b3..0623c67eb47 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -328,19 +328,25 @@ def noop(x): double=False, layout=layout, ) + layout = self._renderer._dock_add_group_box( + name="Weights", + layout=layout, + ) + hlayout = self._renderer._dock_add_layout(vertical=False) for dig in digs: dig = dig.lower() name = f"{dig}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( - name=name, + name=dig, value=getattr(self, f"_{dig}_weight"), rng=[1., 100.], # XXX: does not work with lambda+setattr? callback=getattr(self, f"_set_{dig}_weight"), compact=True, double=True, - layout=layout + layout=hlayout ) + self._renderer._layout_add_widget(layout, hlayout) self._renderer._dock_add_button( name="Reset Fitting Options", callback=self._reset_fitting_parameters, From e1bf6ec3c0f4c0928d07e6c8ada7985ddf2269f9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 10:46:15 +0200 Subject: [PATCH 036/225] rename [ci skip] --- mne/viz/_coreg/_coreg.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0623c67eb47..2a2d6b8625b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -60,8 +60,8 @@ def _set_omit_hsp_distance(self, distance): def _set_lock_fids(self, state): if "fid_file" in self._widgets: self._widgets["fid_file"].set_enabled(not state) - if "digs" in self._widgets: - self._widgets["digs"].set_enabled(not state) + if "fids" in self._widgets: + self._widgets["fids"].set_enabled(not state) for coord in ("X", "Y", "Z"): name = f"dig_{coord}" if name in self._widgets: @@ -198,10 +198,10 @@ def noop(x): placeholder="Path to fiducials", layout=layout, ) - digs = ["LPA", "Nasion", "RPA"] - self._widgets["digs"] = self._renderer._dock_add_radio_buttons( - value=digs[0], - rng=digs, + fids = ["LPA", "Nasion", "RPA"] + self._widgets["fids"] = self._renderer._dock_add_radio_buttons( + value=fids[0], + rng=fids, callback=noop, vertical=False, layout=layout, @@ -333,15 +333,15 @@ def noop(x): layout=layout, ) hlayout = self._renderer._dock_add_layout(vertical=False) - for dig in digs: - dig = dig.lower() - name = f"{dig}_weight" + for fid in fids: + fid_lower = fid.lower() + name = f"{fid}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( - name=dig, - value=getattr(self, f"_{dig}_weight"), + name=fid, + value=getattr(self, f"_{fid_lower}_weight"), rng=[1., 100.], # XXX: does not work with lambda+setattr? - callback=getattr(self, f"_set_{dig}_weight"), + callback=getattr(self, f"_set_{fid_lower}_weight"), compact=True, double=True, layout=hlayout From 13f23a89e07825a2e4a77628697a4d9788c67791 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 11:01:53 +0200 Subject: [PATCH 037/225] proto: high res head [ci skip] --- mne/viz/_coreg/_coreg.py | 15 ++++++++++++++- mne/viz/backends/_qt.py | 2 +- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 2a2d6b8625b..db976f6c5d8 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -12,6 +12,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._verbose = False self._first_time = True self._omit_hsp_distance = 0.0 + self._surface = "head-dense" self._opacity = 1.0 self._default_icp_n_iterations = 20 self._default_weights = { @@ -80,10 +81,12 @@ def _update(self): self._first_time = False else: self._renderer.figure.plotter.clear() + surfaces = dict() + surfaces[self._surface] = self._opacity plot_alignment(self._info, trans=self._coreg.trans, subject=self._subject, subjects_dir=self._subjects_dir, - surfaces=dict(head=self._opacity), + surfaces=surfaces, dig=True, eeg=[], meg=False, coord_frame='meg', fig=self._renderer.figure, show=False, verbose=self._verbose) @@ -97,6 +100,10 @@ def _update(self): val = getattr(self._coreg, f"_{tr}") self._widgets[widget_name].set_value(val[idx]) + def _toggle_high_resolution_head(self, state): + self._surface = "head-dense" if state else "head" + self._update() + def _toggle_transparent(self, state): self._opacity = 0.4 if state else 1.0 self._update() @@ -259,6 +266,12 @@ def noop(x): callback=noop, layout=layout ) + self._widgets["high_res_head"] = self._renderer._dock_add_check_box( + name="Show High Resolution Head", + value=True, + callback=self._toggle_high_resolution_head, + layout=layout + ) self._widgets["make_transparent"] = self._renderer._dock_add_check_box( name="Make skin surface transparent", value=False, diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index ccbd99bc135..5187d487a4e 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -119,7 +119,7 @@ def _dock_add_slider(self, name, value, rng, callback, def _dock_add_check_box(self, name, value, callback, layout=None): layout = self._dock_layout if layout is None else layout widget = QCheckBox(name) - widget.setCheckState(value) + widget.setChecked(value) widget.stateChanged.connect(callback) self._layout_add_widget(layout, widget) return _QtWidget(widget) From 3b14e93b0045005796ded3f46870db6605f301b5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 11:39:41 +0200 Subject: [PATCH 038/225] add more point weights [ci skip] --- mne/viz/_coreg/_coreg.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index db976f6c5d8..d098be9588f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -19,6 +19,9 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): "lpa": 1.0, "nasion": 10.0, "rpa": 1.0, + "hsp": 1.0, + "eeg": 1.0, + "hpi": 1.0, } self._reset_fitting_parameters() @@ -43,13 +46,13 @@ def _reset_fitting_parameters(self): self._widgets["icp_n_iterations"].set_value( self._default_icp_n_iterations) - for dig in ("lpa", "nasion", "rpa"): - widget_name = f"{dig}_weight" + for fid in self._default_weights.keys(): + widget_name = f"{fid}_weight" if widget_name in self._widgets: self._widgets[widget_name].set_value( - self._default_weights[dig]) + self._default_weights[fid]) else: - setattr(self, f"_{dig}_weight", self._default_weights[dig]) + setattr(self, f"_{fid}_weight", self._default_weights[fid]) def _set_scale_mode(self, mode): mode = None if mode == "None" else mode @@ -135,6 +138,15 @@ def _set_nasion_weight(self, value): def _set_rpa_weight(self, value): self._rpa_weight = value + def _set_hsp_weight(self, value): + self._hsp_weight = value + + def _set_eeg_weight(self, value): + self._eeg_weight = value + + def _set_hpi_weight(self, value): + self._hpi_weight = value + def _fit_fiducials(self): self._coreg.fit_fiducials( lpa_weight=self._lpa_weight, @@ -360,6 +372,21 @@ def noop(x): layout=hlayout ) self._renderer._layout_add_widget(layout, hlayout) + hlayout = self._renderer._dock_add_layout(vertical=False) + for point in ("HSP", "EEG", "HPI"): + point_lower = point.lower() + name = f"{point}_weight" + self._widgets[name] = self._renderer._dock_add_spin_box( + name=point, + value=getattr(self, f"_{point_lower}_weight"), + rng=[1., 100.], + # XXX: does not work with lambda+setattr? + callback=getattr(self, f"_set_{point_lower}_weight"), + compact=True, + double=True, + layout=hlayout + ) + self._renderer._layout_add_widget(layout, hlayout) self._renderer._dock_add_button( name="Reset Fitting Options", callback=self._reset_fitting_parameters, From f0b9b2e2d26c7468656a0c71f72d5f553b64c131 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 14:33:45 +0200 Subject: [PATCH 039/225] proto: save trans [ci skip] --- mne/viz/_coreg/_coreg.py | 23 +++++++++++++++------ mne/viz/backends/_abstract.py | 4 ++-- mne/viz/backends/_notebook.py | 4 ++-- mne/viz/backends/_qt.py | 38 +++++++++++++++++++++-------------- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index d098be9588f..675dbf20d57 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -2,6 +2,7 @@ import os.path as op from ...coreg import Coregistration, _is_mri_subject from ...viz import plot_alignment +from ...transforms import read_trans, write_trans from ...utils import get_subjects_dir @@ -166,6 +167,12 @@ def _fit_icp(self): ) self._update() + def _save_trans(self, filename): + write_trans(filename, self._coreg.trans) + + def _load_trans(self, filename): + read_trans(filename) + def _get_subjects(self): # XXX: would be nice to move this function to util sdir = self._subjects_dir @@ -399,14 +406,18 @@ def noop(x): callback=self._reset, layout=hlayout, ) - self._renderer._dock_add_button( - name="Save...", - callback=noop, + self._widgets["save_trans"] = self._renderer._dock_add_file_button( + name="save_trans", + desc="Save...", + func=self._save_trans, + input_text_widget=False, layout=hlayout, ) - self._renderer._dock_add_button( - name="Load...", - callback=noop, + self._widgets["load_trans"] = self._renderer._dock_add_file_button( + name="load_trans", + desc="Load...", + func=self._load_trans, + input_text_widget=False, layout=hlayout, ) self._renderer._layout_add_widget(layout, hlayout) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index e780e5d12f1..ff8530b6d32 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -568,8 +568,8 @@ def _dock_add_text(self, name, value, placeholder, layout=None): @abstractmethod def _dock_add_file_button(self, name, desc, func, value=None, - directory=False, placeholder="Type a file name", - layout=None): + directory=False, input_text_widget=True, + placeholder="Type a file name", layout=None): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 04610260739..c3339a658d0 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -166,8 +166,8 @@ def _dock_add_text(self, name, value, placeholder, layout=None): return _IpyWidget(widget) def _dock_add_file_button(self, name, desc, func, value=None, - directory=False, placeholder="Type a file name", - layout=None): + directory=False, input_text_widget=True, + placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout def callback(): diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 5187d487a4e..bdd6d75c3bc 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -186,31 +186,36 @@ def _dock_add_text(self, name, value, placeholder, layout=None): return _QtWidget(widget) def _dock_add_file_button(self, name, desc, func, value=None, - directory=False, placeholder="Type a file name", - layout=None): + directory=False, input_text_widget=True, + placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout - hlayout = self._dock_add_layout(vertical=False) - text_widget = self._dock_add_text( - name=f"{name}_field", - value=value, - placeholder=placeholder, - layout=hlayout, - ) + if input_text_widget: + hlayout = self._dock_add_layout(vertical=False) + text_widget = self._dock_add_text( + name=f"{name}_field", + value=value, + placeholder=placeholder, + layout=hlayout, + ) - def sync_text_widget(s): - text_widget.set_value(s) + def sync_text_widget(s): + text_widget.set_value(s) + else: + hlayout = layout def callback(): if directory: dname = QFileDialog.getExistingDirectory() - sync_text_widget(dname) + if input_text_widget: + sync_text_widget(dname) func(dname) else: dialog = FileDialog( self.plotter.app_window, callback=func, ) - dialog.dlg_accepted.connect(sync_text_widget) + if input_text_widget: + dialog.dlg_accepted.connect(sync_text_widget) return dialog button_widget = self._dock_add_button( @@ -218,8 +223,11 @@ def callback(): callback=callback, layout=hlayout, ) - self._layout_add_widget(layout, hlayout) - return _QtWidgetList([text_widget, button_widget]) + if input_text_widget: + self._layout_add_widget(layout, hlayout) + return _QtWidgetList([text_widget, button_widget]) + else: + return _QtWidget(button_widget) class QFloatSlider(QSlider): From 04d20e42b31184463c9e6ca273fb95664cf936a2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 15:22:09 +0200 Subject: [PATCH 040/225] proto: read trans [ci skip] --- mne/viz/_coreg/_coreg.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 675dbf20d57..5e07db40ac6 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -2,7 +2,8 @@ import os.path as op from ...coreg import Coregistration, _is_mri_subject from ...viz import plot_alignment -from ...transforms import read_trans, write_trans +from ...transforms import (read_trans, write_trans, _ensure_trans, + rotation_angles) from ...utils import get_subjects_dir @@ -167,11 +168,19 @@ def _fit_icp(self): ) self._update() - def _save_trans(self, filename): - write_trans(filename, self._coreg.trans) - - def _load_trans(self, filename): - read_trans(filename) + def _save_trans(self, fname): + write_trans(fname, self._coreg.trans) + + def _load_trans(self, fname): + mri_head_t = _ensure_trans(read_trans(fname, return_all=True), + 'mri', 'head')['trans'] + rot_x, rot_y, rot_z = rotation_angles(mri_head_t) + x, y, z = mri_head_t[:3, 3] + self._coreg._update_params( + rot=[rot_x, rot_y, rot_z], + tra=[x, y, z], + ) + self._update() def _get_subjects(self): # XXX: would be nice to move this function to util From b7ca6095644f89fea52eb7692f0a5fb899ab891f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 15:35:43 +0200 Subject: [PATCH 041/225] proto: icp fid match --- mne/viz/_coreg/_coreg.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 5e07db40ac6..9c0fa669720 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -16,6 +16,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 + self._default_icp_fid_matches = ('nearest', 'matched') self._default_icp_n_iterations = 20 self._default_weights = { "lpa": 1.0, @@ -46,7 +47,12 @@ def _reset_fitting_parameters(self): self._icp_n_iterations = self._default_icp_n_iterations if "icp_n_iterations" in self._widgets: self._widgets["icp_n_iterations"].set_value( - self._default_icp_n_iterations) + self._icp_n_iterations) + + self._icp_fid_match = self._default_icp_fid_matches[0] + if "icp_fid_match" in self._widgets: + self._widgets["icp_fid_match"].set_value( + self._icp_fid_match) for fid in self._default_weights.keys(): widget_name = f"{fid}_weight" @@ -113,6 +119,10 @@ def _toggle_transparent(self, state): self._opacity = 0.4 if state else 1.0 self._update() + def _switch_icp_fid_match(self, method): + self._coreg.set_fid_match(method) + self._update() + def _switch_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir self._subjects = self._get_subjects() @@ -369,6 +379,14 @@ def noop(x): double=False, layout=layout, ) + self._widgets["icp_fid_match"] = self._renderer._dock_add_combo_box( + name="Fiducial point matching", + value=self._default_icp_fid_matches[0], + rng=self._default_icp_fid_matches, + callback=self._switch_icp_fid_match, + compact=True, + layout=layout + ) layout = self._renderer._dock_add_group_box( name="Weights", layout=layout, From 6bbc7713c8189cea1219e1107b5304fd2c895713 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 15:38:38 +0200 Subject: [PATCH 042/225] rename --- mne/viz/_coreg/_coreg.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9c0fa669720..ded480f7482 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -119,11 +119,11 @@ def _toggle_transparent(self, state): self._opacity = 0.4 if state else 1.0 self._update() - def _switch_icp_fid_match(self, method): + def _set_icp_fid_match(self, method): self._coreg.set_fid_match(method) self._update() - def _switch_subjects_dir(self, subjects_dir): + def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir self._subjects = self._get_subjects() self._subject = self._subjects[0] @@ -132,7 +132,7 @@ def _switch_subjects_dir(self, subjects_dir): self._coreg._subject = self._subject self._reset() - def _switch_subject(self, subject): + def _set_subject(self, subject): self._subject = subject # XXX: add coreg.set_subject() self._coreg._subject = subject @@ -214,7 +214,7 @@ def noop(x): self._widgets["subjects_dir"] = self._renderer._dock_add_file_button( name="subjects_dir", desc="Load", - func=self._switch_subjects_dir, + func=self._set_subjects_dir, value=self._subjects_dir, placeholder="Subjects Directory", directory=True, @@ -224,7 +224,7 @@ def noop(x): name="Subject", value=self._subject, rng=self._subjects, - callback=self._switch_subject, + callback=self._set_subject, compact=True, layout=layout ) @@ -383,7 +383,7 @@ def noop(x): name="Fiducial point matching", value=self._default_icp_fid_matches[0], rng=self._default_icp_fid_matches, - callback=self._switch_icp_fid_match, + callback=self._set_icp_fid_match, compact=True, layout=layout ) From dcf4673b8ea0bb23c5cc6ac6b744e24900aa384b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 15:39:50 +0200 Subject: [PATCH 043/225] newline --- mne/viz/_coreg/_coreg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ded480f7482..fd8e607e477 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -357,6 +357,7 @@ def noop(x): decimals=4, layout=hlayout ) + layout = self._renderer._dock_add_group_box("Fitting") hlayout = self._renderer._dock_add_layout(vertical=False) self._renderer._dock_add_button( From ca4c48a41e1464b113851bd4f642877e60f94cac Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 16:33:11 +0200 Subject: [PATCH 044/225] proto: load info [ci skip] --- mne/viz/_coreg/_coreg.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index fd8e607e477..b3f79eefe3d 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,5 +1,6 @@ import os import os.path as op +from ...io import read_info from ...coreg import Coregistration, _is_mri_subject from ...viz import plot_alignment from ...transforms import (read_trans, write_trans, _ensure_trans, @@ -94,7 +95,7 @@ def _update(self): self._renderer.figure.plotter.clear() surfaces = dict() surfaces[self._surface] = self._opacity - plot_alignment(self._info, trans=self._coreg.trans, + plot_alignment(info=self._info, trans=self._coreg.trans, subject=self._subject, subjects_dir=self._subjects_dir, surfaces=surfaces, @@ -135,7 +136,13 @@ def _set_subjects_dir(self, subjects_dir): def _set_subject(self, subject): self._subject = subject # XXX: add coreg.set_subject() - self._coreg._subject = subject + self._coreg._subject = self._subject + self._reset() + + def _set_info_file(self, fname): + self._info = read_info(fname) + # XXX: add coreg.set_info() + self._coreg._info = self._info self._reset() def _set_icp_n_iterations(self, n_iterations): @@ -270,7 +277,7 @@ def noop(x): self._widgets["info_file"] = self._renderer._dock_add_file_button( name="info_file", desc="Load", - func=noop, + func=self._set_info_file, placeholder="Path to info", layout=layout, ) From 1c8f4094dde8f0a24feaa52e79add03d04abf99d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 16:50:01 +0200 Subject: [PATCH 045/225] rename --- mne/viz/_coreg/_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b3f79eefe3d..155aff22c5b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -76,7 +76,7 @@ def _set_lock_fids(self, state): if "fids" in self._widgets: self._widgets["fids"].set_enabled(not state) for coord in ("X", "Y", "Z"): - name = f"dig_{coord}" + name = f"fid_{coord}" if name in self._widgets: self._widgets[name].set_enabled(not state) @@ -260,7 +260,7 @@ def noop(x): ) hlayout = self._renderer._dock_add_layout() for coord in ("X", "Y", "Z"): - name = f"dig_{coord}" + name = f"fid_{coord}" self._widgets[name] = self._renderer._dock_add_spin_box( name=coord, value=0., From a42e55c25b836838c138dbc814de6c33cb0fa09a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 26 Aug 2021 17:20:57 +0200 Subject: [PATCH 046/225] TST: change units to mm [ci skip] --- mne/viz/_coreg/_coreg.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 155aff22c5b..87fa8b164a2 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -68,7 +68,7 @@ def _set_scale_mode(self, mode): self._coreg.set_scale_mode(mode) def _set_omit_hsp_distance(self, distance): - self._omit_hsp_distance = distance + self._omit_hsp_distance = distance / 1000.0 def _set_lock_fids(self, state): if "fid_file" in self._widgets: @@ -110,7 +110,10 @@ def _update(self): if widget_name in self._widgets: idx = coords.index(coord) val = getattr(self._coreg, f"_{tr}") - self._widgets[widget_name].set_value(val[idx]) + val_idx = val[idx] + if tr in ("translation", "scale"): + val_idx *= 1000.0 + self._widgets[widget_name].set_value(val_idx) def _toggle_high_resolution_head(self, state): self._surface = "head-dense" if state else "head" @@ -291,10 +294,10 @@ def noop(x): hlayout = self._renderer._dock_add_layout(vertical=False) self._widgets["omit_distance"] = self._renderer._dock_add_spin_box( name="Omit Distance", - value=0.0, + value=10., rng=[0.0, 100.0], callback=self._set_omit_hsp_distance, - decimals=4, + decimals=1, layout=hlayout, ) self._widgets["omit"] = self._renderer._dock_add_button( @@ -345,7 +348,7 @@ def noop(x): callback=noop, compact=True, double=True, - decimals=4, + decimals=1, layout=hlayout ) @@ -361,7 +364,7 @@ def noop(x): callback=noop, compact=True, double=True, - decimals=4, + decimals=1, layout=hlayout ) From bd6b43531794b25d2c06a348a0e7cef25e0b2d80 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 10:32:13 +0200 Subject: [PATCH 047/225] fix radio buttons [ci skip] --- mne/viz/backends/_notebook.py | 1 + mne/viz/backends/_qt.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index c3339a658d0..f14320624be 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -149,6 +149,7 @@ def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, value=value, disabled=False, ) + widget.observe(_generate_callback(callback), names='value') self._layout_add_widget(layout, widget) # XXX: works but would be nice to use _IpyWidgetList for consistency return _IpyWidget(widget) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index bdd6d75c3bc..67e6e304523 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -166,7 +166,10 @@ def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, button.setChecked(True) group.addButton(button) self._layout_add_widget(group_layout, button) - group.buttonClicked.connect(callback) + + def func(button): + callback(button.text()) + group.buttonClicked.connect(func) self._layout_add_widget(layout, group_layout) return _QtWidgetList(group) From d57a698d56bd5f36372e129be963e06c0aa6ff60 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 11:50:11 +0200 Subject: [PATCH 048/225] proto: display fids --- mne/viz/_coreg/_coreg.py | 25 ++++++++++++++++++------- mne/viz/backends/_abstract.py | 9 +++++---- mne/viz/backends/_notebook.py | 29 ++++++++++++++++++++++------- mne/viz/backends/_qt.py | 20 ++++++++++++++++++-- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 87fa8b164a2..ff5738e9c3b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -17,6 +17,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 + self._default_fiducials = ("LPA", "Nasion", "RPA") self._default_icp_fid_matches = ('nearest', 'matched') self._default_icp_n_iterations = 20 self._default_weights = { @@ -63,6 +64,16 @@ def _reset_fitting_parameters(self): else: setattr(self, f"_{fid}_weight", self._default_weights[fid]) + def _set_fiducial(self, fid): + print("called") + fid = fid.lower() + val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 + coords = ["X", "Y", "Z"] + for coord in coords: + name = f"fid_{coord}" + idx = coords.index(coord) + self._widgets[name].set_value(val[idx]) + def _set_scale_mode(self, mode): mode = None if mode == "None" else mode self._coreg.set_scale_mode(mode) @@ -253,11 +264,10 @@ def noop(x): placeholder="Path to fiducials", layout=layout, ) - fids = ["LPA", "Nasion", "RPA"] self._widgets["fids"] = self._renderer._dock_add_radio_buttons( - value=fids[0], - rng=fids, - callback=noop, + value=self._default_fiducials[0], + rng=self._default_fiducials, + callback=self._set_fiducial, vertical=False, layout=layout, ) @@ -267,14 +277,15 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=coord, value=0., - rng=[0., 1.], + rng=[-100., 100.], callback=noop, compact=True, double=True, layout=hlayout ) + self._widgets["fids"].set_value(0, self._default_fiducials[0]) # init self._renderer._layout_add_widget(layout, hlayout) - self._widgets["lock_fids"].set_value(True) + self._widgets["lock_fids"].set_value(True) # init layout = self._renderer._dock_add_group_box("Digitization Source") self._widgets["info_file"] = self._renderer._dock_add_file_button( @@ -403,7 +414,7 @@ def noop(x): layout=layout, ) hlayout = self._renderer._dock_add_layout(vertical=False) - for fid in fids: + for fid in self._default_fiducials: fid_lower = fid.lower() name = f"{fid}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index ff8530b6d32..7538b79acd2 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -623,12 +623,13 @@ def _layout_add_widget(self, layout, widget, stretch=0): class _AbstractWidgetList(ABC): + @abstractmethod def set_enabled(self, state): - for widget in self._widgets: - widget.set_enabled(state) + pass - def widget(self, idx): - return self._widgets[idx] + @abstractmethod + def set_value(self, idx, value): + pass class _AbstractWidget(ABC): diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index f14320624be..83cfc90b681 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -151,8 +151,7 @@ def _dock_add_radio_buttons(self, value, rng, callback, vertical=True, ) widget.observe(_generate_callback(callback), names='value') self._layout_add_widget(layout, widget) - # XXX: works but would be nice to use _IpyWidgetList for consistency - return _IpyWidget(widget) + return _IpyWidgetList(widget) def _dock_add_group_box(self, name, layout=None): layout = self._dock_layout if layout is None else layout @@ -373,11 +372,27 @@ def _window_set_theme(self, theme): class _IpyWidgetList(_AbstractWidgetList): def __init__(self, src): self._src = src - self._widgets = list() - for widget in src: - if not isinstance(widget, _IpyWidget): - widget = _IpyWidget(widget) - self._widgets.append(widget) + if isinstance(self._src, RadioButtons): + self._widgets = _IpyWidget(self._src) + else: + self._widgets = list() + for widget in self._src: + if not isinstance(widget, _IpyWidget): + widget = _IpyWidget(widget) + self._widgets.append(widget) + + def set_enabled(self, state): + if isinstance(self._src, RadioButtons): + self._widgets.set_enabled(state) + else: + for widget in self._widgets: + widget.set_enabled(state) + + def set_value(self, idx, value): + if isinstance(self._src, RadioButtons): + self._widgets.set_value(value) + else: + self._widgets[idx].set_value(value) class _IpyWidget(_AbstractWidget): diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 67e6e304523..3fae26ac3c3 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -572,12 +572,25 @@ class _QtWidgetList(_AbstractWidgetList): def __init__(self, src): self._src = src self._widgets = list() - widgets = src.buttons() if isinstance(src, QButtonGroup) else src + if isinstance(self._src, QButtonGroup): + widgets = self._src.buttons() + else: + widgets = src for widget in widgets: if not isinstance(widget, _QtWidget): widget = _QtWidget(widget) self._widgets.append(widget) + def set_enabled(self, state): + for widget in self._widgets: + widget.set_enabled(state) + + def set_value(self, idx, value): + if isinstance(self._src, QButtonGroup): + self._widgets[idx].set_value(True) + else: + self._widgets[idx].set_value(value) + class _QtWidget(_AbstractWidget): def set_value(self, value): @@ -586,7 +599,10 @@ def set_value(self, value): elif hasattr(self._widget, "setCurrentText"): self._widget.setCurrentText(value) elif hasattr(self._widget, "setChecked"): - self._widget.setChecked(value) + if isinstance(self._widget, QRadioButton): + self._widget.click() + else: + self._widget.setChecked(value) else: assert hasattr(self._widget, "setText") self._widget.setText(value) From 396ae09d2cb0770c4defc720cb5865cf65776710 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 11:55:15 +0200 Subject: [PATCH 049/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ff5738e9c3b..a6fb29eb23b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -65,7 +65,6 @@ def _reset_fitting_parameters(self): setattr(self, f"_{fid}_weight", self._default_weights[fid]) def _set_fiducial(self, fid): - print("called") fid = fid.lower() val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 coords = ["X", "Y", "Z"] @@ -82,6 +81,8 @@ def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance / 1000.0 def _set_lock_fids(self, state): + if "lock_fids" in self._widgets: + self._widgets["lock_fids"].set_value(state) if "fid_file" in self._widgets: self._widgets["fid_file"].set_enabled(not state) if "fids" in self._widgets: @@ -283,9 +284,9 @@ def noop(x): double=True, layout=hlayout ) - self._widgets["fids"].set_value(0, self._default_fiducials[0]) # init + self._set_fiducial(self._default_fiducials[0]) # init self._renderer._layout_add_widget(layout, hlayout) - self._widgets["lock_fids"].set_value(True) # init + self._set_lock_fids(True) # init layout = self._renderer._dock_add_group_box("Digitization Source") self._widgets["info_file"] = self._renderer._dock_add_file_button( From b8f7eb2ad9ac6f84684f2fbaa60eb058699de119 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 15:41:21 +0200 Subject: [PATCH 050/225] proto: set fiducial file [ci skip] --- mne/viz/_coreg/_coreg.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index a6fb29eb23b..e450239ee3e 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,6 +1,6 @@ import os import os.path as op -from ...io import read_info +from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject from ...viz import plot_alignment from ...transforms import (read_trans, write_trans, _ensure_trans, @@ -64,6 +64,9 @@ def _reset_fitting_parameters(self): else: setattr(self, f"_{fid}_weight", self._default_weights[fid]) + def _reset_fiducials(self): + self._set_fiducial(self._default_fiducials[0]) + def _set_fiducial(self, fid): fid = fid.lower() val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 @@ -97,6 +100,7 @@ def _omit_hsp(self): def _reset(self): self._reset_fitting_parameters() + self._reset_fiducials() self._coreg.reset() self._update() @@ -154,6 +158,11 @@ def _set_subject(self, subject): self._coreg._subject = self._subject self._reset() + def _set_fiducial_file(self, fname): + fids, _ = read_fiducials(fname) + self._coreg._setup_fiducials(fids) + self._reset() + def _set_info_file(self, fname): self._info = read_info(fname) # XXX: add coreg.set_info() @@ -261,7 +270,7 @@ def noop(x): self._widgets["fid_file"] = self._renderer._dock_add_file_button( name="fid_file", desc="Load", - func=noop, + func=self._set_fiducial_file, placeholder="Path to fiducials", layout=layout, ) @@ -282,6 +291,7 @@ def noop(x): callback=noop, compact=True, double=True, + decimals=1, layout=hlayout ) self._set_fiducial(self._default_fiducials[0]) # init From e0f32f1d1c4cff9da4e6a87414f4ea586c056ccb Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 16:35:15 +0200 Subject: [PATCH 051/225] refactor --- mne/viz/_coreg/_coreg.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index e450239ee3e..9b2f068ba7f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -13,7 +13,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._widgets = dict() self._verbose = False - self._first_time = True self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 @@ -43,7 +42,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._configure_dock() self._renderer.show() - self._update() + self._update(clear=False) def _reset_fitting_parameters(self): self._icp_n_iterations = self._default_icp_n_iterations @@ -104,10 +103,8 @@ def _reset(self): self._coreg.reset() self._update() - def _update(self): - if self._first_time: - self._first_time = False - else: + def _update(self, clear=True, update_parameters=True): + if clear: self._renderer.figure.plotter.clear() surfaces = dict() surfaces[self._surface] = self._opacity @@ -119,17 +116,18 @@ def _update(self): coord_frame='meg', fig=self._renderer.figure, show=False, verbose=self._verbose) self._renderer.reset_camera() - coords = ["X", "Y", "Z"] - for tr in ("translation", "rotation", "scale"): - for coord in coords: - widget_name = tr[0] + coord - if widget_name in self._widgets: - idx = coords.index(coord) - val = getattr(self._coreg, f"_{tr}") - val_idx = val[idx] - if tr in ("translation", "scale"): - val_idx *= 1000.0 - self._widgets[widget_name].set_value(val_idx) + if update_parameters: + coords = ["X", "Y", "Z"] + for tr in ("translation", "rotation", "scale"): + for coord in coords: + widget_name = tr[0] + coord + if widget_name in self._widgets: + idx = coords.index(coord) + val = getattr(self._coreg, f"_{tr}") + val_idx = val[idx] + if tr in ("translation", "scale"): + val_idx *= 1000.0 + self._widgets[widget_name].set_value(val_idx) def _toggle_high_resolution_head(self, state): self._surface = "head-dense" if state else "head" From c09c80c8972b95e62c23edf885e879f068549059 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 16:40:48 +0200 Subject: [PATCH 052/225] refactor --- mne/viz/_coreg/_coreg.py | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9b2f068ba7f..674bb76ffad 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -170,23 +170,8 @@ def _set_info_file(self, fname): def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations - def _set_lpa_weight(self, value): - self._lpa_weight = value - - def _set_nasion_weight(self, value): - self._nasion_weight = value - - def _set_rpa_weight(self, value): - self._rpa_weight = value - - def _set_hsp_weight(self, value): - self._hsp_weight = value - - def _set_eeg_weight(self, value): - self._eeg_weight = value - - def _set_hpi_weight(self, value): - self._hpi_weight = value + def _set_point_weight(self, point, weight): + setattr(f"_{point}_weight", weight) def _fit_fiducials(self): self._coreg.fit_fiducials( @@ -430,8 +415,7 @@ def noop(x): name=fid, value=getattr(self, f"_{fid_lower}_weight"), rng=[1., 100.], - # XXX: does not work with lambda+setattr? - callback=getattr(self, f"_set_{fid_lower}_weight"), + callback=lambda x: self._set_point_weight(fid_lower, x), compact=True, double=True, layout=hlayout From 359c47fdccc89088f6c8f4dc7e41e2465f693342 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 27 Aug 2021 16:45:10 +0200 Subject: [PATCH 053/225] refactor --- mne/viz/_coreg/_coreg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 674bb76ffad..5acfc291d56 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -429,8 +429,7 @@ def noop(x): name=point, value=getattr(self, f"_{point_lower}_weight"), rng=[1., 100.], - # XXX: does not work with lambda+setattr? - callback=getattr(self, f"_set_{point_lower}_weight"), + callback=lambda x: self._set_point_weight(fid_lower, x), compact=True, double=True, layout=hlayout From 08d95cb7bb7306acc254a07b88bfb9430d8ec33e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 30 Aug 2021 16:58:27 +0200 Subject: [PATCH 054/225] migrate to traitlets [ci skip] --- mne/viz/_coreg/_coreg.py | 216 ++++++++++++++++++++++++--------------- 1 file changed, 132 insertions(+), 84 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 5acfc291d56..90725b32e4c 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -6,9 +6,20 @@ from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles) from ...utils import get_subjects_dir +from traitlets import observe, HasTraits, Unicode, Bool -class CoregistrationUI(object): +class CoregistrationUI(HasTraits): + _subject = Unicode() + _subjects_dir = Unicode() + _lock_fids = Bool() + _fiducials_file = Unicode() + _current_fiducial = Unicode() + _info_file = Unicode() + _head_resolution = Bool() + _head_transparency = Bool() + _scale_mode = Unicode() + def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._widgets = dict() @@ -29,21 +40,115 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): } self._reset_fitting_parameters() + self._renderer = _get_renderer() + self._renderer._window_close_connect(self._clean) + self._renderer.show() + self._coreg = Coregistration(info, subject, subjects_dir, fids) self._fids = fids + self._info = info self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) - self._subjects = self._get_subjects() self._subject = subject if subject is not None else self._subjects[0] - self._info = info - self._coreg = Coregistration(info, subject, subjects_dir, fids) - self._renderer = _get_renderer() - self._renderer._window_close_connect(self._clean) self._configure_dock() - self._renderer.show() - self._update(clear=False) + def _set_subjects_dir(self, subjects_dir): + self._subjects_dir = subjects_dir + + def _set_subject(self, subject): + self._subject = subject + + def _set_lock_fids(self, state): + self._lock_fids = bool(state) + + def _set_fiducials_file(self, fname): + self._fiducials_file = fname + + def _set_current_fiducial(self, fid): + self._current_fiducial = fid.lower() + + def _set_info_file(self, fname): + self._info_file = fname + + def _set_omit_hsp_distance(self, distance): + self._omit_hsp_distance = distance / 1000.0 + + def _set_head_resolution(self, state): + self._head_resolution = bool(state) + + def _set_head_transparency(self, state): + self._head_transparency = bool(state) + + def _set_scale_mode(self, mode): + self._scale_mode = mode + + @observe("_subjects_dir") + def _subjects_dir_changed(self, change=None): + # XXX: add coreg.set_subjects_dir + self._coreg._subjects_dir = self._subjects_dir + subjects = self._get_subjects() + self._subject = subjects[0] + self._reset() + + @observe("_subject") + def _subject_changed(self, changed=None): + # XXX: add coreg.set_subject() + self._coreg._subject = self._subject + self._reset() + + @observe("_lock_fids") + def _lock_fids_changed(self, change=None): + if "lock_fids" in self._widgets: + self._widgets["lock_fids"].set_value(self._lock_fids) + if "fid_file" in self._widgets: + self._widgets["fid_file"].set_enabled(not self._lock_fids) + if "fids" in self._widgets: + self._widgets["fids"].set_enabled(not self._lock_fids) + for coord in ("X", "Y", "Z"): + name = f"fid_{coord}" + if name in self._widgets: + self._widgets[name].set_enabled(not self._lock_fids) + + @observe("_fiducials_file") + def _fiducials_file_changed(self, change=None): + fids, _ = read_fiducials(self._fiducials_file) + self._coreg._setup_fiducials(fids) + self._reset() + + @observe("_current_fiducial") + def _current_fiducial_changed(self, change=None): + fid = self._current_fiducial + val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 + coords = ["X", "Y", "Z"] + for coord in coords: + name = f"fid_{coord}" + idx = coords.index(coord) + if name in self._widgets: + self._widgets[name].set_value(val[idx]) + + @observe("_info_file") + def _info_file_changed(self, change=None): + self._info = read_info(self._info_file) + # XXX: add coreg.set_info() + self._coreg._info = self._info + self._reset() + + @observe("_head_resolution") + def _head_resolution_changed(self, change=None): + self._surface = "head-dense" if self._head_resolution else "head" + self._update() + + @observe("_head_transparency") + def _head_transparency_changed(self, change=None): + self._opacity = 0.4 if self._head_transparency else 1.0 + self._update() + + @observe("_scale_mode") + def _scale_mode_changed(self, change=None): + mode = None if self._scale_mode == "None" else self._scale_mode + self._coreg.set_scale_mode(mode) + def _reset_fitting_parameters(self): self._icp_n_iterations = self._default_icp_n_iterations if "icp_n_iterations" in self._widgets: @@ -64,35 +169,7 @@ def _reset_fitting_parameters(self): setattr(self, f"_{fid}_weight", self._default_weights[fid]) def _reset_fiducials(self): - self._set_fiducial(self._default_fiducials[0]) - - def _set_fiducial(self, fid): - fid = fid.lower() - val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 - coords = ["X", "Y", "Z"] - for coord in coords: - name = f"fid_{coord}" - idx = coords.index(coord) - self._widgets[name].set_value(val[idx]) - - def _set_scale_mode(self, mode): - mode = None if mode == "None" else mode - self._coreg.set_scale_mode(mode) - - def _set_omit_hsp_distance(self, distance): - self._omit_hsp_distance = distance / 1000.0 - - def _set_lock_fids(self, state): - if "lock_fids" in self._widgets: - self._widgets["lock_fids"].set_value(state) - if "fid_file" in self._widgets: - self._widgets["fid_file"].set_enabled(not state) - if "fids" in self._widgets: - self._widgets["fids"].set_enabled(not state) - for coord in ("X", "Y", "Z"): - name = f"fid_{coord}" - if name in self._widgets: - self._widgets[name].set_enabled(not state) + self._set_current_fiducial(self._default_fiducials[0]) def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance) @@ -108,13 +185,18 @@ def _update(self, clear=True, update_parameters=True): self._renderer.figure.plotter.clear() surfaces = dict() surfaces[self._surface] = self._opacity - plot_alignment(info=self._info, trans=self._coreg.trans, - subject=self._subject, - subjects_dir=self._subjects_dir, - surfaces=surfaces, - dig=True, eeg=[], meg=False, - coord_frame='meg', fig=self._renderer.figure, - show=False, verbose=self._verbose) + kwargs = dict(info=self._info, trans=self._coreg.trans, + subject=self._subject, + subjects_dir=self._subjects_dir, + surfaces=surfaces, + dig=True, eeg=[], meg=False, + coord_frame='meg', fig=self._renderer.figure, + show=False, verbose=self._verbose) + try: + plot_alignment(**kwargs) + except IOError: + kwargs.update(surfaces="head") + plot_alignment(**kwargs) self._renderer.reset_camera() if update_parameters: coords = ["X", "Y", "Z"] @@ -129,44 +211,10 @@ def _update(self, clear=True, update_parameters=True): val_idx *= 1000.0 self._widgets[widget_name].set_value(val_idx) - def _toggle_high_resolution_head(self, state): - self._surface = "head-dense" if state else "head" - self._update() - - def _toggle_transparent(self, state): - self._opacity = 0.4 if state else 1.0 - self._update() - def _set_icp_fid_match(self, method): self._coreg.set_fid_match(method) self._update() - def _set_subjects_dir(self, subjects_dir): - self._subjects_dir = subjects_dir - self._subjects = self._get_subjects() - self._subject = self._subjects[0] - # XXX: add coreg.set_subjects_dir - self._coreg._subjects_dir = self._subjects_dir - self._coreg._subject = self._subject - self._reset() - - def _set_subject(self, subject): - self._subject = subject - # XXX: add coreg.set_subject() - self._coreg._subject = self._subject - self._reset() - - def _set_fiducial_file(self, fname): - fids, _ = read_fiducials(fname) - self._coreg._setup_fiducials(fids) - self._reset() - - def _set_info_file(self, fname): - self._info = read_info(fname) - # XXX: add coreg.set_info() - self._coreg._info = self._info - self._reset() - def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations @@ -237,7 +285,7 @@ def noop(x): self._widgets["subject"] = self._renderer._dock_add_combo_box( name="Subject", value=self._subject, - rng=self._subjects, + rng=self._get_subjects(), callback=self._set_subject, compact=True, layout=layout @@ -253,14 +301,14 @@ def noop(x): self._widgets["fid_file"] = self._renderer._dock_add_file_button( name="fid_file", desc="Load", - func=self._set_fiducial_file, + func=self._set_fiducials_file, placeholder="Path to fiducials", layout=layout, ) self._widgets["fids"] = self._renderer._dock_add_radio_buttons( value=self._default_fiducials[0], rng=self._default_fiducials, - callback=self._set_fiducial, + callback=self._set_current_fiducial, vertical=False, layout=layout, ) @@ -277,7 +325,7 @@ def noop(x): decimals=1, layout=hlayout ) - self._set_fiducial(self._default_fiducials[0]) # init + self._set_current_fiducial(self._default_fiducials[0]) # init self._renderer._layout_add_widget(layout, hlayout) self._set_lock_fids(True) # init @@ -322,13 +370,13 @@ def noop(x): self._widgets["high_res_head"] = self._renderer._dock_add_check_box( name="Show High Resolution Head", value=True, - callback=self._toggle_high_resolution_head, + callback=self._set_head_resolution, layout=layout ) self._widgets["make_transparent"] = self._renderer._dock_add_check_box( name="Make skin surface transparent", value=False, - callback=self._toggle_transparent, + callback=self._set_head_transparency, layout=layout ) self._renderer._dock_add_stretch() From d99d7b64e526fec8572eee3daa7f278fc06e36e3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 31 Aug 2021 15:38:48 +0200 Subject: [PATCH 055/225] migrate to traitlets [ci skip] --- mne/viz/_coreg/_coreg.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 90725b32e4c..b3dbc93b5a6 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -19,6 +19,7 @@ class CoregistrationUI(HasTraits): _head_resolution = Bool() _head_transparency = Bool() _scale_mode = Unicode() + _icp_fid_match = Unicode() def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer @@ -38,7 +39,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): "eeg": 1.0, "hpi": 1.0, } - self._reset_fitting_parameters() self._renderer = _get_renderer() self._renderer._window_close_connect(self._clean) @@ -50,6 +50,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): raise_error=True) self._subject = subject if subject is not None else self._subjects[0] + self._reset_fitting_parameters() self._configure_dock() self._update(clear=False) @@ -83,6 +84,15 @@ def _set_head_transparency(self, state): def _set_scale_mode(self, mode): self._scale_mode = mode + def _set_icp_n_iterations(self, n_iterations): + self._icp_n_iterations = n_iterations + + def _set_icp_fid_match(self, method): + self._icp_fid_match = method + + def _set_point_weight(self, point, weight): + setattr(f"_{point}_weight", weight) + @observe("_subjects_dir") def _subjects_dir_changed(self, change=None): # XXX: add coreg.set_subjects_dir @@ -149,6 +159,11 @@ def _scale_mode_changed(self, change=None): mode = None if self._scale_mode == "None" else self._scale_mode self._coreg.set_scale_mode(mode) + @observe("_icp_fid_match") + def _icp_fid_match_changed(self, change=None): + self._coreg.set_fid_match(self._icp_fid_match) + self._update() + def _reset_fitting_parameters(self): self._icp_n_iterations = self._default_icp_n_iterations if "icp_n_iterations" in self._widgets: @@ -211,16 +226,6 @@ def _update(self, clear=True, update_parameters=True): val_idx *= 1000.0 self._widgets[widget_name].set_value(val_idx) - def _set_icp_fid_match(self, method): - self._coreg.set_fid_match(method) - self._update() - - def _set_icp_n_iterations(self, n_iterations): - self._icp_n_iterations = n_iterations - - def _set_point_weight(self, point, weight): - setattr(f"_{point}_weight", weight) - def _fit_fiducials(self): self._coreg.fit_fiducials( lpa_weight=self._lpa_weight, From 22021255cf3c18dad2eb8e8a2b4b34653474c88d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 1 Sep 2021 10:28:37 +0200 Subject: [PATCH 056/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b3dbc93b5a6..484b54755af 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,5 +1,6 @@ import os import os.path as op +from functools import partial from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject from ...viz import plot_alignment @@ -90,8 +91,8 @@ def _set_icp_n_iterations(self, n_iterations): def _set_icp_fid_match(self, method): self._icp_fid_match = method - def _set_point_weight(self, point, weight): - setattr(f"_{point}_weight", weight) + def _set_point_weight(self, weight, point): + setattr(self, f"_{point}_weight", weight) @observe("_subjects_dir") def _subjects_dir_changed(self, change=None): @@ -165,15 +166,17 @@ def _icp_fid_match_changed(self, change=None): self._update() def _reset_fitting_parameters(self): - self._icp_n_iterations = self._default_icp_n_iterations if "icp_n_iterations" in self._widgets: self._widgets["icp_n_iterations"].set_value( - self._icp_n_iterations) + self._default_icp_n_iterations) + else: + self._icp_n_iterations = self._default_icp_n_iterations - self._icp_fid_match = self._default_icp_fid_matches[0] if "icp_fid_match" in self._widgets: self._widgets["icp_fid_match"].set_value( - self._icp_fid_match) + self._default_icp_fid_matches[0]) + else: + self._icp_fid_match = self._default_icp_fid_matches[0] for fid in self._default_weights.keys(): widget_name = f"{fid}_weight" @@ -463,12 +466,12 @@ def noop(x): hlayout = self._renderer._dock_add_layout(vertical=False) for fid in self._default_fiducials: fid_lower = fid.lower() - name = f"{fid}_weight" + name = f"{fid_lower}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( name=fid, value=getattr(self, f"_{fid_lower}_weight"), rng=[1., 100.], - callback=lambda x: self._set_point_weight(fid_lower, x), + callback=partial(self._set_point_weight, point=fid_lower), compact=True, double=True, layout=hlayout @@ -482,7 +485,7 @@ def noop(x): name=point, value=getattr(self, f"_{point_lower}_weight"), rng=[1., 100.], - callback=lambda x: self._set_point_weight(fid_lower, x), + callback=partial(self._set_point_weight, point=fid_lower), compact=True, double=True, layout=hlayout From dc959a51d818c2c20a12337096e346b9a99ddadf Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 1 Sep 2021 11:39:02 +0200 Subject: [PATCH 057/225] proto: edit parameters [ci skip] --- mne/viz/_coreg/_coreg.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 484b54755af..27bad56ea8b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,5 +1,6 @@ import os import os.path as op +import numpy as np from functools import partial from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject @@ -85,6 +86,25 @@ def _set_head_transparency(self, state): def _set_scale_mode(self, mode): self._scale_mode = mode + def _set_parameter(self, x, mode_name, coord): + params = dict( + rotation=self._coreg._rotation, + translation=self._coreg._translation, + scale=self._coreg._scale, + ) + coords = ["X", "Y", "Z"] + idx = coords.index(coord) + if mode_name == "rotation": + params[mode_name][idx] = np.deg2rad(x) + else: + params[mode_name][idx] = x / 1000.0 + self._coreg._update_params( + rot=params["rotation"], + tra=params["translation"], + sca=params["scale"], + ) + self._update(update_parameters=False) + def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations @@ -421,8 +441,12 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=name, value=0., - rng=[-100., 100.], - callback=noop, + rng=[-1000., 1000.], + callback=partial( + self._set_parameter, + mode_name=mode_name.lower(), + coord=coord, + ), compact=True, double=True, decimals=1, From 6d86c96751df178984421002ae5f7149368ba2f4 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 1 Sep 2021 11:59:55 +0200 Subject: [PATCH 058/225] proto: edit other weights [ci skip] --- mne/coreg.py | 12 +++++++++++- mne/viz/_coreg/_coreg.py | 7 +++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 2c3d0d48257..4d41c54cd40 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -1796,7 +1796,8 @@ def set_fid_match(self, match): @verbose def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., - rpa_weight=1., verbose=None): + rpa_weight=1., hsp_weight=1., eeg_weight=1., hpi_weight=1., + verbose=None): """Find MRI scaling, translation, and rotation to match HSP. Parameters @@ -1809,6 +1810,12 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., Relative weight for nasion. The default value is 10. rpa_weight : float Relative weight for RPA. The default value is 1. + hsp_weight : float + Relative weight for HSP. The default value is 1. + eeg_weight : float + Relative weight for EEG. The default value is 1. + hpi_weight : float + Relative weight for HPI. The default value is 1. %(verbose)s Returns @@ -1822,6 +1829,9 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., self._lpa_weight = lpa_weight self._nasion_weight = nasion_weight self._rpa_weight = rpa_weight + self._hsp_weight = hsp_weight + self._eeg_weight = eeg_weight + self._hsp_weight = hpi_weight # Initial guess (current state) est = self._parameters diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 27bad56ea8b..3cd588c8b0c 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -92,8 +92,7 @@ def _set_parameter(self, x, mode_name, coord): translation=self._coreg._translation, scale=self._coreg._scale, ) - coords = ["X", "Y", "Z"] - idx = coords.index(coord) + idx = ["X", "Y", "Z"].index(coord) if mode_name == "rotation": params[mode_name][idx] = np.deg2rad(x) else: @@ -504,12 +503,12 @@ def noop(x): hlayout = self._renderer._dock_add_layout(vertical=False) for point in ("HSP", "EEG", "HPI"): point_lower = point.lower() - name = f"{point}_weight" + name = f"{point_lower}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( name=point, value=getattr(self, f"_{point_lower}_weight"), rng=[1., 100.], - callback=partial(self._set_point_weight, point=fid_lower), + callback=partial(self._set_point_weight, point=point_lower), compact=True, double=True, layout=hlayout From 2348199db3bfb4122a09387fa70930cb21438cc9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 1 Sep 2021 12:05:52 +0200 Subject: [PATCH 059/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3cd588c8b0c..759e44c264c 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -51,6 +51,11 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) self._subject = subject if subject is not None else self._subjects[0] + self._head_resolution = True + self._icp_n_iterations = self._default_icp_n_iterations + self._icp_fid_match = self._default_icp_fid_matches[0] + for fid in self._default_weights.keys(): + setattr(self, f"_{fid}_weight", self._default_weights[fid]) self._reset_fitting_parameters() self._configure_dock() @@ -188,22 +193,14 @@ def _reset_fitting_parameters(self): if "icp_n_iterations" in self._widgets: self._widgets["icp_n_iterations"].set_value( self._default_icp_n_iterations) - else: - self._icp_n_iterations = self._default_icp_n_iterations - if "icp_fid_match" in self._widgets: self._widgets["icp_fid_match"].set_value( self._default_icp_fid_matches[0]) - else: - self._icp_fid_match = self._default_icp_fid_matches[0] - for fid in self._default_weights.keys(): widget_name = f"{fid}_weight" if widget_name in self._widgets: self._widgets[widget_name].set_value( self._default_weights[fid]) - else: - setattr(self, f"_{fid}_weight", self._default_weights[fid]) def _reset_fiducials(self): self._set_current_fiducial(self._default_fiducials[0]) From 995544067fe328d3dbe88288efa24418a98bc261 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 1 Sep 2021 14:21:00 +0200 Subject: [PATCH 060/225] Fix notebook [ci skip] --- mne/viz/_coreg/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 759e44c264c..ab8f602057c 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -44,7 +44,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._renderer = _get_renderer() self._renderer._window_close_connect(self._clean) - self._renderer.show() self._coreg = Coregistration(info, subject, subjects_dir, fids) self._fids = fids self._info = info @@ -60,6 +59,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._reset_fitting_parameters() self._configure_dock() self._update(clear=False) + self._renderer.show() def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir From dd16992e538e228bd6dd7c62ac19351932816f02 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 7 Sep 2021 15:32:46 +0200 Subject: [PATCH 061/225] tst: plot_head_surface [ci skip] --- mne/viz/_coreg/_coreg.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ab8f602057c..98796238822 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -4,9 +4,9 @@ from functools import partial from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject -from ...viz import plot_alignment +from ...viz._3d import _plot_head_surface from ...transforms import (read_trans, write_trans, _ensure_trans, - rotation_angles) + rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir from traitlets import observe, HasTraits, Unicode, Bool @@ -217,21 +217,20 @@ def _reset(self): def _update(self, clear=True, update_parameters=True): if clear: self._renderer.figure.plotter.clear() - surfaces = dict() - surfaces[self._surface] = self._opacity - kwargs = dict(info=self._info, trans=self._coreg.trans, - subject=self._subject, - subjects_dir=self._subjects_dir, - surfaces=surfaces, - dig=True, eeg=[], meg=False, - coord_frame='meg', fig=self._renderer.figure, - show=False, verbose=self._verbose) + bem = None + coord_frame = 'mri' + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) try: - plot_alignment(**kwargs) + head_actor, _ = _plot_head_surface( + self._renderer, self._surface, self._subject, + self._subjects_dir, bem, coord_frame, to_cf_t, + alpha=self._opacity) except IOError: - kwargs.update(surfaces="head") - plot_alignment(**kwargs) - self._renderer.reset_camera() + head_actor, _ = _plot_head_surface( + self._renderer, "head", self._subject, self._subjects_dir, + bem, coord_frame, to_cf_t, alpha=self._opacity) + del head_actor # XXX: unused for now if update_parameters: coords = ["X", "Y", "Z"] for tr in ("translation", "rotation", "scale"): From 580b96aaf361f5430485bc2b53663bc0585b367b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 8 Sep 2021 15:51:08 +0200 Subject: [PATCH 062/225] add _update_head --- mne/viz/_coreg/_coreg.py | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 98796238822..3c38c6aa0cc 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -42,6 +42,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): "hpi": 1.0, } + self._actors = dict() self._renderer = _get_renderer() self._renderer._window_close_connect(self._clean) self._coreg = Coregistration(info, subject, subjects_dir, fids) @@ -131,6 +132,7 @@ def _subject_changed(self, changed=None): # XXX: add coreg.set_subject() self._coreg._subject = self._subject self._reset() + self._update_head() @observe("_lock_fids") def _lock_fids_changed(self, change=None): @@ -172,12 +174,12 @@ def _info_file_changed(self, change=None): @observe("_head_resolution") def _head_resolution_changed(self, change=None): self._surface = "head-dense" if self._head_resolution else "head" - self._update() + self._update_head() @observe("_head_transparency") def _head_transparency_changed(self, change=None): self._opacity = 0.4 if self._head_transparency else 1.0 - self._update() + self._update_head() @observe("_scale_mode") def _scale_mode_changed(self, change=None): @@ -187,7 +189,6 @@ def _scale_mode_changed(self, change=None): @observe("_icp_fid_match") def _icp_fid_match_changed(self, change=None): self._coreg.set_fid_match(self._icp_fid_match) - self._update() def _reset_fitting_parameters(self): if "icp_n_iterations" in self._widgets: @@ -212,11 +213,10 @@ def _reset(self): self._reset_fitting_parameters() self._reset_fiducials() self._coreg.reset() - self._update() - def _update(self, clear=True, update_parameters=True): - if clear: - self._renderer.figure.plotter.clear() + def _update_head(self): + if "head" in self._actors: + self._renderer.plotter.remove_actor(self._actors["head"]) bem = None coord_frame = 'mri' to_cf_t = _get_transforms_to_coord_frame( @@ -230,19 +230,8 @@ def _update(self, clear=True, update_parameters=True): head_actor, _ = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, bem, coord_frame, to_cf_t, alpha=self._opacity) - del head_actor # XXX: unused for now - if update_parameters: - coords = ["X", "Y", "Z"] - for tr in ("translation", "rotation", "scale"): - for coord in coords: - widget_name = tr[0] + coord - if widget_name in self._widgets: - idx = coords.index(coord) - val = getattr(self._coreg, f"_{tr}") - val_idx = val[idx] - if tr in ("translation", "scale"): - val_idx *= 1000.0 - self._widgets[widget_name].set_value(val_idx) + self._actors["head"] = head_actor + self._renderer._update() def _fit_fiducials(self): self._coreg.fit_fiducials( @@ -251,7 +240,6 @@ def _fit_fiducials(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._update() def _fit_icp(self): self._coreg.fit_icp( @@ -261,7 +249,6 @@ def _fit_icp(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._update() def _save_trans(self, fname): write_trans(fname, self._coreg.trans) @@ -275,7 +262,6 @@ def _load_trans(self, fname): rot=[rot_x, rot_y, rot_z], tra=[x, y, z], ) - self._update() def _get_subjects(self): # XXX: would be nice to move this function to util @@ -541,3 +527,4 @@ def noop(x): def _clean(self): self._renderer = None + self._actors.clear() From 382aaa3f9cc608699a85e2cd8cd2600424b6b096 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 8 Sep 2021 16:06:30 +0200 Subject: [PATCH 063/225] add _add_head_fiducials [ci skip] --- mne/viz/_coreg/_coreg.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3c38c6aa0cc..233df57c3c2 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -2,9 +2,10 @@ import os.path as op import numpy as np from functools import partial +from ...defaults import DEFAULTS from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject -from ...viz._3d import _plot_head_surface +from ...viz._3d import _plot_head_surface, _plot_head_fiducials from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir @@ -59,7 +60,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._reset_fitting_parameters() self._configure_dock() - self._update(clear=False) self._renderer.show() def _set_subjects_dir(self, subjects_dir): @@ -132,7 +132,8 @@ def _subject_changed(self, changed=None): # XXX: add coreg.set_subject() self._coreg._subject = self._subject self._reset() - self._update_head() + self._add_head_surface() + self._add_head_fiducials() @observe("_lock_fids") def _lock_fids_changed(self, change=None): @@ -152,6 +153,7 @@ def _fiducials_file_changed(self, change=None): fids, _ = read_fiducials(self._fiducials_file) self._coreg._setup_fiducials(fids) self._reset() + self._add_head_fiducials() @observe("_current_fiducial") def _current_fiducial_changed(self, change=None): @@ -174,12 +176,13 @@ def _info_file_changed(self, change=None): @observe("_head_resolution") def _head_resolution_changed(self, change=None): self._surface = "head-dense" if self._head_resolution else "head" - self._update_head() + self._add_head_surface() @observe("_head_transparency") def _head_transparency_changed(self, change=None): self._opacity = 0.4 if self._head_transparency else 1.0 - self._update_head() + self._actors["head"].GetProperty().SetOpacity(self._opacity) + self._renderer._update() @observe("_scale_mode") def _scale_mode_changed(self, change=None): @@ -214,7 +217,20 @@ def _reset(self): self._reset_fiducials() self._coreg.reset() - def _update_head(self): + def _add_head_fiducials(self): + if "head_fids" in self._actors: + self._renderer.plotter.remove_actor(self._actors["head_fids"]) + coord_frame = 'mri' + defaults = DEFAULTS['coreg'] + fid_colors = tuple( + defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + head_fids_actors = _plot_head_fiducials( + self._renderer, self._info, to_cf_t, fid_colors) + self._actors["head_fids"] = head_fids_actors + + def _add_head_surface(self): if "head" in self._actors: self._renderer.plotter.remove_actor(self._actors["head"]) bem = None From 90ab1be83052f9045db5f218db5820643bfacdc5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 11:11:59 +0200 Subject: [PATCH 064/225] add traitlets dep --- environment.yml | 1 + requirements.txt | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 825d62619bc..302524bb6eb 100644 --- a/environment.yml +++ b/environment.yml @@ -23,6 +23,7 @@ dependencies: - spyder-kernels>=1.10.0 - imageio-ffmpeg>=0.4.1 - vtk>=9.0.1 +- traitlets - pyvista>=0.30 - pyvistaqt>=0.4 - qdarkstyle diff --git a/requirements.txt b/requirements.txt index ef0e8aee69b..51149b1583b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,10 +28,11 @@ nilearn xlrd imageio>=2.6.1 imageio-ffmpeg>=0.4.1 +trailets pyvista>=0.30 pyvistaqt>=0.4 tqdm mffpy>=0.5.7 ipywidgets ipyvtklink -pooch \ No newline at end of file +pooch From d5d077ac787533e2a77558d8443d2c6fc0f6599c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 11:18:07 +0200 Subject: [PATCH 065/225] fix --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 51149b1583b..6cf607cecd6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,7 +28,7 @@ nilearn xlrd imageio>=2.6.1 imageio-ffmpeg>=0.4.1 -trailets +traitlets pyvista>=0.30 pyvistaqt>=0.4 tqdm From ec548b94b0d68b48536dd13622e7400ab9ade4ba Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 14:27:31 +0200 Subject: [PATCH 066/225] Add _add_head_shape_points [ci skip] --- mne/viz/_coreg/_coreg.py | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 233df57c3c2..b6e91137620 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -5,7 +5,8 @@ from ...defaults import DEFAULTS from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject -from ...viz._3d import _plot_head_surface, _plot_head_fiducials +from ...viz._3d import (_plot_head_surface, _plot_head_fiducials, + _plot_head_shape_points) from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir @@ -19,6 +20,7 @@ class CoregistrationUI(HasTraits): _fiducials_file = Unicode() _current_fiducial = Unicode() _info_file = Unicode() + _head_shape_point = Bool() _head_resolution = Bool() _head_transparency = Bool() _scale_mode = Unicode() @@ -27,7 +29,7 @@ class CoregistrationUI(HasTraits): def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._widgets = dict() - self._verbose = False + self._verbose = True self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 @@ -83,6 +85,9 @@ def _set_info_file(self, fname): def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance / 1000.0 + def _set_head_shape_points(self, state): + self._head_shape_point = bool(state) + def _set_head_resolution(self, state): self._head_resolution = bool(state) @@ -173,6 +178,10 @@ def _info_file_changed(self, change=None): self._coreg._info = self._info self._reset() + @observe("_head_shape_point") + def _head_shape_point_changed(self, change=None): + self._add_head_shape_points() + @observe("_head_resolution") def _head_resolution_changed(self, change=None): self._surface = "head-dense" if self._head_resolution else "head" @@ -230,6 +239,18 @@ def _add_head_fiducials(self): self._renderer, self._info, to_cf_t, fid_colors) self._actors["head_fids"] = head_fids_actors + def _add_head_shape_points(self): + if "head_shape_points" in self._actors: + self._renderer.plotter.remove_actor( + self._actors["head_shape_points"]) + coord_frame = 'mri' + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + head_shape_points = _plot_head_shape_points( + self._renderer, self._info, to_cf_t) + self._actors["head_shape_points"] = head_shape_points + self._renderer._update() + def _add_head_surface(self): if "head" in self._actors: self._renderer.plotter.remove_actor(self._actors["head"]) @@ -389,7 +410,7 @@ def noop(x): self._widgets["show_hsp"] = self._renderer._dock_add_check_box( name="Show Head Shape Points", value=False, - callback=noop, + callback=self._set_head_shape_points, layout=layout ) self._widgets["high_res_head"] = self._renderer._dock_add_check_box( From 11a9bd96b0c88459ec4ce81aee0512508ec09b42 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 14:36:06 +0200 Subject: [PATCH 067/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b6e91137620..83af807c244 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -54,6 +54,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) self._subject = subject if subject is not None else self._subjects[0] + self._head_shape_point = True self._head_resolution = True self._icp_n_iterations = self._default_icp_n_iterations self._icp_fid_match = self._default_icp_fid_matches[0] @@ -238,16 +239,20 @@ def _add_head_fiducials(self): head_fids_actors = _plot_head_fiducials( self._renderer, self._info, to_cf_t, fid_colors) self._actors["head_fids"] = head_fids_actors + self._renderer._update() def _add_head_shape_points(self): if "head_shape_points" in self._actors: self._renderer.plotter.remove_actor( self._actors["head_shape_points"]) - coord_frame = 'mri' - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) - head_shape_points = _plot_head_shape_points( - self._renderer, self._info, to_cf_t) + if self._head_shape_point: + coord_frame = 'mri' + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + head_shape_points = _plot_head_shape_points( + self._renderer, self._info, to_cf_t) + else: + head_shape_points = None self._actors["head_shape_points"] = head_shape_points self._renderer._update() @@ -409,7 +414,7 @@ def noop(x): layout = self._renderer._dock_add_group_box("View") self._widgets["show_hsp"] = self._renderer._dock_add_check_box( name="Show Head Shape Points", - value=False, + value=True, callback=self._set_head_shape_points, layout=layout ) From 4a42281519f93bb249e861e70204c5a62c0a46a0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 14:47:08 +0200 Subject: [PATCH 068/225] fix [ci skip] --- mne/viz/_3d.py | 11 +++++++---- mne/viz/_coreg/_coreg.py | 4 ++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 0a70ff11064..0949babbfde 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -974,14 +974,16 @@ def _plot_mri_fiducials(renderer, mri_fiducials, subjects_dir, subject, def _plot_head_shape_points(renderer, info, to_cf_t): defaults = DEFAULTS['coreg'] + actors = list() hpi_loc = np.array([ d['r'] for d in (info['dig'] or []) if (d['kind'] == FIFF.FIFFV_POINT_HPI and d['coord_frame'] == FIFF.FIFFV_COORD_HEAD)]) hpi_loc = apply_trans(to_cf_t['head'], hpi_loc) - renderer.sphere(center=hpi_loc, color=defaults['hpi_color'], - scale=defaults['hpi_scale'], opacity=0.5, - backface_culling=True) + actor, _ = renderer.sphere(center=hpi_loc, color=defaults['hpi_color'], + scale=defaults['hpi_scale'], opacity=0.5, + backface_culling=True) + actors.append(actor) ext_loc = np.array([ d['r'] for d in (info['dig'] or []) if (d['kind'] == FIFF.FIFFV_POINT_EXTRA and @@ -990,7 +992,8 @@ def _plot_head_shape_points(renderer, info, to_cf_t): actor, _ = renderer.sphere(center=ext_loc, color=defaults['extra_color'], scale=defaults['extra_scale'], opacity=0.25, backface_culling=True) - return actor + actors.append(actor) + return actors def _plot_forward(renderer, fwd, to_cf_t): diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 83af807c244..38255506e9f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -282,6 +282,8 @@ def _fit_fiducials(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) + # XXX: better way to update viz (traits maybe)? + self._head_shape_point_changed() def _fit_icp(self): self._coreg.fit_icp( @@ -291,6 +293,8 @@ def _fit_icp(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) + # XXX: better way to update viz (traits maybe)? + self._head_shape_point_changed() def _save_trans(self, fname): write_trans(fname, self._coreg.trans) From b8abeb3b53d6c6bf11221ada663fb3d3e30abda5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 14:58:13 +0200 Subject: [PATCH 069/225] Add _emit_coreg_modified [ci skip] --- mne/viz/_coreg/_coreg.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 38255506e9f..f5c11ddbb92 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -20,6 +20,7 @@ class CoregistrationUI(HasTraits): _fiducials_file = Unicode() _current_fiducial = Unicode() _info_file = Unicode() + _coreg_modified = Bool() _head_shape_point = Bool() _head_resolution = Bool() _head_transparency = Bool() @@ -88,6 +89,7 @@ def _set_omit_hsp_distance(self, distance): def _set_head_shape_points(self, state): self._head_shape_point = bool(state) + self._emit_coreg_modified() def _set_head_resolution(self, state): self._head_resolution = bool(state) @@ -179,7 +181,7 @@ def _info_file_changed(self, change=None): self._coreg._info = self._info self._reset() - @observe("_head_shape_point") + @observe("_coreg_modified") def _head_shape_point_changed(self, change=None): self._add_head_shape_points() @@ -226,6 +228,10 @@ def _reset(self): self._reset_fitting_parameters() self._reset_fiducials() self._coreg.reset() + self._emit_coreg_modified() + + def _emit_coreg_modified(self): + self._coreg_modified = not self._coreg_modified def _add_head_fiducials(self): if "head_fids" in self._actors: @@ -282,8 +288,7 @@ def _fit_fiducials(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - # XXX: better way to update viz (traits maybe)? - self._head_shape_point_changed() + self._emit_coreg_modified() def _fit_icp(self): self._coreg.fit_icp( @@ -293,8 +298,7 @@ def _fit_icp(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - # XXX: better way to update viz (traits maybe)? - self._head_shape_point_changed() + self._emit_coreg_modified() def _save_trans(self, fname): write_trans(fname, self._coreg.trans) From 10a87859a77a999b7c8b804e632974575ea358e6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 15:02:44 +0200 Subject: [PATCH 070/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index f5c11ddbb92..11e9fc4150c 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -182,6 +182,10 @@ def _info_file_changed(self, change=None): self._reset() @observe("_coreg_modified") + def _update(self, change=None): + self._head_resolution_changed() + + @observe("_head_shape_point") def _head_shape_point_changed(self, change=None): self._add_head_shape_points() From 115b17ed4992bc4f4272160f480c79eb83745634 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Sep 2021 15:04:39 +0200 Subject: [PATCH 071/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 11e9fc4150c..354e75e702d 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -183,7 +183,7 @@ def _info_file_changed(self, change=None): @observe("_coreg_modified") def _update(self, change=None): - self._head_resolution_changed() + self._head_shape_point_changed() @observe("_head_shape_point") def _head_shape_point_changed(self, change=None): From 280b92cd0cd0e2fe513ba70eb2ddfd42950e09d6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 15:45:59 +0200 Subject: [PATCH 072/225] add _plot_hpi_coils [ci skip] --- mne/viz/_3d.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 0949babbfde..3b74d10cb25 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -720,6 +720,7 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, _check_option('dig', dig, (True, False, 'fiducials')) if dig: if dig is True: + _plot_hpi_coils(renderer, info, to_cf_t) _plot_head_shape_points(renderer, info, to_cf_t) _plot_head_fiducials(renderer, info, to_cf_t, fid_colors) @@ -972,9 +973,8 @@ def _plot_mri_fiducials(renderer, mri_fiducials, subjects_dir, subject, return actors -def _plot_head_shape_points(renderer, info, to_cf_t): +def _plot_hpi_coils(renderer, info, to_cf_t): defaults = DEFAULTS['coreg'] - actors = list() hpi_loc = np.array([ d['r'] for d in (info['dig'] or []) if (d['kind'] == FIFF.FIFFV_POINT_HPI and @@ -983,7 +983,11 @@ def _plot_head_shape_points(renderer, info, to_cf_t): actor, _ = renderer.sphere(center=hpi_loc, color=defaults['hpi_color'], scale=defaults['hpi_scale'], opacity=0.5, backface_culling=True) - actors.append(actor) + return actor + + +def _plot_head_shape_points(renderer, info, to_cf_t): + defaults = DEFAULTS['coreg'] ext_loc = np.array([ d['r'] for d in (info['dig'] or []) if (d['kind'] == FIFF.FIFFV_POINT_EXTRA and @@ -992,8 +996,7 @@ def _plot_head_shape_points(renderer, info, to_cf_t): actor, _ = renderer.sphere(center=ext_loc, color=defaults['extra_color'], scale=defaults['extra_scale'], opacity=0.25, backface_culling=True) - actors.append(actor) - return actors + return actor def _plot_forward(renderer, fwd, to_cf_t): From 067947c31a2e017999f573f0c78ac971da30c2d5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:06:31 +0200 Subject: [PATCH 073/225] refactor --- mne/viz/_coreg/_coreg.py | 85 +++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 354e75e702d..9bad4c4c98d 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager import os import os.path as op import numpy as np @@ -237,53 +238,55 @@ def _reset(self): def _emit_coreg_modified(self): self._coreg_modified = not self._coreg_modified - def _add_head_fiducials(self): - if "head_fids" in self._actors: - self._renderer.plotter.remove_actor(self._actors["head_fids"]) - coord_frame = 'mri' - defaults = DEFAULTS['coreg'] - fid_colors = tuple( - defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) - head_fids_actors = _plot_head_fiducials( - self._renderer, self._info, to_cf_t, fid_colors) - self._actors["head_fids"] = head_fids_actors - self._renderer._update() + @contextmanager + def _update_actor(self, actor_name): + if actor_name in self._actors: + self._renderer.plotter.remove_actor(self._actors[actor_name]) + try: + yield + finally: + self._renderer._update() - def _add_head_shape_points(self): - if "head_shape_points" in self._actors: - self._renderer.plotter.remove_actor( - self._actors["head_shape_points"]) - if self._head_shape_point: + def _add_head_fiducials(self): + with self._update_actor("head_fiducials"): coord_frame = 'mri' + defaults = DEFAULTS['coreg'] + fid_colors = tuple( + defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=coord_frame) - head_shape_points = _plot_head_shape_points( - self._renderer, self._info, to_cf_t) - else: - head_shape_points = None - self._actors["head_shape_points"] = head_shape_points - self._renderer._update() + head_fids_actors = _plot_head_fiducials( + self._renderer, self._info, to_cf_t, fid_colors) + self._actors["head_fiducials"] = head_fids_actors + + def _add_head_shape_points(self): + with self._update_actor("head_shape_points"): + if self._head_shape_point: + coord_frame = 'mri' + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + head_shape_points = _plot_head_shape_points( + self._renderer, self._info, to_cf_t) + else: + head_shape_points = None + self._actors["head_shape_points"] = head_shape_points def _add_head_surface(self): - if "head" in self._actors: - self._renderer.plotter.remove_actor(self._actors["head"]) - bem = None - coord_frame = 'mri' - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) - try: - head_actor, _ = _plot_head_surface( - self._renderer, self._surface, self._subject, - self._subjects_dir, bem, coord_frame, to_cf_t, - alpha=self._opacity) - except IOError: - head_actor, _ = _plot_head_surface( - self._renderer, "head", self._subject, self._subjects_dir, - bem, coord_frame, to_cf_t, alpha=self._opacity) - self._actors["head"] = head_actor - self._renderer._update() + with self._update_actor("head_surface"): + bem = None + coord_frame = 'mri' + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + try: + head_actor, _ = _plot_head_surface( + self._renderer, self._surface, self._subject, + self._subjects_dir, bem, coord_frame, to_cf_t, + alpha=self._opacity) + except IOError: + head_actor, _ = _plot_head_surface( + self._renderer, "head", self._subject, self._subjects_dir, + bem, coord_frame, to_cf_t, alpha=self._opacity) + self._actors["head_surface"] = head_actor def _fit_fiducials(self): self._coreg.fit_fiducials( From 263b81e1490156fe9195b5f65ad6c1780f623f02 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:11:10 +0200 Subject: [PATCH 074/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 77 ++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9bad4c4c98d..af3c995806d 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,4 +1,3 @@ -from contextlib import contextmanager import os import os.path as op import numpy as np @@ -198,7 +197,7 @@ def _head_resolution_changed(self, change=None): @observe("_head_transparency") def _head_transparency_changed(self, change=None): self._opacity = 0.4 if self._head_transparency else 1.0 - self._actors["head"].GetProperty().SetOpacity(self._opacity) + self._actors["head_surface"].GetProperty().SetOpacity(self._opacity) self._renderer._update() @observe("_scale_mode") @@ -238,55 +237,49 @@ def _reset(self): def _emit_coreg_modified(self): self._coreg_modified = not self._coreg_modified - @contextmanager - def _update_actor(self, actor_name): + def _update_actor(self, actor_name, actor): if actor_name in self._actors: self._renderer.plotter.remove_actor(self._actors[actor_name]) - try: - yield - finally: - self._renderer._update() + self._renderer._update() + self._actors[actor_name] = actor def _add_head_fiducials(self): - with self._update_actor("head_fiducials"): - coord_frame = 'mri' - defaults = DEFAULTS['coreg'] - fid_colors = tuple( - defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) - head_fids_actors = _plot_head_fiducials( - self._renderer, self._info, to_cf_t, fid_colors) - self._actors["head_fiducials"] = head_fids_actors + coord_frame = 'mri' + defaults = DEFAULTS['coreg'] + fid_colors = tuple( + defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + head_fids_actors = _plot_head_fiducials( + self._renderer, self._info, to_cf_t, fid_colors) + self._update_actor("head_fiducials", head_fids_actors) def _add_head_shape_points(self): - with self._update_actor("head_shape_points"): - if self._head_shape_point: - coord_frame = 'mri' - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) - head_shape_points = _plot_head_shape_points( - self._renderer, self._info, to_cf_t) - else: - head_shape_points = None - self._actors["head_shape_points"] = head_shape_points - - def _add_head_surface(self): - with self._update_actor("head_surface"): - bem = None + if self._head_shape_point: coord_frame = 'mri' to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=coord_frame) - try: - head_actor, _ = _plot_head_surface( - self._renderer, self._surface, self._subject, - self._subjects_dir, bem, coord_frame, to_cf_t, - alpha=self._opacity) - except IOError: - head_actor, _ = _plot_head_surface( - self._renderer, "head", self._subject, self._subjects_dir, - bem, coord_frame, to_cf_t, alpha=self._opacity) - self._actors["head_surface"] = head_actor + hsp_actors = _plot_head_shape_points( + self._renderer, self._info, to_cf_t) + else: + hsp_actors = None + self._update_actor("head_shape_points", hsp_actors) + + def _add_head_surface(self): + bem = None + coord_frame = 'mri' + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + try: + head_actor, _ = _plot_head_surface( + self._renderer, self._surface, self._subject, + self._subjects_dir, bem, coord_frame, to_cf_t, + alpha=self._opacity) + except IOError: + head_actor, _ = _plot_head_surface( + self._renderer, "head", self._subject, self._subjects_dir, + bem, coord_frame, to_cf_t, alpha=self._opacity) + self._update_actor("head_surface", head_actor) def _fit_fiducials(self): self._coreg.fit_fiducials( From c9fdf53d9d64cd0da5e0a538f3195e51cb4de96e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:14:09 +0200 Subject: [PATCH 075/225] nitpick --- mne/viz/_coreg/_coreg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index af3c995806d..bdf9b787b33 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -238,8 +238,7 @@ def _emit_coreg_modified(self): self._coreg_modified = not self._coreg_modified def _update_actor(self, actor_name, actor): - if actor_name in self._actors: - self._renderer.plotter.remove_actor(self._actors[actor_name]) + self._renderer.plotter.remove_actor(self._actors.get(actor_name)) self._renderer._update() self._actors[actor_name] = actor From cc6ba70d6ea236237251dcbf78fc6c047e12582c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:15:17 +0200 Subject: [PATCH 076/225] nitpick --- mne/viz/_coreg/_coreg.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index bdf9b787b33..2a93035b560 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -239,7 +239,6 @@ def _emit_coreg_modified(self): def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) - self._renderer._update() self._actors[actor_name] = actor def _add_head_fiducials(self): From 2ae04e152d73ec1c0bddb0564e22eb4a055cebb2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:31:58 +0200 Subject: [PATCH 077/225] add _add_mri_fiducials [ci skip] --- mne/viz/_coreg/_coreg.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 2a93035b560..4d8c589f736 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -6,7 +6,7 @@ from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject from ...viz._3d import (_plot_head_surface, _plot_head_fiducials, - _plot_head_shape_points) + _plot_head_shape_points, _plot_mri_fiducials) from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir @@ -140,8 +140,6 @@ def _subject_changed(self, changed=None): # XXX: add coreg.set_subject() self._coreg._subject = self._subject self._reset() - self._add_head_surface() - self._add_head_fiducials() @observe("_lock_fids") def _lock_fids_changed(self, change=None): @@ -161,7 +159,6 @@ def _fiducials_file_changed(self, change=None): fids, _ = read_fiducials(self._fiducials_file) self._coreg._setup_fiducials(fids) self._reset() - self._add_head_fiducials() @observe("_current_fiducial") def _current_fiducial_changed(self, change=None): @@ -183,7 +180,9 @@ def _info_file_changed(self, change=None): @observe("_coreg_modified") def _update(self, change=None): - self._head_shape_point_changed() + self._add_head_shape_points() + self._add_head_fiducials() + self._add_mri_fiducials() @observe("_head_shape_point") def _head_shape_point_changed(self, change=None): @@ -241,6 +240,20 @@ def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) self._actors[actor_name] = actor + def _add_mri_fiducials(self): + # XXX: Need a better sanity check + if len(self._fiducials_file) > 0: + coord_frame = 'mri' + defaults = DEFAULTS['coreg'] + fid_colors = tuple( + defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=coord_frame) + mri_fids_actors = _plot_mri_fiducials( + self._renderer, self._fiducials_file, self._subjects_dir, + self._subject, to_cf_t, fid_colors) + self._update_actor("mri_fiducials", mri_fids_actors) + def _add_head_fiducials(self): coord_frame = 'mri' defaults = DEFAULTS['coreg'] From 4f2c39b21d532180a02ef034a61e5e1ce293971e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:39:33 +0200 Subject: [PATCH 078/225] refactor --- mne/viz/_coreg/_coreg.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 4d8c589f736..3f537dae71b 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -31,6 +31,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._widgets = dict() self._verbose = True + self._coord_frame = "mri" self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 @@ -243,33 +244,30 @@ def _update_actor(self, actor_name, actor): def _add_mri_fiducials(self): # XXX: Need a better sanity check if len(self._fiducials_file) > 0: - coord_frame = 'mri' defaults = DEFAULTS['coreg'] fid_colors = tuple( defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) + self._info, self._coreg.trans, coord_frame=self._coord_frame) mri_fids_actors = _plot_mri_fiducials( self._renderer, self._fiducials_file, self._subjects_dir, self._subject, to_cf_t, fid_colors) self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): - coord_frame = 'mri' defaults = DEFAULTS['coreg'] fid_colors = tuple( defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) + self._info, self._coreg.trans, coord_frame=self._coord_frame) head_fids_actors = _plot_head_fiducials( self._renderer, self._info, to_cf_t, fid_colors) self._update_actor("head_fiducials", head_fids_actors) def _add_head_shape_points(self): if self._head_shape_point: - coord_frame = 'mri' to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) + self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( self._renderer, self._info, to_cf_t) else: @@ -278,18 +276,17 @@ def _add_head_shape_points(self): def _add_head_surface(self): bem = None - coord_frame = 'mri' to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=coord_frame) + self._info, self._coreg.trans, coord_frame=self._coord_frame) try: head_actor, _ = _plot_head_surface( self._renderer, self._surface, self._subject, - self._subjects_dir, bem, coord_frame, to_cf_t, + self._subjects_dir, bem, self._coord_frame, to_cf_t, alpha=self._opacity) except IOError: head_actor, _ = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, - bem, coord_frame, to_cf_t, alpha=self._opacity) + bem, self._coord_frame, to_cf_t, alpha=self._opacity) self._update_actor("head_surface", head_actor) def _fit_fiducials(self): From 167fab5a76d7affcd1d2375e80c6e2a93ab2979d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:40:16 +0200 Subject: [PATCH 079/225] improve mri fids [ci skip] --- mne/viz/_coreg/_coreg.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3f537dae71b..ece2145f1d9 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -244,15 +244,18 @@ def _update_actor(self, actor_name, actor): def _add_mri_fiducials(self): # XXX: Need a better sanity check if len(self._fiducials_file) > 0: - defaults = DEFAULTS['coreg'] - fid_colors = tuple( - defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) - mri_fids_actors = _plot_mri_fiducials( - self._renderer, self._fiducials_file, self._subjects_dir, - self._subject, to_cf_t, fid_colors) - self._update_actor("mri_fiducials", mri_fids_actors) + mri_fiducials = self._fiducials_file + else: + mri_fiducials = "estimated" + defaults = DEFAULTS['coreg'] + fid_colors = tuple( + defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=self._coord_frame) + mri_fids_actors = _plot_mri_fiducials( + self._renderer, mri_fiducials, self._subjects_dir, + self._subject, to_cf_t, fid_colors) + self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): defaults = DEFAULTS['coreg'] From f26cf65c6c7edc9b7313116ac4522b8edeb5bd3f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:52:29 +0200 Subject: [PATCH 080/225] improve mri fids [ci skip] --- mne/viz/_3d.py | 5 ++++- mne/viz/_coreg/_coreg.py | 7 +------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 3b74d10cb25..30650cb17a4 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -955,7 +955,10 @@ def _plot_mri_fiducials(renderer, mri_fiducials, subjects_dir, subject, mri_fiducials, cf = read_fiducials(mri_fiducials) if cf != FIFF.FIFFV_COORD_MRI: raise ValueError("Fiducials are not in MRI space") - fid_loc = _fiducial_coords(mri_fiducials, FIFF.FIFFV_COORD_MRI) + if isinstance(mri_fiducials, np.ndarray): + fid_loc = mri_fiducials + else: + fid_loc = _fiducial_coords(mri_fiducials, FIFF.FIFFV_COORD_MRI) fid_loc = apply_trans(to_cf_t['mri'], fid_loc) transform = np.eye(4) transform[:3, :3] = to_cf_t['mri']['trans'][:3, :3] * \ diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ece2145f1d9..1db2f239e42 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -242,18 +242,13 @@ def _update_actor(self, actor_name, actor): self._actors[actor_name] = actor def _add_mri_fiducials(self): - # XXX: Need a better sanity check - if len(self._fiducials_file) > 0: - mri_fiducials = self._fiducials_file - else: - mri_fiducials = "estimated" defaults = DEFAULTS['coreg'] fid_colors = tuple( defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) mri_fids_actors = _plot_mri_fiducials( - self._renderer, mri_fiducials, self._subjects_dir, + self._renderer, self._coreg._fid_points, self._subjects_dir, self._subject, to_cf_t, fid_colors) self._update_actor("mri_fiducials", mri_fids_actors) From 6f79a84909f89dce403932268099fb3729f6858e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 16:58:37 +0200 Subject: [PATCH 081/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 1db2f239e42..697d26f8d15 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -35,6 +35,9 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 + self._fid_colors = tuple( + DEFAULTS['coreg'][f'{key}_color'] for key in + ('lpa', 'nasion', 'rpa')) self._default_fiducials = ("LPA", "Nasion", "RPA") self._default_icp_fid_matches = ('nearest', 'matched') self._default_icp_n_iterations = 20 @@ -242,24 +245,18 @@ def _update_actor(self, actor_name, actor): self._actors[actor_name] = actor def _add_mri_fiducials(self): - defaults = DEFAULTS['coreg'] - fid_colors = tuple( - defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) mri_fids_actors = _plot_mri_fiducials( self._renderer, self._coreg._fid_points, self._subjects_dir, - self._subject, to_cf_t, fid_colors) + self._subject, to_cf_t, self._fid_colors) self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): - defaults = DEFAULTS['coreg'] - fid_colors = tuple( - defaults[f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) head_fids_actors = _plot_head_fiducials( - self._renderer, self._info, to_cf_t, fid_colors) + self._renderer, self._info, to_cf_t, self._fid_colors) self._update_actor("head_fiducials", head_fids_actors) def _add_head_shape_points(self): From d2db266eada58ebac11bd7761c13b610ee3ce1d0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Sep 2021 17:11:22 +0200 Subject: [PATCH 082/225] remove cruft [ci skip] --- mne/viz/_3d.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 30650cb17a4..22f714d06ae 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -423,7 +423,7 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, meg=None, eeg='original', fwd=None, dig=False, ecog=True, src=None, mri_fiducials=False, bem=None, seeg=True, fnirs=True, show_axes=False, dbs=True, - fig=None, interaction='trackball', show=True, verbose=None): + fig=None, interaction='trackball', verbose=None): """Plot head, sensor, and source space alignment in 3D. Parameters @@ -780,9 +780,7 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, renderer.set_camera(azimuth=90, elevation=90, distance=0.6, focalpoint=(0., 0., 0.)) - # XXX: temporary workaround - if show: - renderer.show() + renderer.show() return renderer.scene() From 34897663141ee7565b7a43268aef298f6868b33b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 29 Sep 2021 14:32:38 +0200 Subject: [PATCH 083/225] add _grow_hair_changed [ci skip] --- mne/viz/_3d.py | 2 +- mne/viz/_coreg/_coreg.py | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 22f714d06ae..921c2c1471a 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -891,7 +891,7 @@ def _plot_head_surface(renderer, head, subject, subjects_dir, bem, surf = transform_surface_to( surf, coord_frame, [to_cf_t['mri'], to_cf_t['head']], copy=True) - actor, _ = renderer.surface( + actor, surf = renderer.surface( surface=surf, color=color, opacity=alpha, backface_culling=False) return actor, surf diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 697d26f8d15..b67751efd98 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -10,7 +10,7 @@ from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir -from traitlets import observe, HasTraits, Unicode, Bool +from traitlets import observe, HasTraits, Unicode, Bool, Float class CoregistrationUI(HasTraits): @@ -24,6 +24,7 @@ class CoregistrationUI(HasTraits): _head_shape_point = Bool() _head_resolution = Bool() _head_transparency = Bool() + _grow_hair = Float() _scale_mode = Unicode() _icp_fid_match = Unicode() @@ -51,6 +52,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): } self._actors = dict() + self._surfaces = dict() self._renderer = _get_renderer() self._renderer._window_close_connect(self._clean) self._coreg = Coregistration(info, subject, subjects_dir, fids) @@ -101,6 +103,9 @@ def _set_head_resolution(self, state): def _set_head_transparency(self, state): self._head_transparency = bool(state) + def _set_grow_hair(self, value): + self._grow_hair = value + def _set_scale_mode(self, mode): self._scale_mode = mode @@ -196,11 +201,21 @@ def _head_shape_point_changed(self, change=None): def _head_resolution_changed(self, change=None): self._surface = "head-dense" if self._head_resolution else "head" self._add_head_surface() + self._grow_hair_changed() @observe("_head_transparency") def _head_transparency_changed(self, change=None): self._opacity = 0.4 if self._head_transparency else 1.0 - self._actors["head_surface"].GetProperty().SetOpacity(self._opacity) + self._actors["head"].GetProperty().SetOpacity(self._opacity) + self._renderer._update() + + @observe("_grow_hair") + def _grow_hair_changed(self, change=None): + self._coreg.set_grow_hair(self._grow_hair) + if "head" in self._surfaces: + res = "high" if self._head_resolution else "low" + self._surfaces["head"].points = \ + self._coreg._get_processed_mri_points(res) self._renderer._update() @observe("_scale_mode") @@ -274,15 +289,16 @@ def _add_head_surface(self): to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) try: - head_actor, _ = _plot_head_surface( + head_actor, head_surf = _plot_head_surface( self._renderer, self._surface, self._subject, self._subjects_dir, bem, self._coord_frame, to_cf_t, alpha=self._opacity) except IOError: - head_actor, _ = _plot_head_surface( + head_actor, head_surf = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, bem, self._coord_frame, to_cf_t, alpha=self._opacity) - self._update_actor("head_surface", head_actor) + self._update_actor("head", head_actor) + self._surfaces["head"] = head_surf def _fit_fiducials(self): self._coreg.fit_fiducials( @@ -403,7 +419,7 @@ def noop(x): name="Grow Hair", value=0.0, rng=[0.0, 10.0], - callback=noop, + callback=self._set_grow_hair, layout=layout, ) hlayout = self._renderer._dock_add_layout(vertical=False) @@ -581,3 +597,4 @@ def noop(x): def _clean(self): self._renderer = None self._actors.clear() + self._surfaces.clear() From 0cecb6815fa8813ec39cfd4477a3aa1666ea3a97 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 29 Sep 2021 14:39:48 +0200 Subject: [PATCH 084/225] fix --- mne/viz/_coreg/_coreg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b67751efd98..91099c016a4 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -331,6 +331,7 @@ def _load_trans(self, fname): rot=[rot_x, rot_y, rot_z], tra=[x, y, z], ) + self._emit_coreg_modified() def _get_subjects(self): # XXX: would be nice to move this function to util From 36ad5bec12e6ce57c5756a6b9986267adbe3ed16 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 29 Sep 2021 14:43:50 +0200 Subject: [PATCH 085/225] change bgcolor [ci skip] --- mne/viz/_coreg/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 91099c016a4..cfe7d141eb8 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -53,7 +53,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._actors = dict() self._surfaces = dict() - self._renderer = _get_renderer() + self._renderer = _get_renderer(bgcolor="grey") self._renderer._window_close_connect(self._clean) self._coreg = Coregistration(info, subject, subjects_dir, fids) self._fids = fids From 690d2a0f591d7b648b17aba26893a401a303fa1e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 29 Sep 2021 14:55:26 +0200 Subject: [PATCH 086/225] prepare lock_fids UX [ci skip] --- mne/viz/_coreg/_coreg.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index cfe7d141eb8..a5f435a8ec3 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -152,6 +152,12 @@ def _subject_changed(self, changed=None): @observe("_lock_fids") def _lock_fids_changed(self, change=None): + if "show_hsp" in self._widgets: + if self._lock_fids: + self._widgets["show_hsp"].set_enabled(True) + else: + self._widgets["show_hsp"].set_value(False) + self._widgets["show_hsp"].set_enabled(False) if "lock_fids" in self._widgets: self._widgets["lock_fids"].set_value(self._lock_fids) if "fid_file" in self._widgets: From 26d8708ca70c30201dbc12244d20f25c357e2791 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 29 Sep 2021 17:04:45 +0200 Subject: [PATCH 087/225] prepare picking [ci skip] --- mne/viz/_coreg/_coreg.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index a5f435a8ec3..8108eda9d7a 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -33,6 +33,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._widgets = dict() self._verbose = True self._coord_frame = "mri" + self._mouse_no_mvt = -1 self._omit_hsp_distance = 0.0 self._surface = "head-dense" self._opacity = 1.0 @@ -70,8 +71,38 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._reset_fitting_parameters() self._configure_dock() + self._configure_picking() self._renderer.show() + def _configure_picking(self): + self._renderer._update_picking_callback( + self._on_mouse_move, + self._on_button_press, + self._on_button_release, + self._on_pick + ) + + def _on_mouse_move(self, vtk_picker, event): + if self._mouse_no_mvt: + self._mouse_no_mvt -= 1 + + def _on_button_press(self, vtk_picker, event): + self._mouse_no_mvt = 2 + + def _on_button_release(self, vtk_picker, event): + if self._mouse_no_mvt > 0: + x, y = vtk_picker.GetEventPosition() + # XXX: plotter/renderer should not be exposed if possible + plotter = self._renderer.figure.plotter + picked_renderer = self._renderer.figure.plotter.renderer + # trigger the pick + plotter.picker.Pick(x, y, 0, picked_renderer) + self._mouse_no_mvt = 0 + + def _on_pick(self, vtk_picker, event): + # XXX: for debug only + print("picked!") + def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir From 14c750618c16fcfa3b117fc1d8138a179411b2b0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 29 Sep 2021 17:10:05 +0200 Subject: [PATCH 088/225] enable cell -> vertex picking [ci skip] --- mne/viz/_coreg/_coreg.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 8108eda9d7a..d2337772c6e 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -100,8 +100,22 @@ def _on_button_release(self, vtk_picker, event): self._mouse_no_mvt = 0 def _on_pick(self, vtk_picker, event): + if self._lock_fids: + return + # XXX: taken from Brain, can be refactored + cell_id = vtk_picker.GetCellId() + mesh = vtk_picker.GetDataSet() + if mesh is None or cell_id == -1 or not self._mouse_no_mvt: + return + pos = np.array(vtk_picker.GetPickPosition()) + vtk_cell = mesh.GetCell(cell_id) + cell = [vtk_cell.GetPointId(point_id) for point_id + in range(vtk_cell.GetNumberOfPoints())] + vertices = mesh.points[cell] + idx = np.argmin(abs(vertices - pos), axis=0) + vertex_id = cell[idx[0]] # XXX: for debug only - print("picked!") + print(vertex_id) def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir From 77d8c1718b035f804b31c8c6e66c2aa272be3ad2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 11:23:05 +0200 Subject: [PATCH 089/225] display picking msg [ci skip] --- mne/viz/_coreg/_coreg.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index d2337772c6e..92ed43f6300 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -31,6 +31,7 @@ class CoregistrationUI(HasTraits): def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer self._widgets = dict() + self._fids_to_pick = list() self._verbose = True self._coord_frame = "mri" self._mouse_no_mvt = -1 @@ -81,6 +82,7 @@ def _configure_picking(self): self._on_button_release, self._on_pick ) + self._actors["msg"] = self._renderer.text2d(0, 0, "") def _on_mouse_move(self, vtk_picker, event): if self._mouse_no_mvt: @@ -102,6 +104,8 @@ def _on_button_release(self, vtk_picker, event): def _on_pick(self, vtk_picker, event): if self._lock_fids: return + if len(self._fid_to_pick) == 0: + return # XXX: taken from Brain, can be refactored cell_id = vtk_picker.GetCellId() mesh = vtk_picker.GetDataSet() @@ -115,7 +119,14 @@ def _on_pick(self, vtk_picker, event): idx = np.argmin(abs(vertices - pos), axis=0) vertex_id = cell[idx[0]] # XXX: for debug only - print(vertex_id) + fid = self._fid_to_pick.pop() + print(fid, vertex_id) + if len(self._fid_to_pick) == 0: + self._actors["msg"].SetInput("") + else: + next_fid = self._fid_to_pick[-1].upper() + self._actors["msg"].SetInput(f"Picking {next_fid}...") + self._renderer._update() def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir @@ -203,6 +214,10 @@ def _lock_fids_changed(self, change=None): else: self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) + self._fid_to_pick = ["lpa", "nasion", "rpa"] + next_fid = self._fid_to_pick[-1].upper() + self._actors["msg"].SetInput(f"Picking {next_fid}...") + self._renderer._update() if "lock_fids" in self._widgets: self._widgets["lock_fids"].set_value(self._lock_fids) if "fid_file" in self._widgets: From 2f56ebcb2a7384995e2b93543ef18723cf8dd733 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 11:24:27 +0200 Subject: [PATCH 090/225] rename [ci skip] --- mne/viz/_coreg/_coreg.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 92ed43f6300..d22654f8857 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -104,7 +104,7 @@ def _on_button_release(self, vtk_picker, event): def _on_pick(self, vtk_picker, event): if self._lock_fids: return - if len(self._fid_to_pick) == 0: + if len(self._fids_to_pick) == 0: return # XXX: taken from Brain, can be refactored cell_id = vtk_picker.GetCellId() @@ -119,12 +119,12 @@ def _on_pick(self, vtk_picker, event): idx = np.argmin(abs(vertices - pos), axis=0) vertex_id = cell[idx[0]] # XXX: for debug only - fid = self._fid_to_pick.pop() + fid = self._fids_to_pick.pop() print(fid, vertex_id) - if len(self._fid_to_pick) == 0: + if len(self._fids_to_pick) == 0: self._actors["msg"].SetInput("") else: - next_fid = self._fid_to_pick[-1].upper() + next_fid = self._fids_to_pick[-1].upper() self._actors["msg"].SetInput(f"Picking {next_fid}...") self._renderer._update() @@ -214,8 +214,8 @@ def _lock_fids_changed(self, change=None): else: self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) - self._fid_to_pick = ["lpa", "nasion", "rpa"] - next_fid = self._fid_to_pick[-1].upper() + self._fids_to_pick = ["lpa", "nasion", "rpa"] + next_fid = self._fids_to_pick[-1].upper() self._actors["msg"].SetInput(f"Picking {next_fid}...") self._renderer._update() if "lock_fids" in self._widgets: From 188e039d58fde061df4219ca3103062da3884458 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 11:25:47 +0200 Subject: [PATCH 091/225] nitpick --- mne/viz/_coreg/_coreg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index d22654f8857..b82a56c08c9 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -118,8 +118,9 @@ def _on_pick(self, vtk_picker, event): vertices = mesh.points[cell] idx = np.argmin(abs(vertices - pos), axis=0) vertex_id = cell[idx[0]] - # XXX: for debug only + fid = self._fids_to_pick.pop() + # XXX: for debug only print(fid, vertex_id) if len(self._fids_to_pick) == 0: self._actors["msg"].SetInput("") From 6b8eb5be782b93806c61caa81793ce1d6f19464b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 11:27:35 +0200 Subject: [PATCH 092/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b82a56c08c9..9ea79dbc294 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -125,7 +125,7 @@ def _on_pick(self, vtk_picker, event): if len(self._fids_to_pick) == 0: self._actors["msg"].SetInput("") else: - next_fid = self._fids_to_pick[-1].upper() + next_fid = self._fids_to_pick[-1] self._actors["msg"].SetInput(f"Picking {next_fid}...") self._renderer._update() @@ -215,8 +215,8 @@ def _lock_fids_changed(self, change=None): else: self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) - self._fids_to_pick = ["lpa", "nasion", "rpa"] - next_fid = self._fids_to_pick[-1].upper() + self._fids_to_pick = list(self._default_fiducials) + next_fid = self._fids_to_pick[-1] self._actors["msg"].SetInput(f"Picking {next_fid}...") self._renderer._update() if "lock_fids" in self._widgets: From 7b55b8cab9e4b990f0146731c7188e8e7e8b21d7 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 15:10:17 +0200 Subject: [PATCH 093/225] improve picking [ci skip] --- mne/viz/_coreg/_coreg.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9ea79dbc294..7f9f21c5665 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -119,15 +119,17 @@ def _on_pick(self, vtk_picker, event): idx = np.argmin(abs(vertices - pos), axis=0) vertex_id = cell[idx[0]] - fid = self._fids_to_pick.pop() - # XXX: for debug only - print(fid, vertex_id) + fid = self._fids_to_pick.pop(0) # guaranteed previously + idx = self._default_fiducials.index(fid) + # XXX: add coreg.set_fids + self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] + self._coreg._reset_fiducials() if len(self._fids_to_pick) == 0: self._actors["msg"].SetInput("") else: - next_fid = self._fids_to_pick[-1] + next_fid = self._fids_to_pick[0] self._actors["msg"].SetInput(f"Picking {next_fid}...") - self._renderer._update() + self._emit_coreg_modified() def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir @@ -216,7 +218,7 @@ def _lock_fids_changed(self, change=None): self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) self._fids_to_pick = list(self._default_fiducials) - next_fid = self._fids_to_pick[-1] + next_fid = self._fids_to_pick[0] self._actors["msg"].SetInput(f"Picking {next_fid}...") self._renderer._update() if "lock_fids" in self._widgets: From c8742e2657bf6678c0a20e2a09021626a4bad163 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 15:17:08 +0200 Subject: [PATCH 094/225] improve UX consistency [ci skip] --- mne/viz/_coreg/_coreg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 7f9f21c5665..7649ae84982 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -214,9 +214,11 @@ def _lock_fids_changed(self, change=None): if "show_hsp" in self._widgets: if self._lock_fids: self._widgets["show_hsp"].set_enabled(True) + self._widgets["high_res_head"].set_enabled(True) else: self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) + self._widgets["high_res_head"].set_enabled(False) self._fids_to_pick = list(self._default_fiducials) next_fid = self._fids_to_pick[0] self._actors["msg"].SetInput(f"Picking {next_fid}...") From aebade487e9cf8f612e18f03190a93bc6f1592db Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 15:31:33 +0200 Subject: [PATCH 095/225] add _add_hpi_coils [ci skip] --- mne/viz/_coreg/_coreg.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 7649ae84982..08dcec8d292 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -6,7 +6,8 @@ from ...io import read_info, read_fiducials from ...coreg import Coregistration, _is_mri_subject from ...viz._3d import (_plot_head_surface, _plot_head_fiducials, - _plot_head_shape_points, _plot_mri_fiducials) + _plot_head_shape_points, _plot_mri_fiducials, + _plot_hpi_coils) from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir @@ -21,6 +22,7 @@ class CoregistrationUI(HasTraits): _current_fiducial = Unicode() _info_file = Unicode() _coreg_modified = Bool() + _hpi_coils = Bool() _head_shape_point = Bool() _head_resolution = Bool() _head_transparency = Bool() @@ -63,6 +65,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) self._subject = subject if subject is not None else self._subjects[0] + self._hpi_coils = True self._head_shape_point = True self._head_resolution = True self._icp_n_iterations = self._default_icp_n_iterations @@ -152,9 +155,11 @@ def _set_info_file(self, fname): def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance / 1000.0 + def _set_hpi_coils(self, state): + self._hpi_coils = bool(state) + def _set_head_shape_points(self, state): self._head_shape_point = bool(state) - self._emit_coreg_modified() def _set_head_resolution(self, state): self._head_resolution = bool(state) @@ -260,10 +265,15 @@ def _info_file_changed(self, change=None): @observe("_coreg_modified") def _update(self, change=None): + self._add_hpi_coils() self._add_head_shape_points() self._add_head_fiducials() self._add_mri_fiducials() + @observe("_hpi_coils") + def _hpi_coils_changed(self, change=None): + self._add_hpi_coils() + @observe("_head_shape_point") def _head_shape_point_changed(self, change=None): self._add_head_shape_points() @@ -329,6 +339,7 @@ def _emit_coreg_modified(self): def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) self._actors[actor_name] = actor + self._renderer._update() def _add_mri_fiducials(self): to_cf_t = _get_transforms_to_coord_frame( @@ -345,6 +356,16 @@ def _add_head_fiducials(self): self._renderer, self._info, to_cf_t, self._fid_colors) self._update_actor("head_fiducials", head_fids_actors) + def _add_hpi_coils(self): + if self._hpi_coils: + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=self._coord_frame) + hpi_actors = _plot_hpi_coils( + self._renderer, self._info, to_cf_t) + else: + hpi_actors = None + self._update_actor("hpi_coils", hpi_actors) + def _add_head_shape_points(self): if self._head_shape_point: to_cf_t = _get_transforms_to_coord_frame( @@ -511,6 +532,12 @@ def noop(x): self._renderer._layout_add_widget(layout, hlayout) layout = self._renderer._dock_add_group_box("View") + self._widgets["show_hpi"] = self._renderer._dock_add_check_box( + name="Show HPI Coils", + value=True, + callback=self._set_hpi_coils, + layout=layout + ) self._widgets["show_hsp"] = self._renderer._dock_add_check_box( name="Show Head Shape Points", value=True, From 7f960a365e5b1fb1080a6a207f8021993196057a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 15:32:45 +0200 Subject: [PATCH 096/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 08dcec8d292..0bc8e3f53e5 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -32,6 +32,8 @@ class CoregistrationUI(HasTraits): def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): from ..backends.renderer import _get_renderer + self._actors = dict() + self._surfaces = dict() self._widgets = dict() self._fids_to_pick = list() self._verbose = True @@ -55,8 +57,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): "hpi": 1.0, } - self._actors = dict() - self._surfaces = dict() self._renderer = _get_renderer(bgcolor="grey") self._renderer._window_close_connect(self._clean) self._coreg = Coregistration(info, subject, subjects_dir, fids) @@ -695,5 +695,6 @@ def noop(x): def _clean(self): self._renderer = None + self._widgets.clear() self._actors.clear() self._surfaces.clear() From 9f3548d4b8a9023d53793460e940d68505ad0f23 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 15:36:18 +0200 Subject: [PATCH 097/225] improve UX consistency [ci skip] --- mne/viz/_coreg/_coreg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0bc8e3f53e5..6642bd68de9 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -218,16 +218,20 @@ def _subject_changed(self, changed=None): def _lock_fids_changed(self, change=None): if "show_hsp" in self._widgets: if self._lock_fids: + self._widgets["show_hpi"].set_enabled(True) self._widgets["show_hsp"].set_enabled(True) self._widgets["high_res_head"].set_enabled(True) + self._actors["msg"].SetInput("") else: + self._widgets["show_hpi"].set_value(False) + self._widgets["show_hpi"].set_enabled(False) self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) self._widgets["high_res_head"].set_enabled(False) self._fids_to_pick = list(self._default_fiducials) next_fid = self._fids_to_pick[0] self._actors["msg"].SetInput(f"Picking {next_fid}...") - self._renderer._update() + self._renderer._update() if "lock_fids" in self._widgets: self._widgets["lock_fids"].set_value(self._lock_fids) if "fid_file" in self._widgets: From e086cf6d528c240782be95aa69b5b2c6e939ee2f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 30 Sep 2021 15:57:50 +0200 Subject: [PATCH 098/225] improve UX consistency [ci skip] --- mne/viz/_coreg/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 6642bd68de9..c1e696f17ce 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -128,7 +128,7 @@ def _on_pick(self, vtk_picker, event): self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() if len(self._fids_to_pick) == 0: - self._actors["msg"].SetInput("") + self._lock_fids = True else: next_fid = self._fids_to_pick[0] self._actors["msg"].SetInput(f"Picking {next_fid}...") From 4fb40abf8dafd72108eba4eccb3c4a0692a2ed66 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 14:38:41 +0200 Subject: [PATCH 099/225] prepare for glyph scaling [ci skip] --- mne/viz/_3d.py | 59 +++++++++++++++++++++++++++++++++--- mne/viz/_coreg/_coreg.py | 7 ++++- mne/viz/backends/_pyvista.py | 2 ++ 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 921c2c1471a..9943bae05ee 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -32,7 +32,7 @@ SourceSpaces, read_freesurfer_lut) from ..surface import (get_meg_helmet_surf, _read_mri_surface, _DistanceQuery, - _project_onto_surface, _reorder_ccw) + _project_onto_surface, _reorder_ccw, _CheckInside) from ..transforms import (apply_trans, rot_to_quat, combine_transforms, _get_trans, _ensure_trans, Transform, rotation, read_ras_mni_t, _print_coord_trans, _find_trans, @@ -987,16 +987,65 @@ def _plot_hpi_coils(renderer, info, to_cf_t): return actor -def _plot_head_shape_points(renderer, info, to_cf_t): +def _get_nearest(nearest, check_inside, project_to_trans, proj_rr): + idx = nearest.query(proj_rr)[1] + proj_pts = apply_trans( + project_to_trans, nearest.data[idx]) + proj_nn = apply_trans( + project_to_trans, check_inside.surf['nn'][idx], + move=False) + return proj_pts, proj_nn + + +def _plot_head_shape_points(renderer, info, to_cf_t, mode="sphere", + surf=None, rr=None): defaults = DEFAULTS['coreg'] ext_loc = np.array([ d['r'] for d in (info['dig'] or []) if (d['kind'] == FIFF.FIFFV_POINT_EXTRA and d['coord_frame'] == FIFF.FIFFV_COORD_HEAD)]) ext_loc = apply_trans(to_cf_t['head'], ext_loc) - actor, _ = renderer.sphere(center=ext_loc, color=defaults['extra_color'], - scale=defaults['extra_scale'], opacity=0.25, - backface_culling=True) + if mode == "sphere": + actor, _ = renderer.sphere(center=ext_loc, + color=defaults['extra_color'], + scale=defaults['extra_scale'], opacity=0.25, + backface_culling=True) + else: + mark_inside = True + project_to_surface = False + check_inside = _CheckInside(surf) + nearest = _DistanceQuery(rr) + project_to_trans = np.eye(4) + glyph_height = defaults['eegp_height'] + glyph_center = (0., -defaults['eegp_height'], 0) + glyph_resolution = 16 + + pts = ext_loc + inv_trans = np.linalg.inv(project_to_trans) + proj_rr = apply_trans(inv_trans, pts) + proj_pts, proj_nn = _get_nearest( + nearest, check_inside, project_to_trans, proj_rr) + vec = pts - proj_pts # point to the surface + nn = proj_nn + if mark_inside and not project_to_surface: + scalars = (~check_inside(proj_rr, verbose=False)).astype(int) + else: + scalars = np.ones(len(pts)) + dist = np.linalg.norm(vec, axis=-1, keepdims=True) + vectors = (250 * dist + 1) * nn + + scale = 0.005 + color = "white" + x, y, z = ext_loc.T + u, v, w = vectors.T + actor, _ = renderer.quiver3d( + x, y, z, u, v, w, color, scale, mode, resolution=8, + glyph_height=glyph_height, glyph_center=glyph_center, + glyph_resolution=glyph_resolution, + opacity=1.0, scale_mode='vector', scalars=scalars, + backface_culling=False, line_width=2., name=None, + glyph_width=None, glyph_depth=None, + solid_transform=None) return actor diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index c1e696f17ce..3f568e3c71a 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -371,11 +371,16 @@ def _add_hpi_coils(self): self._update_actor("hpi_coils", hpi_actors) def _add_head_shape_points(self): + rr = (self._coreg._processed_low_res_mri_points * + self._coreg._scale) + surf = dict(rr=rr, tris=self._coreg._bem_low_res["tris"], + nn=self._coreg._bem_low_res["nn"]) if self._head_shape_point: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( - self._renderer, self._info, to_cf_t) + self._renderer, self._info, to_cf_t, mode="cylinder", + surf=surf, rr=rr) else: hsp_actors = None self._update_actor("head_shape_points", hsp_actors) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 196adc630ad..e90fe59924c 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -465,6 +465,8 @@ def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8, if scale_mode == 'scalar': _point_data(grid)['mag'] = np.array(scalars) scale = 'mag' + elif scale_mode == 'vector': + scale = True else: scale = False if mode == '2darrow': From 723a92692f0bffb3a848018fc9d0b51a6006998d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 14:52:33 +0200 Subject: [PATCH 100/225] add _orient_glyphs_changed --- mne/viz/_3d.py | 21 +++++++++------------ mne/viz/_coreg/_coreg.py | 19 ++++++++++++++++++- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 9943bae05ee..b3eb1d9abc6 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1005,10 +1005,12 @@ def _plot_head_shape_points(renderer, info, to_cf_t, mode="sphere", if (d['kind'] == FIFF.FIFFV_POINT_EXTRA and d['coord_frame'] == FIFF.FIFFV_COORD_HEAD)]) ext_loc = apply_trans(to_cf_t['head'], ext_loc) + color = defaults['extra_color'] + scale = defaults['extra_scale'] + opacity = 0.25 if mode == "sphere": - actor, _ = renderer.sphere(center=ext_loc, - color=defaults['extra_color'], - scale=defaults['extra_scale'], opacity=0.25, + actor, _ = renderer.sphere(center=ext_loc, color=color, + scale=scale, opacity=opacity, backface_culling=True) else: mark_inside = True @@ -1018,7 +1020,7 @@ def _plot_head_shape_points(renderer, info, to_cf_t, mode="sphere", project_to_trans = np.eye(4) glyph_height = defaults['eegp_height'] glyph_center = (0., -defaults['eegp_height'], 0) - glyph_resolution = 16 + resolution = glyph_resolution = 16 pts = ext_loc inv_trans = np.linalg.inv(project_to_trans) @@ -1034,18 +1036,13 @@ def _plot_head_shape_points(renderer, info, to_cf_t, mode="sphere", dist = np.linalg.norm(vec, axis=-1, keepdims=True) vectors = (250 * dist + 1) * nn - scale = 0.005 - color = "white" x, y, z = ext_loc.T u, v, w = vectors.T actor, _ = renderer.quiver3d( - x, y, z, u, v, w, color, scale, mode, resolution=8, + x, y, z, u, v, w, color=color, scale=scale, mode=mode, glyph_height=glyph_height, glyph_center=glyph_center, - glyph_resolution=glyph_resolution, - opacity=1.0, scale_mode='vector', scalars=scalars, - backface_culling=False, line_width=2., name=None, - glyph_width=None, glyph_depth=None, - solid_transform=None) + resolution=resolution, glyph_resolution=glyph_resolution, + opacity=opacity, scale_mode='vector', scalars=scalars) return actor diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3f568e3c71a..539dc470813 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -22,6 +22,7 @@ class CoregistrationUI(HasTraits): _current_fiducial = Unicode() _info_file = Unicode() _coreg_modified = Bool() + _orient_glyphs = Bool() _hpi_coils = Bool() _head_shape_point = Bool() _head_resolution = Bool() @@ -37,6 +38,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._widgets = dict() self._fids_to_pick = list() self._verbose = True + self._glyph_mode = "sphere" self._coord_frame = "mri" self._mouse_no_mvt = -1 self._omit_hsp_distance = 0.0 @@ -65,6 +67,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) self._subject = subject if subject is not None else self._subjects[0] + self._orient_glyphs = False self._hpi_coils = True self._head_shape_point = True self._head_resolution = True @@ -155,6 +158,9 @@ def _set_info_file(self, fname): def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance / 1000.0 + def _set_orient_glyphs(self, state): + self._orient_glyphs = bool(state) + def _set_hpi_coils(self, state): self._hpi_coils = bool(state) @@ -274,6 +280,11 @@ def _update(self, change=None): self._add_head_fiducials() self._add_mri_fiducials() + @observe("_orient_glyphs") + def _orient_glyphs_changed(self, change=None): + self._glyph_mode = "cylinder" if self._orient_glyphs else "sphere" + self._add_head_shape_points() + @observe("_hpi_coils") def _hpi_coils_changed(self, change=None): self._add_hpi_coils() @@ -379,7 +390,7 @@ def _add_head_shape_points(self): to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( - self._renderer, self._info, to_cf_t, mode="cylinder", + self._renderer, self._info, to_cf_t, mode=self._glyph_mode, surf=surf, rr=rr) else: hsp_actors = None @@ -541,6 +552,12 @@ def noop(x): self._renderer._layout_add_widget(layout, hlayout) layout = self._renderer._dock_add_group_box("View") + self._widgets["orient_glyphs"] = self._renderer._dock_add_check_box( + name="Orient glyphs", + value=False, + callback=self._set_orient_glyphs, + layout=layout + ) self._widgets["show_hpi"] = self._renderer._dock_add_check_box( name="Show HPI Coils", value=True, From d051353195bac4169357cfd7932dff75aaac6a6e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 14:53:59 +0200 Subject: [PATCH 101/225] improve UX consistency --- mne/viz/_coreg/_coreg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 539dc470813..b8a04f77fbb 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -224,11 +224,13 @@ def _subject_changed(self, changed=None): def _lock_fids_changed(self, change=None): if "show_hsp" in self._widgets: if self._lock_fids: + self._widgets["orient_glyphs"].set_enabled(True) self._widgets["show_hpi"].set_enabled(True) self._widgets["show_hsp"].set_enabled(True) self._widgets["high_res_head"].set_enabled(True) self._actors["msg"].SetInput("") else: + self._widgets["orient_glyphs"].set_enabled(False) self._widgets["show_hpi"].set_value(False) self._widgets["show_hpi"].set_enabled(False) self._widgets["show_hsp"].set_value(False) From 79ea34b990dce0be381adecf33fe0bb5cb50338e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 14:56:58 +0200 Subject: [PATCH 102/225] fix opacity [ci skip] --- mne/viz/_3d.py | 9 ++++----- mne/viz/_coreg/_coreg.py | 6 +++--- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index b3eb1d9abc6..dbb17f95b4a 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -974,7 +974,7 @@ def _plot_mri_fiducials(renderer, mri_fiducials, subjects_dir, subject, return actors -def _plot_hpi_coils(renderer, info, to_cf_t): +def _plot_hpi_coils(renderer, info, to_cf_t, opacity=0.5): defaults = DEFAULTS['coreg'] hpi_loc = np.array([ d['r'] for d in (info['dig'] or []) @@ -982,7 +982,7 @@ def _plot_hpi_coils(renderer, info, to_cf_t): d['coord_frame'] == FIFF.FIFFV_COORD_HEAD)]) hpi_loc = apply_trans(to_cf_t['head'], hpi_loc) actor, _ = renderer.sphere(center=hpi_loc, color=defaults['hpi_color'], - scale=defaults['hpi_scale'], opacity=0.5, + scale=defaults['hpi_scale'], opacity=opacity, backface_culling=True) return actor @@ -997,8 +997,8 @@ def _get_nearest(nearest, check_inside, project_to_trans, proj_rr): return proj_pts, proj_nn -def _plot_head_shape_points(renderer, info, to_cf_t, mode="sphere", - surf=None, rr=None): +def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, + mode="sphere", surf=None, rr=None): defaults = DEFAULTS['coreg'] ext_loc = np.array([ d['r'] for d in (info['dig'] or []) @@ -1007,7 +1007,6 @@ def _plot_head_shape_points(renderer, info, to_cf_t, mode="sphere", ext_loc = apply_trans(to_cf_t['head'], ext_loc) color = defaults['extra_color'] scale = defaults['extra_scale'] - opacity = 0.25 if mode == "sphere": actor, _ = renderer.sphere(center=ext_loc, color=color, scale=scale, opacity=opacity, diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b8a04f77fbb..2f8c04e75da 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -378,7 +378,7 @@ def _add_hpi_coils(self): to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hpi_actors = _plot_hpi_coils( - self._renderer, self._info, to_cf_t) + self._renderer, self._info, to_cf_t, opacity=1.0) else: hpi_actors = None self._update_actor("hpi_coils", hpi_actors) @@ -392,8 +392,8 @@ def _add_head_shape_points(self): to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( - self._renderer, self._info, to_cf_t, mode=self._glyph_mode, - surf=surf, rr=rr) + self._renderer, self._info, to_cf_t, opacity=1.0, + mode=self._glyph_mode, surf=surf, rr=rr) else: hsp_actors = None self._update_actor("head_shape_points", hsp_actors) From a48740267b9c1ddb06fcaa0c8756d99558bc6f2e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 15:15:33 +0200 Subject: [PATCH 103/225] orient hpi coils too --- mne/viz/_3d.py | 81 +++++++++++++++++++++++++--------------- mne/viz/_coreg/_coreg.py | 12 ++++-- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index dbb17f95b4a..2ff3c7cab61 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -974,16 +974,31 @@ def _plot_mri_fiducials(renderer, mri_fiducials, subjects_dir, subject, return actors -def _plot_hpi_coils(renderer, info, to_cf_t, opacity=0.5): +def _plot_hpi_coils(renderer, info, to_cf_t, opacity=0.5, + orient_glyphs=False, surf=None): defaults = DEFAULTS['coreg'] hpi_loc = np.array([ d['r'] for d in (info['dig'] or []) if (d['kind'] == FIFF.FIFFV_POINT_HPI and d['coord_frame'] == FIFF.FIFFV_COORD_HEAD)]) hpi_loc = apply_trans(to_cf_t['head'], hpi_loc) - actor, _ = renderer.sphere(center=hpi_loc, color=defaults['hpi_color'], - scale=defaults['hpi_scale'], opacity=opacity, - backface_culling=True) + color = defaults['hpi_color'] + scale = defaults['hpi_scale'] + if orient_glyphs: + glyph_height = defaults['eegp_height'] + glyph_center = (0., -defaults['eegp_height'], 0) + resolution = glyph_resolution = 16 + scalars, vectors = _orient_glyphs(hpi_loc, surf) + x, y, z = hpi_loc.T + u, v, w = vectors.T + actor, _ = renderer.quiver3d( + x, y, z, u, v, w, color=color, scale=scale, mode="cylinder", + glyph_height=glyph_height, glyph_center=glyph_center, + resolution=resolution, glyph_resolution=glyph_resolution, + opacity=opacity, scale_mode='vector', scalars=scalars) + else: + actor, _ = renderer.sphere(center=hpi_loc, color=color, scale=scale, + opacity=opacity, backface_culling=True) return actor @@ -997,8 +1012,31 @@ def _get_nearest(nearest, check_inside, project_to_trans, proj_rr): return proj_pts, proj_nn +def _orient_glyphs(pts, surf): + mark_inside = True + project_to_surface = False + rr = surf["rr"] + check_inside = _CheckInside(surf) + nearest = _DistanceQuery(rr) + project_to_trans = np.eye(4) + + inv_trans = np.linalg.inv(project_to_trans) + proj_rr = apply_trans(inv_trans, pts) + proj_pts, proj_nn = _get_nearest( + nearest, check_inside, project_to_trans, proj_rr) + vec = pts - proj_pts # point to the surface + nn = proj_nn + if mark_inside and not project_to_surface: + scalars = (~check_inside(proj_rr, verbose=False)).astype(int) + else: + scalars = np.ones(len(pts)) + dist = np.linalg.norm(vec, axis=-1, keepdims=True) + vectors = (250 * dist + 1) * nn + return scalars, vectors + + def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, - mode="sphere", surf=None, rr=None): + orient_glyphs=False, surf=None): defaults = DEFAULTS['coreg'] ext_loc = np.array([ d['r'] for d in (info['dig'] or []) @@ -1007,41 +1045,22 @@ def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, ext_loc = apply_trans(to_cf_t['head'], ext_loc) color = defaults['extra_color'] scale = defaults['extra_scale'] - if mode == "sphere": - actor, _ = renderer.sphere(center=ext_loc, color=color, - scale=scale, opacity=opacity, - backface_culling=True) - else: - mark_inside = True - project_to_surface = False - check_inside = _CheckInside(surf) - nearest = _DistanceQuery(rr) - project_to_trans = np.eye(4) + if orient_glyphs: glyph_height = defaults['eegp_height'] glyph_center = (0., -defaults['eegp_height'], 0) resolution = glyph_resolution = 16 - - pts = ext_loc - inv_trans = np.linalg.inv(project_to_trans) - proj_rr = apply_trans(inv_trans, pts) - proj_pts, proj_nn = _get_nearest( - nearest, check_inside, project_to_trans, proj_rr) - vec = pts - proj_pts # point to the surface - nn = proj_nn - if mark_inside and not project_to_surface: - scalars = (~check_inside(proj_rr, verbose=False)).astype(int) - else: - scalars = np.ones(len(pts)) - dist = np.linalg.norm(vec, axis=-1, keepdims=True) - vectors = (250 * dist + 1) * nn - + scalars, vectors = _orient_glyphs(ext_loc, surf) x, y, z = ext_loc.T u, v, w = vectors.T actor, _ = renderer.quiver3d( - x, y, z, u, v, w, color=color, scale=scale, mode=mode, + x, y, z, u, v, w, color=color, scale=scale, mode="cylinder", glyph_height=glyph_height, glyph_center=glyph_center, resolution=resolution, glyph_resolution=glyph_resolution, opacity=opacity, scale_mode='vector', scalars=scalars) + else: + actor, _ = renderer.sphere(center=ext_loc, color=color, + scale=scale, opacity=opacity, + backface_culling=True) return actor diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 2f8c04e75da..de6aa5ff898 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -38,7 +38,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._widgets = dict() self._fids_to_pick = list() self._verbose = True - self._glyph_mode = "sphere" self._coord_frame = "mri" self._mouse_no_mvt = -1 self._omit_hsp_distance = 0.0 @@ -284,7 +283,7 @@ def _update(self, change=None): @observe("_orient_glyphs") def _orient_glyphs_changed(self, change=None): - self._glyph_mode = "cylinder" if self._orient_glyphs else "sphere" + self._add_hpi_coils() self._add_head_shape_points() @observe("_hpi_coils") @@ -374,11 +373,16 @@ def _add_head_fiducials(self): self._update_actor("head_fiducials", head_fids_actors) def _add_hpi_coils(self): + rr = (self._coreg._processed_low_res_mri_points * + self._coreg._scale) + surf = dict(rr=rr, tris=self._coreg._bem_low_res["tris"], + nn=self._coreg._bem_low_res["nn"]) if self._hpi_coils: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hpi_actors = _plot_hpi_coils( - self._renderer, self._info, to_cf_t, opacity=1.0) + self._renderer, self._info, to_cf_t, opacity=1.0, + orient_glyphs=self._orient_glyphs, surf=surf) else: hpi_actors = None self._update_actor("hpi_coils", hpi_actors) @@ -393,7 +397,7 @@ def _add_head_shape_points(self): self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( self._renderer, self._info, to_cf_t, opacity=1.0, - mode=self._glyph_mode, surf=surf, rr=rr) + orient_glyphs=self._orient_glyphs, surf=surf) else: hsp_actors = None self._update_actor("head_shape_points", hsp_actors) From 0f91e5aa8b19701facd8e5851504c87afd76b1c4 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 15:27:02 +0200 Subject: [PATCH 104/225] fix --- mne/viz/_coreg/_coreg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index de6aa5ff898..b63f747a119 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -217,6 +217,8 @@ def _subjects_dir_changed(self, change=None): def _subject_changed(self, changed=None): # XXX: add coreg.set_subject() self._coreg._subject = self._subject + self._coreg._setup_bem() + self._coreg._setup_fiducials(self._fids) self._reset() @observe("_lock_fids") @@ -276,6 +278,7 @@ def _info_file_changed(self, change=None): @observe("_coreg_modified") def _update(self, change=None): + self._add_head_surface() self._add_hpi_coils() self._add_head_shape_points() self._add_head_fiducials() From c7a8917d2a7582adf83917dc6f82dfafed249027 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 1 Oct 2021 15:30:57 +0200 Subject: [PATCH 105/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b63f747a119..422e35a07cb 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -38,6 +38,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._widgets = dict() self._fids_to_pick = list() self._verbose = True + self._head_geo = None self._coord_frame = "mri" self._mouse_no_mvt = -1 self._omit_hsp_distance = 0.0 @@ -220,6 +221,10 @@ def _subject_changed(self, changed=None): self._coreg._setup_bem() self._coreg._setup_fiducials(self._fids) self._reset() + rr = (self._coreg._processed_low_res_mri_points * + self._coreg._scale) + self._head_geo = dict(rr=rr, tris=self._coreg._bem_low_res["tris"], + nn=self._coreg._bem_low_res["nn"]) @observe("_lock_fids") def _lock_fids_changed(self, change=None): @@ -376,31 +381,23 @@ def _add_head_fiducials(self): self._update_actor("head_fiducials", head_fids_actors) def _add_hpi_coils(self): - rr = (self._coreg._processed_low_res_mri_points * - self._coreg._scale) - surf = dict(rr=rr, tris=self._coreg._bem_low_res["tris"], - nn=self._coreg._bem_low_res["nn"]) if self._hpi_coils: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hpi_actors = _plot_hpi_coils( self._renderer, self._info, to_cf_t, opacity=1.0, - orient_glyphs=self._orient_glyphs, surf=surf) + orient_glyphs=self._orient_glyphs, surf=self._head_geo) else: hpi_actors = None self._update_actor("hpi_coils", hpi_actors) def _add_head_shape_points(self): - rr = (self._coreg._processed_low_res_mri_points * - self._coreg._scale) - surf = dict(rr=rr, tris=self._coreg._bem_low_res["tris"], - nn=self._coreg._bem_low_res["nn"]) if self._head_shape_point: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( self._renderer, self._info, to_cf_t, opacity=1.0, - orient_glyphs=self._orient_glyphs, surf=surf) + orient_glyphs=self._orient_glyphs, surf=self._head_geo) else: hsp_actors = None self._update_actor("head_shape_points", hsp_actors) From fb4dce823a514f433bc301fbfa6f04a9a2c99971 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 09:43:07 +0200 Subject: [PATCH 106/225] update picking UX [ci skip] --- mne/viz/_coreg/_coreg.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 422e35a07cb..055e05e5b6f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -36,7 +36,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._actors = dict() self._surfaces = dict() self._widgets = dict() - self._fids_to_pick = list() self._verbose = True self._head_geo = None self._coord_frame = "mri" @@ -110,8 +109,6 @@ def _on_button_release(self, vtk_picker, event): def _on_pick(self, vtk_picker, event): if self._lock_fids: return - if len(self._fids_to_pick) == 0: - return # XXX: taken from Brain, can be refactored cell_id = vtk_picker.GetCellId() mesh = vtk_picker.GetDataSet() @@ -125,16 +122,11 @@ def _on_pick(self, vtk_picker, event): idx = np.argmin(abs(vertices - pos), axis=0) vertex_id = cell[idx[0]] - fid = self._fids_to_pick.pop(0) # guaranteed previously - idx = self._default_fiducials.index(fid) + default_fiducials = [s.lower() for s in self._default_fiducials] + idx = default_fiducials.index(self._current_fiducial) # XXX: add coreg.set_fids self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() - if len(self._fids_to_pick) == 0: - self._lock_fids = True - else: - next_fid = self._fids_to_pick[0] - self._actors["msg"].SetInput(f"Picking {next_fid}...") self._emit_coreg_modified() def _set_subjects_dir(self, subjects_dir): @@ -242,9 +234,7 @@ def _lock_fids_changed(self, change=None): self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) self._widgets["high_res_head"].set_enabled(False) - self._fids_to_pick = list(self._default_fiducials) - next_fid = self._fids_to_pick[0] - self._actors["msg"].SetInput(f"Picking {next_fid}...") + self._actors["msg"].SetInput("Picking fiducials...") self._renderer._update() if "lock_fids" in self._widgets: self._widgets["lock_fids"].set_value(self._lock_fids) From 9e00fec84e3d84cafe2245f7abe5323eeec30322 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 11:02:45 +0200 Subject: [PATCH 107/225] improve glyph scaling [ci skip] --- mne/viz/_3d.py | 6 ++++-- mne/viz/backends/_pyvista.py | 8 +++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 2ff3c7cab61..3f27d326254 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -995,7 +995,8 @@ def _plot_hpi_coils(renderer, info, to_cf_t, opacity=0.5, x, y, z, u, v, w, color=color, scale=scale, mode="cylinder", glyph_height=glyph_height, glyph_center=glyph_center, resolution=resolution, glyph_resolution=glyph_resolution, - opacity=opacity, scale_mode='vector', scalars=scalars) + glyph_radius=None, opacity=opacity, scale_mode='vector', + scalars=scalars) else: actor, _ = renderer.sphere(center=hpi_loc, color=color, scale=scale, opacity=opacity, backface_culling=True) @@ -1056,7 +1057,8 @@ def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, x, y, z, u, v, w, color=color, scale=scale, mode="cylinder", glyph_height=glyph_height, glyph_center=glyph_center, resolution=resolution, glyph_resolution=glyph_resolution, - opacity=opacity, scale_mode='vector', scalars=scalars) + glyph_radius=None, opacity=opacity, scale_mode='vector', + scalars=scalars) else: actor, _ = renderer.sphere(center=ext_loc, color=color, scale=scale, opacity=opacity, diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index e90fe59924c..baf31a4e0ce 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -446,7 +446,7 @@ def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8, glyph_height=None, glyph_center=None, glyph_resolution=None, opacity=1.0, scale_mode='none', scalars=None, backface_culling=False, line_width=2., name=None, - glyph_width=None, glyph_depth=None, + glyph_width=None, glyph_depth=None, glyph_radius=0.15, solid_transform=None): _check_option('mode', mode, ALLOWED_QUIVER_MODES) with warnings.catch_warnings(): @@ -484,10 +484,12 @@ def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8, if mode == 'cone': glyph = vtk.vtkConeSource() glyph.SetCenter(0.5, 0, 0) - glyph.SetRadius(0.15) + if glyph_radius is not None: + glyph.SetRadius(glyph_radius) elif mode == 'cylinder': glyph = vtk.vtkCylinderSource() - glyph.SetRadius(0.15) + if glyph_radius is not None: + glyph.SetRadius(glyph_radius) elif mode == 'oct': glyph = vtk.vtkPlatonicSolidSource() glyph.SetSolidTypeToOctahedron() From 3ce8197d154a587a51d3beb991bf9c9515277d42 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 15:14:56 +0200 Subject: [PATCH 108/225] TST: lock scale parameters --- mne/viz/_coreg/_coreg.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 055e05e5b6f..bb2a7c660e0 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -80,6 +80,8 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._configure_picking() self._renderer.show() + self._scale_mode = "None" + def _configure_picking(self): self._renderer._update_picking_callback( self._on_mouse_move, @@ -317,6 +319,10 @@ def _grow_hair_changed(self, change=None): def _scale_mode_changed(self, change=None): mode = None if self._scale_mode == "None" else self._scale_mode self._coreg.set_scale_mode(mode) + for coord in ("X", "Y", "Z"): + name = f"s{coord}" + if name in self._widgets: + self._widgets[name].set_enabled(mode is None) @observe("_icp_fid_match") def _icp_fid_match_changed(self, change=None): From 6af2c40f9366f2dd78d3985afea6e652aa26c559 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 15:18:21 +0200 Subject: [PATCH 109/225] fix --- mne/viz/_coreg/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index bb2a7c660e0..0524226f854 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -322,7 +322,7 @@ def _scale_mode_changed(self, change=None): for coord in ("X", "Y", "Z"): name = f"s{coord}" if name in self._widgets: - self._widgets[name].set_enabled(mode is None) + self._widgets[name].set_enabled(mode is not None) @observe("_icp_fid_match") def _icp_fid_match_changed(self, change=None): From f5de425b0bf565dd893d20fa812e0e1b71492605 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 15:45:01 +0200 Subject: [PATCH 110/225] allow partial update --- mne/viz/_coreg/_coreg.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0524226f854..1ede9440b4a 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -129,7 +129,7 @@ def _on_pick(self, vtk_picker, event): # XXX: add coreg.set_fids self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() - self._emit_coreg_modified() + self._update("fids") def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir @@ -189,7 +189,6 @@ def _set_parameter(self, x, mode_name, coord): tra=params["translation"], sca=params["scale"], ) - self._update(update_parameters=False) def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations @@ -273,13 +272,19 @@ def _info_file_changed(self, change=None): self._coreg._info = self._info self._reset() - @observe("_coreg_modified") - def _update(self, change=None): - self._add_head_surface() - self._add_hpi_coils() - self._add_head_shape_points() - self._add_head_fiducials() - self._add_mri_fiducials() + def _update(self, change="all"): + if not isinstance(change, list): + change = [change] + forced = "all" in change + if "head" in change or forced: + self._add_head_surface() + if "hsp" in change or forced: + self._add_head_shape_points() + if "hpi" in change or forced: + self._add_hpi_coils() + if "fids" in change or forced: + self._add_head_fiducials() + self._add_mri_fiducials() @observe("_orient_glyphs") def _orient_glyphs_changed(self, change=None): @@ -351,10 +356,7 @@ def _reset(self): self._reset_fitting_parameters() self._reset_fiducials() self._coreg.reset() - self._emit_coreg_modified() - - def _emit_coreg_modified(self): - self._coreg_modified = not self._coreg_modified + self._update() def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) @@ -421,7 +423,7 @@ def _fit_fiducials(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._emit_coreg_modified() + self._update(["hsp", "hpi", "fids"]) def _fit_icp(self): self._coreg.fit_icp( @@ -431,7 +433,7 @@ def _fit_icp(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._emit_coreg_modified() + self._update(["hsp", "hpi", "fids"]) def _save_trans(self, fname): write_trans(fname, self._coreg.trans) @@ -445,7 +447,7 @@ def _load_trans(self, fname): rot=[rot_x, rot_y, rot_z], tra=[x, y, z], ) - self._emit_coreg_modified() + self._update(["hsp", "hpi", "fids"]) def _get_subjects(self): # XXX: would be nice to move this function to util From 0b5455e43760c8db5e1b66dacdf3855adcfe1082 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 15:45:28 +0200 Subject: [PATCH 111/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 1ede9440b4a..82e57609676 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -272,20 +272,6 @@ def _info_file_changed(self, change=None): self._coreg._info = self._info self._reset() - def _update(self, change="all"): - if not isinstance(change, list): - change = [change] - forced = "all" in change - if "head" in change or forced: - self._add_head_surface() - if "hsp" in change or forced: - self._add_head_shape_points() - if "hpi" in change or forced: - self._add_hpi_coils() - if "fids" in change or forced: - self._add_head_fiducials() - self._add_mri_fiducials() - @observe("_orient_glyphs") def _orient_glyphs_changed(self, change=None): self._add_hpi_coils() @@ -352,6 +338,20 @@ def _reset_fiducials(self): def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance) + def _update(self, change="all"): + if not isinstance(change, list): + change = [change] + forced = "all" in change + if "head" in change or forced: + self._add_head_surface() + if "hsp" in change or forced: + self._add_head_shape_points() + if "hpi" in change or forced: + self._add_hpi_coils() + if "fids" in change or forced: + self._add_head_fiducials() + self._add_mri_fiducials() + def _reset(self): self._reset_fitting_parameters() self._reset_fiducials() From 725a115394e5278bdbb4bfce7ed185877481c82d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 4 Oct 2021 15:51:27 +0200 Subject: [PATCH 112/225] rename [ci skip] --- mne/viz/_coreg/_coreg.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 82e57609676..244eaca8d47 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -129,7 +129,7 @@ def _on_pick(self, vtk_picker, event): # XXX: add coreg.set_fids self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() - self._update("fids") + self._update_plot("fids") def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir @@ -338,17 +338,17 @@ def _reset_fiducials(self): def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance) - def _update(self, change="all"): - if not isinstance(change, list): - change = [change] - forced = "all" in change - if "head" in change or forced: + def _update_plot(self, changes="all"): + if not isinstance(changes, list): + changes = [changes] + forced = "all" in changes + if "head" in changes or forced: self._add_head_surface() - if "hsp" in change or forced: + if "hsp" in changes or forced: self._add_head_shape_points() - if "hpi" in change or forced: + if "hpi" in changes or forced: self._add_hpi_coils() - if "fids" in change or forced: + if "fids" in changes or forced: self._add_head_fiducials() self._add_mri_fiducials() @@ -356,7 +356,7 @@ def _reset(self): self._reset_fitting_parameters() self._reset_fiducials() self._coreg.reset() - self._update() + self._update_plot() def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) @@ -423,7 +423,7 @@ def _fit_fiducials(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._update(["hsp", "hpi", "fids"]) + self._update_plot(["hsp", "hpi", "fids"]) def _fit_icp(self): self._coreg.fit_icp( @@ -433,7 +433,7 @@ def _fit_icp(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._update(["hsp", "hpi", "fids"]) + self._update_plot(["hsp", "hpi", "fids"]) def _save_trans(self, fname): write_trans(fname, self._coreg.trans) @@ -447,7 +447,7 @@ def _load_trans(self, fname): rot=[rot_x, rot_y, rot_z], tra=[x, y, z], ) - self._update(["hsp", "hpi", "fids"]) + self._update_plot(["hsp", "hpi", "fids"]) def _get_subjects(self): # XXX: would be nice to move this function to util From 64d36ca9f6c9a0a054707d19a1d7c123df6689c9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 5 Oct 2021 15:33:29 +0200 Subject: [PATCH 113/225] fix --- mne/viz/_coreg/_coreg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 244eaca8d47..59fde19f100 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -70,6 +70,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._hpi_coils = True self._head_shape_point = True self._head_resolution = True + self._omit_hsp_distance = 10.0 self._icp_n_iterations = self._default_icp_n_iterations self._icp_fid_match = self._default_icp_fid_matches[0] for fid in self._default_weights.keys(): @@ -336,7 +337,7 @@ def _reset_fiducials(self): self._set_current_fiducial(self._default_fiducials[0]) def _omit_hsp(self): - self._coreg.omit_head_shape_points(self._omit_hsp_distance) + self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1000.) def _update_plot(self, changes="all"): if not isinstance(changes, list): @@ -542,7 +543,7 @@ def noop(x): hlayout = self._renderer._dock_add_layout(vertical=False) self._widgets["omit_distance"] = self._renderer._dock_add_spin_box( name="Omit Distance", - value=10., + value=self._omit_hsp_distance, rng=[0.0, 100.0], callback=self._set_omit_hsp_distance, decimals=1, From 749318cc11812e3d605247c387cba834d6fe3157 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 5 Oct 2021 15:40:25 +0200 Subject: [PATCH 114/225] add support for hsp mask --- mne/viz/_3d.py | 3 ++- mne/viz/_coreg/_coreg.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 9685f14b069..34f4b2dd6b9 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1044,7 +1044,7 @@ def _orient_glyphs(pts, surf): def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, - orient_glyphs=False, surf=None): + orient_glyphs=False, surf=None, mask=None): defaults = DEFAULTS['coreg'] ext_loc = np.array([ d['r'] for d in (info['dig'] or []) @@ -1053,6 +1053,7 @@ def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, ext_loc = apply_trans(to_cf_t['head'], ext_loc) color = defaults['extra_color'] scale = defaults['extra_scale'] + ext_loc = ext_loc[mask] if mask is not None else ext_loc if orient_glyphs: glyph_height = defaults['eegp_height'] glyph_center = (0., -defaults['eegp_height'], 0) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 59fde19f100..1381c9940a0 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -338,6 +338,7 @@ def _reset_fiducials(self): def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1000.) + self._update_plot("hsp") def _update_plot(self, changes="all"): if not isinstance(changes, list): @@ -396,7 +397,8 @@ def _add_head_shape_points(self): self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( self._renderer, self._info, to_cf_t, opacity=1.0, - orient_glyphs=self._orient_glyphs, surf=self._head_geo) + orient_glyphs=self._orient_glyphs, surf=self._head_geo, + mask=self._coreg._extra_points_filter) else: hsp_actors = None self._update_actor("head_shape_points", hsp_actors) From ac0f78d6f8cb82cce946bdbb8caafacea0f5889d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 5 Oct 2021 15:46:17 +0200 Subject: [PATCH 115/225] add _reset_omit_hsp_filter [ci skip] --- mne/viz/_coreg/_coreg.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 1381c9940a0..657a7d62670 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -151,7 +151,7 @@ def _set_info_file(self, fname): self._info_file = fname def _set_omit_hsp_distance(self, distance): - self._omit_hsp_distance = distance / 1000.0 + self._omit_hsp_distance = distance def _set_orient_glyphs(self, state): self._orient_glyphs = bool(state) @@ -340,6 +340,10 @@ def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1000.) self._update_plot("hsp") + def _reset_omit_hsp_filter(self): + self._coreg._extra_points_filter = None + self._update_plot("hsp") + def _update_plot(self, changes="all"): if not isinstance(changes, list): changes = [changes] @@ -556,6 +560,11 @@ def noop(x): callback=self._omit_hsp, layout=hlayout, ) + self._widgets["reset_omit"] = self._renderer._dock_add_button( + name="Reset", + callback=self._reset_omit_hsp_filter, + layout=hlayout, + ) self._renderer._layout_add_widget(layout, hlayout) layout = self._renderer._dock_add_group_box("View") From 815067f6c5764014532283104da0cbce0208bdfb Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 5 Oct 2021 16:33:04 +0200 Subject: [PATCH 116/225] prepare for plotting EEG channels --- mne/viz/_coreg/_coreg.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 657a7d62670..125e67a41d1 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -25,6 +25,7 @@ class CoregistrationUI(HasTraits): _orient_glyphs = Bool() _hpi_coils = Bool() _head_shape_point = Bool() + _eeg_channels = Bool() _head_resolution = Bool() _head_transparency = Bool() _grow_hair = Float() @@ -69,6 +70,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._orient_glyphs = False self._hpi_coils = True self._head_shape_point = True + self._eeg_channels = True self._head_resolution = True self._omit_hsp_distance = 10.0 self._icp_n_iterations = self._default_icp_n_iterations @@ -162,6 +164,9 @@ def _set_hpi_coils(self, state): def _set_head_shape_points(self, state): self._head_shape_point = bool(state) + def _set_eeg_channels(self, state): + self._eeg_channels = bool(state) + def _set_head_resolution(self, state): self._head_resolution = bool(state) @@ -286,6 +291,10 @@ def _hpi_coils_changed(self, change=None): def _head_shape_point_changed(self, change=None): self._add_head_shape_points() + @observe("_eeg_channels") + def _eeg_channels_changed(self, change=None): + self._add_eeg_channels() + @observe("_head_resolution") def _head_resolution_changed(self, change=None): self._surface = "head-dense" if self._head_resolution else "head" @@ -407,6 +416,9 @@ def _add_head_shape_points(self): hsp_actors = None self._update_actor("head_shape_points", hsp_actors) + def _add_eeg_channels(self): + pass + def _add_head_surface(self): bem = None to_cf_t = _get_transforms_to_coord_frame( @@ -586,6 +598,12 @@ def noop(x): callback=self._set_head_shape_points, layout=layout ) + self._widgets["show_eeg"] = self._renderer._dock_add_check_box( + name="Show EEG Channels", + value=True, + callback=self._set_eeg_channels, + layout=layout + ) self._widgets["high_res_head"] = self._renderer._dock_add_check_box( name="Show High Resolution Head", value=True, From 9dbde188e63c4a5a829afcaecef2f713adbe6e0d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 5 Oct 2021 16:46:41 +0200 Subject: [PATCH 117/225] add _add_eeg_channels [ci skip] --- mne/viz/_coreg/_coreg.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 125e67a41d1..0f3dacd7ff8 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -4,10 +4,11 @@ from functools import partial from ...defaults import DEFAULTS from ...io import read_info, read_fiducials +from ...io.pick import pick_types from ...coreg import Coregistration, _is_mri_subject from ...viz._3d import (_plot_head_surface, _plot_head_fiducials, _plot_head_shape_points, _plot_mri_fiducials, - _plot_hpi_coils) + _plot_hpi_coils, _plot_sensors) from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ...utils import get_subjects_dir @@ -363,6 +364,8 @@ def _update_plot(self, changes="all"): self._add_head_shape_points() if "hpi" in changes or forced: self._add_hpi_coils() + if "eeg" in changes or forced: + self._add_eeg_channels() if "fids" in changes or forced: self._add_head_fiducials() self._add_mri_fiducials() @@ -417,7 +420,19 @@ def _add_head_shape_points(self): self._update_actor("head_shape_points", hsp_actors) def _add_eeg_channels(self): - pass + if self._eeg_channels: + eeg = ["original"] + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=self._coord_frame) + picks = pick_types(self._info, eeg=(len(eeg) > 0)) + eeg_actors = _plot_sensors( + self._renderer, self._info, to_cf_t, picks, meg=False, + eeg=eeg, fnirs=False, warn_meg=False, + head_surf=self._head_geo, units='m') + eeg_actors = eeg_actors["eeg"] + else: + eeg_actors = None + self._update_actor("eeg_channels", eeg_actors) def _add_head_surface(self): bem = None From 1ff20982713b03e723aabacd3c929d8639cfa548 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 5 Oct 2021 16:56:39 +0200 Subject: [PATCH 118/225] improve UX consistency [ci skip] --- mne/viz/_coreg/_coreg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0f3dacd7ff8..de53c6777f3 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -233,6 +233,7 @@ def _lock_fids_changed(self, change=None): self._widgets["orient_glyphs"].set_enabled(True) self._widgets["show_hpi"].set_enabled(True) self._widgets["show_hsp"].set_enabled(True) + self._widgets["show_eeg"].set_enabled(True) self._widgets["high_res_head"].set_enabled(True) self._actors["msg"].SetInput("") else: @@ -241,6 +242,8 @@ def _lock_fids_changed(self, change=None): self._widgets["show_hpi"].set_enabled(False) self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) + self._widgets["show_eeg"].set_value(False) + self._widgets["show_eeg"].set_enabled(False) self._widgets["high_res_head"].set_enabled(False) self._actors["msg"].SetInput("Picking fiducials...") self._renderer._update() From 3ea0f9f51d9e21ec2f21fcfba68cb95b39d3c66e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 09:56:05 +0200 Subject: [PATCH 119/225] TMP: faster init --- mne/viz/_coreg/_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index de53c6777f3..2a09ca48a49 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -71,8 +71,8 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._orient_glyphs = False self._hpi_coils = True self._head_shape_point = True - self._eeg_channels = True - self._head_resolution = True + self._eeg_channels = False + self._head_resolution = False self._omit_hsp_distance = 10.0 self._icp_n_iterations = self._default_icp_n_iterations self._icp_fid_match = self._default_icp_fid_matches[0] From cc23513c2b32b85811ad3f2ad4026a7d1d947844 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 10:20:37 +0200 Subject: [PATCH 120/225] better init [ci skip] --- mne/viz/_coreg/_coreg.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 2a09ca48a49..32809ac5ccc 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -43,7 +43,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._coord_frame = "mri" self._mouse_no_mvt = -1 self._omit_hsp_distance = 0.0 - self._surface = "head-dense" self._opacity = 1.0 self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in @@ -73,6 +72,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._head_shape_point = True self._eeg_channels = False self._head_resolution = False + self._head_transparency = False self._omit_hsp_distance = 10.0 self._icp_n_iterations = self._default_icp_n_iterations self._icp_fid_match = self._default_icp_fid_matches[0] @@ -301,7 +301,6 @@ def _eeg_channels_changed(self, change=None): @observe("_head_resolution") def _head_resolution_changed(self, change=None): - self._surface = "head-dense" if self._head_resolution else "head" self._add_head_surface() self._grow_hair_changed() @@ -438,12 +437,13 @@ def _add_eeg_channels(self): self._update_actor("eeg_channels", eeg_actors) def _add_head_surface(self): + surface = "head-dense" if self._head_resolution else "head" bem = None to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) try: head_actor, head_surf = _plot_head_surface( - self._renderer, self._surface, self._subject, + self._renderer, surface, self._subject, self._subjects_dir, bem, self._coord_frame, to_cf_t, alpha=self._opacity) except IOError: @@ -600,37 +600,37 @@ def noop(x): layout = self._renderer._dock_add_group_box("View") self._widgets["orient_glyphs"] = self._renderer._dock_add_check_box( name="Orient glyphs", - value=False, + value=self._orient_glyphs, callback=self._set_orient_glyphs, layout=layout ) self._widgets["show_hpi"] = self._renderer._dock_add_check_box( name="Show HPI Coils", - value=True, + value=self._hpi_coils, callback=self._set_hpi_coils, layout=layout ) self._widgets["show_hsp"] = self._renderer._dock_add_check_box( name="Show Head Shape Points", - value=True, + value=self._head_shape_point, callback=self._set_head_shape_points, layout=layout ) self._widgets["show_eeg"] = self._renderer._dock_add_check_box( name="Show EEG Channels", - value=True, + value=self._eeg_channels, callback=self._set_eeg_channels, layout=layout ) self._widgets["high_res_head"] = self._renderer._dock_add_check_box( name="Show High Resolution Head", - value=True, + value=self._head_resolution, callback=self._set_head_resolution, layout=layout ) self._widgets["make_transparent"] = self._renderer._dock_add_check_box( name="Make skin surface transparent", - value=False, + value=self._head_transparency, callback=self._set_head_transparency, layout=layout ) From 2659d204dd39e31f2a1762f6035aaa575310e8c2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 10:27:45 +0200 Subject: [PATCH 121/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 32809ac5ccc..a80e8c584ac 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -22,7 +22,6 @@ class CoregistrationUI(HasTraits): _fiducials_file = Unicode() _current_fiducial = Unicode() _info_file = Unicode() - _coreg_modified = Bool() _orient_glyphs = Bool() _hpi_coils = Bool() _head_shape_point = Bool() @@ -437,8 +436,8 @@ def _add_eeg_channels(self): self._update_actor("eeg_channels", eeg_actors) def _add_head_surface(self): - surface = "head-dense" if self._head_resolution else "head" bem = None + surface = "head-dense" if self._head_resolution else "head" to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) try: @@ -571,7 +570,7 @@ def noop(x): ) self._widgets["grow_hair"] = self._renderer._dock_add_spin_box( name="Grow Hair", - value=0.0, + value=self._grow_hair, rng=[0.0, 10.0], callback=self._set_grow_hair, layout=layout, From f613ce3b7e43f536da861d1261ceb42342ae549a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 10:53:03 +0200 Subject: [PATCH 122/225] add _update_parameters [ci skip] --- mne/viz/_coreg/_coreg.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index a80e8c584ac..cde6488176c 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -179,6 +179,26 @@ def _set_grow_hair(self, value): def _set_scale_mode(self, mode): self._scale_mode = mode + def _update_parameters(self): + # rotation + for idx, name in enumerate(("rX", "rY", "rZ")): + if name in self._widgets: + val = np.rad2deg(self._coreg._rotation[idx]) + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + # translation + for idx, name in enumerate(("tX", "tY", "tZ")): + if name in self._widgets: + val = self._coreg._translation[idx] * 1e3 + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + # scale + for idx, name in enumerate(("sX", "sY", "sZ")): + if name in self._widgets: + val = self._coreg._scale[idx] * 1e2 + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + def _set_parameter(self, x, mode_name, coord): params = dict( rotation=self._coreg._rotation, @@ -188,8 +208,11 @@ def _set_parameter(self, x, mode_name, coord): idx = ["X", "Y", "Z"].index(coord) if mode_name == "rotation": params[mode_name][idx] = np.deg2rad(x) - else: + elif mode_name == "translation": params[mode_name][idx] = x / 1000.0 + else: + assert mode_name == "scale" + params[mode_name][idx] = x / 100.0 self._coreg._update_params( rot=params["rotation"], tra=params["translation"], @@ -376,6 +399,7 @@ def _reset(self): self._reset_fiducials() self._coreg.reset() self._update_plot() + self._update_parameters() def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) @@ -460,6 +484,7 @@ def _fit_fiducials(self): verbose=self._verbose, ) self._update_plot(["hsp", "hpi", "fids"]) + self._update_parameters() def _fit_icp(self): self._coreg.fit_icp( @@ -470,6 +495,7 @@ def _fit_icp(self): verbose=self._verbose, ) self._update_plot(["hsp", "hpi", "fids"]) + self._update_parameters() def _save_trans(self, fname): write_trans(fname, self._coreg.trans) From 58b33605aa7e4963b612af5ef28752840532acdd Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 10:56:54 +0200 Subject: [PATCH 123/225] refactor --- mne/viz/_coreg/_coreg.py | 138 +++++++++++++++++++-------------------- 1 file changed, 69 insertions(+), 69 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index cde6488176c..9d4e40e366e 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -85,55 +85,6 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._scale_mode = "None" - def _configure_picking(self): - self._renderer._update_picking_callback( - self._on_mouse_move, - self._on_button_press, - self._on_button_release, - self._on_pick - ) - self._actors["msg"] = self._renderer.text2d(0, 0, "") - - def _on_mouse_move(self, vtk_picker, event): - if self._mouse_no_mvt: - self._mouse_no_mvt -= 1 - - def _on_button_press(self, vtk_picker, event): - self._mouse_no_mvt = 2 - - def _on_button_release(self, vtk_picker, event): - if self._mouse_no_mvt > 0: - x, y = vtk_picker.GetEventPosition() - # XXX: plotter/renderer should not be exposed if possible - plotter = self._renderer.figure.plotter - picked_renderer = self._renderer.figure.plotter.renderer - # trigger the pick - plotter.picker.Pick(x, y, 0, picked_renderer) - self._mouse_no_mvt = 0 - - def _on_pick(self, vtk_picker, event): - if self._lock_fids: - return - # XXX: taken from Brain, can be refactored - cell_id = vtk_picker.GetCellId() - mesh = vtk_picker.GetDataSet() - if mesh is None or cell_id == -1 or not self._mouse_no_mvt: - return - pos = np.array(vtk_picker.GetPickPosition()) - vtk_cell = mesh.GetCell(cell_id) - cell = [vtk_cell.GetPointId(point_id) for point_id - in range(vtk_cell.GetNumberOfPoints())] - vertices = mesh.points[cell] - idx = np.argmin(abs(vertices - pos), axis=0) - vertex_id = cell[idx[0]] - - default_fiducials = [s.lower() for s in self._default_fiducials] - idx = default_fiducials.index(self._current_fiducial) - # XXX: add coreg.set_fids - self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] - self._coreg._reset_fiducials() - self._update_plot("fids") - def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir @@ -179,26 +130,6 @@ def _set_grow_hair(self, value): def _set_scale_mode(self, mode): self._scale_mode = mode - def _update_parameters(self): - # rotation - for idx, name in enumerate(("rX", "rY", "rZ")): - if name in self._widgets: - val = np.rad2deg(self._coreg._rotation[idx]) - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) - # translation - for idx, name in enumerate(("tX", "tY", "tZ")): - if name in self._widgets: - val = self._coreg._translation[idx] * 1e3 - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) - # scale - for idx, name in enumerate(("sX", "sY", "sZ")): - if name in self._widgets: - val = self._coreg._scale[idx] * 1e2 - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) - def _set_parameter(self, x, mode_name, coord): params = dict( rotation=self._coreg._rotation, @@ -354,6 +285,55 @@ def _scale_mode_changed(self, change=None): def _icp_fid_match_changed(self, change=None): self._coreg.set_fid_match(self._icp_fid_match) + def _configure_picking(self): + self._renderer._update_picking_callback( + self._on_mouse_move, + self._on_button_press, + self._on_button_release, + self._on_pick + ) + self._actors["msg"] = self._renderer.text2d(0, 0, "") + + def _on_mouse_move(self, vtk_picker, event): + if self._mouse_no_mvt: + self._mouse_no_mvt -= 1 + + def _on_button_press(self, vtk_picker, event): + self._mouse_no_mvt = 2 + + def _on_button_release(self, vtk_picker, event): + if self._mouse_no_mvt > 0: + x, y = vtk_picker.GetEventPosition() + # XXX: plotter/renderer should not be exposed if possible + plotter = self._renderer.figure.plotter + picked_renderer = self._renderer.figure.plotter.renderer + # trigger the pick + plotter.picker.Pick(x, y, 0, picked_renderer) + self._mouse_no_mvt = 0 + + def _on_pick(self, vtk_picker, event): + if self._lock_fids: + return + # XXX: taken from Brain, can be refactored + cell_id = vtk_picker.GetCellId() + mesh = vtk_picker.GetDataSet() + if mesh is None or cell_id == -1 or not self._mouse_no_mvt: + return + pos = np.array(vtk_picker.GetPickPosition()) + vtk_cell = mesh.GetCell(cell_id) + cell = [vtk_cell.GetPointId(point_id) for point_id + in range(vtk_cell.GetNumberOfPoints())] + vertices = mesh.points[cell] + idx = np.argmin(abs(vertices - pos), axis=0) + vertex_id = cell[idx[0]] + + default_fiducials = [s.lower() for s in self._default_fiducials] + idx = default_fiducials.index(self._current_fiducial) + # XXX: add coreg.set_fids + self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] + self._coreg._reset_fiducials() + self._update_plot("fids") + def _reset_fitting_parameters(self): if "icp_n_iterations" in self._widgets: self._widgets["icp_n_iterations"].set_value( @@ -394,6 +374,26 @@ def _update_plot(self, changes="all"): self._add_head_fiducials() self._add_mri_fiducials() + def _update_parameters(self): + # rotation + for idx, name in enumerate(("rX", "rY", "rZ")): + if name in self._widgets: + val = np.rad2deg(self._coreg._rotation[idx]) + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + # translation + for idx, name in enumerate(("tX", "tY", "tZ")): + if name in self._widgets: + val = self._coreg._translation[idx] * 1e3 + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + # scale + for idx, name in enumerate(("sX", "sY", "sZ")): + if name in self._widgets: + val = self._coreg._scale[idx] * 1e2 + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + def _reset(self): self._reset_fitting_parameters() self._reset_fiducials() From 32a3519f79a3d3ee78f0b1c4061e4c990e6177cc Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 11:37:49 +0200 Subject: [PATCH 124/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9d4e40e366e..f8819f2ecbf 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -83,6 +83,8 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._configure_picking() self._renderer.show() + self._current_fiducial = self._default_fiducials[0] + self._lock_fids = True self._scale_mode = "None" def _set_subjects_dir(self, subjects_dir): @@ -219,7 +221,7 @@ def _fiducials_file_changed(self, change=None): @observe("_current_fiducial") def _current_fiducial_changed(self, change=None): - fid = self._current_fiducial + fid = self._current_fiducial.lower() val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 coords = ["X", "Y", "Z"] for coord in coords: @@ -551,7 +553,7 @@ def noop(x): layout = self._renderer._dock_add_group_box("MRI Fiducials") self._widgets["lock_fids"] = self._renderer._dock_add_check_box( name="Lock fiducials", - value=False, + value=self._lock_fids, callback=self._set_lock_fids, layout=layout ) @@ -582,9 +584,7 @@ def noop(x): decimals=1, layout=hlayout ) - self._set_current_fiducial(self._default_fiducials[0]) # init self._renderer._layout_add_widget(layout, hlayout) - self._set_lock_fids(True) # init layout = self._renderer._dock_add_group_box("Digitization Source") self._widgets["info_file"] = self._renderer._dock_add_file_button( From e66413d0b4f607042c2f2de805906e03d46e70b1 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 13:36:01 +0200 Subject: [PATCH 125/225] fix plot sensors --- mne/viz/_coreg/_coreg.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index f8819f2ecbf..02a29f39e05 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -364,15 +364,16 @@ def _update_plot(self, changes="all"): if not isinstance(changes, list): changes = [changes] forced = "all" in changes + sensors = "sensors" in changes if "head" in changes or forced: self._add_head_surface() - if "hsp" in changes or forced: + if "hsp" in changes or forced or sensors: self._add_head_shape_points() - if "hpi" in changes or forced: + if "hpi" in changes or forced or sensors: self._add_hpi_coils() - if "eeg" in changes or forced: + if "eeg" in changes or forced or sensors: self._add_eeg_channels() - if "fids" in changes or forced: + if "fids" in changes or forced or sensors: self._add_head_fiducials() self._add_mri_fiducials() @@ -485,7 +486,7 @@ def _fit_fiducials(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._update_plot(["hsp", "hpi", "fids"]) + self._update_plot("sensors") self._update_parameters() def _fit_icp(self): @@ -496,7 +497,7 @@ def _fit_icp(self): rpa_weight=self._rpa_weight, verbose=self._verbose, ) - self._update_plot(["hsp", "hpi", "fids"]) + self._update_plot("sensors") self._update_parameters() def _save_trans(self, fname): @@ -511,7 +512,8 @@ def _load_trans(self, fname): rot=[rot_x, rot_y, rot_z], tra=[x, y, z], ) - self._update_plot(["hsp", "hpi", "fids"]) + self._update_plot("sensors") + self._update_parameters() def _get_subjects(self): # XXX: would be nice to move this function to util From bc3f5a39c1f4136c2d67f05e6d2d02284a09e776 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 13:40:36 +0200 Subject: [PATCH 126/225] fix sensor opacity --- mne/viz/_3d.py | 11 +++++++---- mne/viz/_coreg/_coreg.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 34f4b2dd6b9..ba97340a213 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1105,7 +1105,7 @@ def _plot_forward(renderer, fwd, to_cf_t): def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, - warn_meg, head_surf, units): + warn_meg, head_surf, units, sensor_opacity=0.8): """Render sensors in a 3D scene.""" defaults = DEFAULTS['coreg'] ch_pos, sources, detectors = _ch_pos_in_coord_frame( @@ -1136,19 +1136,22 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, if plot_sensors: actor, _ = renderer.sphere( center=tuple(ch_coord * scalar), color=color, - scale=defaults[ch_type + '_scale'] * scalar, opacity=0.8) + scale=defaults[ch_type + '_scale'] * scalar, + opacity=sensor_opacity) actors[ch_type].append(actor) if ch_name in sources and 'sources' in fnirs: actor, _ = renderer.sphere( center=tuple(sources[ch_name] * scalar), color=defaults['source_color'], - scale=defaults['source_scale'] * scalar, opacity=0.8) + scale=defaults['source_scale'] * scalar, + opacity=sensor_opacity) actors[ch_type].append(actor) if ch_name in detectors and 'detectors' in fnirs: actor, _ = renderer.sphere( center=tuple(detectors[ch_name] * scalar), color=defaults['detector_color'], - scale=defaults['detector_scale'] * scalar, opacity=0.8) + scale=defaults['detector_scale'] * scalar, + opacity=sensor_opacity) actors[ch_type].append(actor) if ch_name in sources and ch_name in detectors and \ 'pairs' in fnirs: diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 02a29f39e05..0c40b0f6600 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -455,8 +455,8 @@ def _add_eeg_channels(self): picks = pick_types(self._info, eeg=(len(eeg) > 0)) eeg_actors = _plot_sensors( self._renderer, self._info, to_cf_t, picks, meg=False, - eeg=eeg, fnirs=False, warn_meg=False, - head_surf=self._head_geo, units='m') + eeg=eeg, fnirs=False, warn_meg=False, head_surf=self._head_geo, + units='m', sensor_opacity=1.0) eeg_actors = eeg_actors["eeg"] else: eeg_actors = None From 62c7571bcd8093fb8fd9b805e7cca6bb74c39c51 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 14:12:36 +0200 Subject: [PATCH 127/225] allow extra renderer kwargs [ci skip] --- mne/viz/_coreg/_coreg.py | 2 +- mne/viz/backends/_pyvista.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0c40b0f6600..631802446f2 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -58,7 +58,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): "hpi": 1.0, } - self._renderer = _get_renderer(bgcolor="grey") + self._renderer = _get_renderer(bgcolor="grey", toolbar=True) self._renderer._window_close_connect(self._clean) self._coreg = Coregistration(info, subject, subjects_dir, fids) self._fids = fids diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index baf31a4e0ce..9b36fa9e038 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -54,7 +54,8 @@ def __init__(self, background_color='black', smooth_shading=True, off_screen=False, - notebook=False): + notebook=False, + **kwargs): self.plotter = plotter self.display = None self.background_color = background_color @@ -65,7 +66,7 @@ def __init__(self, self.store['window_size'] = size self.store['shape'] = shape self.store['off_screen'] = off_screen - self.store['border'] = False + self.store['border'] = kwargs.get("border", False) # multi_samples > 1 is broken on macOS + Intel Iris + volume rendering self.store['multi_samples'] = 1 if sys.platform == 'darwin' else 4 @@ -73,8 +74,8 @@ def __init__(self, self.store['show'] = show self.store['title'] = title self.store['auto_update'] = False - self.store['menu_bar'] = False - self.store['toolbar'] = False + self.store['menu_bar'] = kwargs.get("menu_bar", False) + self.store['toolbar'] = kwargs.get("toolbar", False) self.store['update_app_icon'] = False self._nrows, self._ncols = self.store['shape'] @@ -148,12 +149,12 @@ class _PyVistaRenderer(_AbstractRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=True): + notebook=None, smooth_shading=True, **kwargs): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, background_color=bgcolor, notebook=notebook, - smooth_shading=smooth_shading) + smooth_shading=smooth_shading, **kwargs) self.font_family = "arial" self.tube_n_sides = 20 antialias = _get_3d_option('antialias') From 58ae7605306bd9939a49c42d646129e2aba85ca5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 15:22:12 +0200 Subject: [PATCH 128/225] selecting a fiducial updates the view [ci skip] --- mne/viz/_coreg/_coreg.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 631802446f2..03a0b0e9c86 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -229,6 +229,12 @@ def _current_fiducial_changed(self, change=None): idx = coords.index(coord) if name in self._widgets: self._widgets[name].set_value(val[idx]) + view = dict(lpa='left', rpa='right', nasion='front') + kwargs = dict(front=(90., 90.), left=(180, 90), right=(0., 90)) + kwargs = dict(zip(('azimuth', 'elevation'), kwargs[view[fid]])) + if not self._lock_fids: + self._renderer.set_camera( + distance=None, **kwargs) @observe("_info_file") def _info_file_changed(self, change=None): From a1eb8ab1de01bd5c42392206174a48c4a1ee93d9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 15:29:20 +0200 Subject: [PATCH 129/225] connect scale combo boxes --- mne/viz/_coreg/_coreg.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 03a0b0e9c86..7870738ace0 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -686,7 +686,11 @@ def noop(x): name=name, value=0., rng=[-100., 100.], - callback=noop, + callback=partial( + self._set_parameter, + mode_name="scale", + coord=coord, + ), compact=True, double=True, decimals=1, From 26b4d9b91fcf346db882acbb09bdcd1c413fae7d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 15:47:02 +0200 Subject: [PATCH 130/225] add _lock_plot --- mne/coreg.py | 8 +++---- mne/viz/_coreg/_coreg.py | 51 ++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 4d41c54cd40..98dd0af0d75 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -1927,11 +1927,9 @@ def reset(self): The modified Coregistration object. """ self._grow_hair = 0. - self._update_params( - rot=self._default_parameters[:3], - tra=self._default_parameters[3:6], - sca=self._default_parameters[6:9], - ) + self.set_rotation(self._default_parameters[:3]) + self.set_translation(self._default_parameters[3:6]) + self.set_scale(self._default_parameters[6:9]) self._extra_points_filter = None self._update_nearest_calc() return self diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 7870738ace0..4fa5639ec44 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -1,3 +1,4 @@ +from contextlib import contextmanager import os import os.path as op import numpy as np @@ -38,6 +39,7 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._surfaces = dict() self._widgets = dict() self._verbose = True + self._plot_locked = False self._head_geo = None self._coord_frame = "mri" self._mouse_no_mvt = -1 @@ -151,6 +153,7 @@ def _set_parameter(self, x, mode_name, coord): tra=params["translation"], sca=params["scale"], ) + self._update_plot("sensors") def _set_icp_n_iterations(self, n_iterations): self._icp_n_iterations = n_iterations @@ -367,6 +370,8 @@ def _reset_omit_hsp_filter(self): self._update_plot("hsp") def _update_plot(self, changes="all"): + if self._plot_locked: + return if not isinstance(changes, list): changes = [changes] forced = "all" in changes @@ -383,25 +388,35 @@ def _update_plot(self, changes="all"): self._add_head_fiducials() self._add_mri_fiducials() + @contextmanager + def _lock_plot(self): + old_plot_locked = self._plot_locked + self._plot_locked = True + try: + yield + finally: + self._plot_locked = old_plot_locked + def _update_parameters(self): - # rotation - for idx, name in enumerate(("rX", "rY", "rZ")): - if name in self._widgets: - val = np.rad2deg(self._coreg._rotation[idx]) - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) - # translation - for idx, name in enumerate(("tX", "tY", "tZ")): - if name in self._widgets: - val = self._coreg._translation[idx] * 1e3 - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) - # scale - for idx, name in enumerate(("sX", "sY", "sZ")): - if name in self._widgets: - val = self._coreg._scale[idx] * 1e2 - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) + with self._lock_plot(): + # rotation + for idx, name in enumerate(("rX", "rY", "rZ")): + if name in self._widgets: + val = np.rad2deg(self._coreg._rotation[idx]) + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + # translation + for idx, name in enumerate(("tX", "tY", "tZ")): + if name in self._widgets: + val = self._coreg._translation[idx] * 1e3 + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) + # scale + for idx, name in enumerate(("sX", "sY", "sZ")): + if name in self._widgets: + val = self._coreg._scale[idx] * 1e2 + if val != self._widgets[name].get_value(): + self._widgets[name].set_value(val) def _reset(self): self._reset_fitting_parameters() From d712f79e65fef86ed1cd307cb6119026b27f7fba Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 16:11:26 +0200 Subject: [PATCH 131/225] add _set_fiducial [ci skip] --- mne/viz/_coreg/_coreg.py | 68 +++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 4fa5639ec44..bba886ac5f3 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -134,7 +134,14 @@ def _set_grow_hair(self, value): def _set_scale_mode(self, mode): self._scale_mode = mode - def _set_parameter(self, x, mode_name, coord): + def _set_fiducial(self, value, coord): + fid = self._current_fiducial.lower() + coords = ["X", "Y", "Z"] + idx = coords.index(coord) + getattr(self._coreg, f"_{fid}")[0][idx] = value / 1e3 + self._update_plot("fids") + + def _set_parameter(self, value, mode_name, coord): params = dict( rotation=self._coreg._rotation, translation=self._coreg._translation, @@ -142,12 +149,12 @@ def _set_parameter(self, x, mode_name, coord): ) idx = ["X", "Y", "Z"].index(coord) if mode_name == "rotation": - params[mode_name][idx] = np.deg2rad(x) + params[mode_name][idx] = np.deg2rad(value) elif mode_name == "translation": - params[mode_name][idx] = x / 1000.0 + params[mode_name][idx] = value / 1e3 else: assert mode_name == "scale" - params[mode_name][idx] = x / 100.0 + params[mode_name][idx] = value / 1e2 self._coreg._update_params( rot=params["rotation"], tra=params["translation"], @@ -224,20 +231,8 @@ def _fiducials_file_changed(self, change=None): @observe("_current_fiducial") def _current_fiducial_changed(self, change=None): - fid = self._current_fiducial.lower() - val = getattr(self._coreg, f"_{fid}")[0] * 1000.0 - coords = ["X", "Y", "Z"] - for coord in coords: - name = f"fid_{coord}" - idx = coords.index(coord) - if name in self._widgets: - self._widgets[name].set_value(val[idx]) - view = dict(lpa='left', rpa='right', nasion='front') - kwargs = dict(front=(90., 90.), left=(180, 90), right=(0., 90)) - kwargs = dict(zip(('azimuth', 'elevation'), kwargs[view[fid]])) - if not self._lock_fids: - self._renderer.set_camera( - distance=None, **kwargs) + self._update_fiducials() + self._follow_fiducial_view() @observe("_info_file") def _info_file_changed(self, change=None): @@ -343,6 +338,7 @@ def _on_pick(self, vtk_picker, event): # XXX: add coreg.set_fids self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() + self._update_fiducials() self._update_plot("fids") def _reset_fitting_parameters(self): @@ -362,7 +358,7 @@ def _reset_fiducials(self): self._set_current_fiducial(self._default_fiducials[0]) def _omit_hsp(self): - self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1000.) + self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1e3) self._update_plot("hsp") def _reset_omit_hsp_filter(self): @@ -397,6 +393,26 @@ def _lock_plot(self): finally: self._plot_locked = old_plot_locked + def _follow_fiducial_view(self): + fid = self._current_fiducial.lower() + view = dict(lpa='left', rpa='right', nasion='front') + kwargs = dict(front=(90., 90.), left=(180, 90), right=(0., 90)) + kwargs = dict(zip(('azimuth', 'elevation'), kwargs[view[fid]])) + if not self._lock_fids: + self._renderer.set_camera( + distance=None, **kwargs) + + def _update_fiducials(self): + fid = self._current_fiducial.lower() + val = getattr(self._coreg, f"_{fid}")[0] * 1e3 + coords = ["X", "Y", "Z"] + with self._lock_plot(): + for coord in coords: + name = f"fid_{coord}" + idx = coords.index(coord) + if name in self._widgets: + self._widgets[name].set_value(val[idx]) + def _update_parameters(self): with self._lock_plot(): # rotation @@ -550,9 +566,6 @@ def _get_subjects(self): return sorted(subjects) def _configure_dock(self): - def noop(x): - del x - self._renderer._dock_initialize(name="Input", area="left") layout = self._renderer._dock_add_group_box("MRI Subject") self._widgets["subjects_dir"] = self._renderer._dock_add_file_button( @@ -600,8 +613,11 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=coord, value=0., - rng=[-100., 100.], - callback=noop, + rng=[-1e3, 1e3], + callback=partial( + self._set_fiducial, + coord=coord, + ), compact=True, double=True, decimals=1, @@ -700,7 +716,7 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=name, value=0., - rng=[-100., 100.], + rng=[-1e3, 1e3], callback=partial( self._set_parameter, mode_name="scale", @@ -720,7 +736,7 @@ def noop(x): self._widgets[name] = self._renderer._dock_add_spin_box( name=name, value=0., - rng=[-1000., 1000.], + rng=[-1e3, 1e3], callback=partial( self._set_parameter, mode_name=mode_name.lower(), From b8b5a98aa9bfe468cd3c0292a6dba7f29f1b114d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 16:36:57 +0200 Subject: [PATCH 132/225] nitpick --- mne/viz/_coreg/_coreg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index bba886ac5f3..599bc8c3271 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -399,8 +399,7 @@ def _follow_fiducial_view(self): kwargs = dict(front=(90., 90.), left=(180, 90), right=(0., 90)) kwargs = dict(zip(('azimuth', 'elevation'), kwargs[view[fid]])) if not self._lock_fids: - self._renderer.set_camera( - distance=None, **kwargs) + self._renderer.set_camera(distance=None, **kwargs) def _update_fiducials(self): fid = self._current_fiducial.lower() From 7d6965bcaf6f22852e50ea85ede462bc0978492a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 6 Oct 2021 16:45:13 +0200 Subject: [PATCH 133/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 599bc8c3271..86444e4a50e 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -334,7 +334,7 @@ def _on_pick(self, vtk_picker, event): vertex_id = cell[idx[0]] default_fiducials = [s.lower() for s in self._default_fiducials] - idx = default_fiducials.index(self._current_fiducial) + idx = default_fiducials.index(self._current_fiducial.lower()) # XXX: add coreg.set_fids self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() @@ -435,7 +435,6 @@ def _update_parameters(self): def _reset(self): self._reset_fitting_parameters() - self._reset_fiducials() self._coreg.reset() self._update_plot() self._update_parameters() From 020bce27f62fb832aab04c251ff27ba73b645e4a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 14:34:56 +0200 Subject: [PATCH 134/225] add _set_sensors_visibility [ci skip] --- mne/viz/_coreg/_coreg.py | 14 +++++++++++--- mne/viz/backends/_qt.py | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 86444e4a50e..5d856b787f5 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -201,16 +201,15 @@ def _lock_fids_changed(self, change=None): self._widgets["show_eeg"].set_enabled(True) self._widgets["high_res_head"].set_enabled(True) self._actors["msg"].SetInput("") + self._set_sensors_visibility(True) else: self._widgets["orient_glyphs"].set_enabled(False) - self._widgets["show_hpi"].set_value(False) self._widgets["show_hpi"].set_enabled(False) - self._widgets["show_hsp"].set_value(False) self._widgets["show_hsp"].set_enabled(False) - self._widgets["show_eeg"].set_value(False) self._widgets["show_eeg"].set_enabled(False) self._widgets["high_res_head"].set_enabled(False) self._actors["msg"].SetInput("Picking fiducials...") + self._set_sensors_visibility(False) self._renderer._update() if "lock_fids" in self._widgets: self._widgets["lock_fids"].set_value(self._lock_fids) @@ -439,6 +438,15 @@ def _reset(self): self._update_plot() self._update_parameters() + def _set_sensors_visibility(self, state): + sensors = ["hpi_coils", "head_shape_points", "eeg_channels"] + for sensor in sensors: + if sensor in self._actors and self._actors[sensor] is not None: + actors = self._actors[sensor] + actors = actors if isinstance(actors, list) else [actors] + for actor in actors: + actor.SetVisibility(state) + def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) self._actors[actor_name] = actor diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 3fae26ac3c3..59a437030ef 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -612,6 +612,8 @@ def get_value(self): return self._widget.value() elif hasattr(self._widget, "currentText"): return self._widget.currentText() + elif hasattr(self._widget, "checkState"): + return self._widget.checkState() elif hasattr(self._widget, "text"): return self._widget.text() From 85e6afa980c7acee42647b4bb513867559615f48 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 14:58:18 +0200 Subject: [PATCH 135/225] add _forward_widget_command [ci skip] --- mne/viz/_coreg/_coreg.py | 49 +++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 5d856b787f5..1bec4627bc1 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -193,34 +193,19 @@ def _subject_changed(self, changed=None): @observe("_lock_fids") def _lock_fids_changed(self, change=None): - if "show_hsp" in self._widgets: - if self._lock_fids: - self._widgets["orient_glyphs"].set_enabled(True) - self._widgets["show_hpi"].set_enabled(True) - self._widgets["show_hsp"].set_enabled(True) - self._widgets["show_eeg"].set_enabled(True) - self._widgets["high_res_head"].set_enabled(True) - self._actors["msg"].SetInput("") - self._set_sensors_visibility(True) - else: - self._widgets["orient_glyphs"].set_enabled(False) - self._widgets["show_hpi"].set_enabled(False) - self._widgets["show_hsp"].set_enabled(False) - self._widgets["show_eeg"].set_enabled(False) - self._widgets["high_res_head"].set_enabled(False) - self._actors["msg"].SetInput("Picking fiducials...") - self._set_sensors_visibility(False) - self._renderer._update() - if "lock_fids" in self._widgets: - self._widgets["lock_fids"].set_value(self._lock_fids) - if "fid_file" in self._widgets: - self._widgets["fid_file"].set_enabled(not self._lock_fids) - if "fids" in self._widgets: - self._widgets["fids"].set_enabled(not self._lock_fids) - for coord in ("X", "Y", "Z"): - name = f"fid_{coord}" - if name in self._widgets: - self._widgets[name].set_enabled(not self._lock_fids) + view_widgets = ["orient_glyphs", "show_hpi", "show_hsp", + "show_eeg", "high_res_head"] + fid_widgets = ["fid_X", "fid_Y", "fid_Z", "fids_file", "fids"] + if self._lock_fids: + self._forward_widget_command(view_widgets, "set_enabled", True) + self._actors["msg"].SetInput("") + else: + self._forward_widget_command(view_widgets, "set_enabled", False) + self._actors["msg"].SetInput("Picking fiducials...") + self._set_sensors_visibility(self._lock_fids) + self._forward_widget_command("lock_fids", "set_value", self._lock_fids) + self._forward_widget_command(fid_widgets, "set_enabled", + not self._lock_fids) @observe("_fiducials_file") def _fiducials_file_changed(self, change=None): @@ -438,6 +423,13 @@ def _reset(self): self._update_plot() self._update_parameters() + def _forward_widget_command(self, names, command, value): + if not isinstance(names, list): + names = [names] + for name in names: + if name in self._widgets: + getattr(self._widgets[name], command)(value) + def _set_sensors_visibility(self, state): sensors = ["hpi_coils", "head_shape_points", "eeg_channels"] for sensor in sensors: @@ -446,6 +438,7 @@ def _set_sensors_visibility(self, state): actors = actors if isinstance(actors, list) else [actors] for actor in actors: actor.SetVisibility(state) + self._renderer._update() def _update_actor(self, actor_name, actor): self._renderer.plotter.remove_actor(self._actors.get(actor_name)) From f68d604d4920236a60a0f8dcc7410a3e1509bd1e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 15:15:21 +0200 Subject: [PATCH 136/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 1bec4627bc1..4a63e145492 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -326,17 +326,13 @@ def _on_pick(self, vtk_picker, event): self._update_plot("fids") def _reset_fitting_parameters(self): - if "icp_n_iterations" in self._widgets: - self._widgets["icp_n_iterations"].set_value( - self._default_icp_n_iterations) - if "icp_fid_match" in self._widgets: - self._widgets["icp_fid_match"].set_value( - self._default_icp_fid_matches[0]) - for fid in self._default_weights.keys(): - widget_name = f"{fid}_weight" - if widget_name in self._widgets: - self._widgets[widget_name].set_value( - self._default_weights[fid]) + self._forward_widget_command("icp_n_iterations", "set_value", + self._default_icp_n_iterations) + self._forward_widget_command("icp_fid_match", "set_value", + self._default_icp_fid_matches[0]) + weights_widgets = [f"{w}_weight" for w in self._default_weights.keys()] + self._forward_widget_command(weights_widgets, "set_value", + list(self._default_weights.values())) def _reset_fiducials(self): self._set_current_fiducial(self._default_fiducials[0]) @@ -426,9 +422,10 @@ def _reset(self): def _forward_widget_command(self, names, command, value): if not isinstance(names, list): names = [names] - for name in names: + for idx, name in enumerate(names): + val = value[idx] if isinstance(value, list) else value if name in self._widgets: - getattr(self._widgets[name], command)(value) + getattr(self._widgets[name], command)(val) def _set_sensors_visibility(self, state): sensors = ["hpi_coils", "head_shape_points", "eeg_channels"] From 510f5a63d04ef6fc698f44750ae82f3b8b2c5f90 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 15:26:12 +0200 Subject: [PATCH 137/225] refactor --- mne/viz/_coreg/_coreg.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 4a63e145492..33e998a1f46 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -395,23 +395,14 @@ def _update_fiducials(self): def _update_parameters(self): with self._lock_plot(): # rotation - for idx, name in enumerate(("rX", "rY", "rZ")): - if name in self._widgets: - val = np.rad2deg(self._coreg._rotation[idx]) - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) + self._forward_widget_command(["rX", "rY", "rZ"], "set_value", + list(np.rad2deg(self._coreg._rotation))) # translation - for idx, name in enumerate(("tX", "tY", "tZ")): - if name in self._widgets: - val = self._coreg._translation[idx] * 1e3 - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) + self._forward_widget_command(["tX", "tY", "tZ"], "set_value", + list(self._coreg._translation * 1e3)) # scale - for idx, name in enumerate(("sX", "sY", "sZ")): - if name in self._widgets: - val = self._coreg._scale[idx] * 1e2 - if val != self._widgets[name].get_value(): - self._widgets[name].set_value(val) + self._forward_widget_command(["sX", "sY", "sZ"], "set_value", + list(self._coreg._scale * 1e2)) def _reset(self): self._reset_fitting_parameters() From 90144609d861f60c819dd64348044e67fdb75e90 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 15:34:29 +0200 Subject: [PATCH 138/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 33e998a1f46..6cf11300d6d 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -384,25 +384,21 @@ def _follow_fiducial_view(self): def _update_fiducials(self): fid = self._current_fiducial.lower() val = getattr(self._coreg, f"_{fid}")[0] * 1e3 - coords = ["X", "Y", "Z"] with self._lock_plot(): - for coord in coords: - name = f"fid_{coord}" - idx = coords.index(coord) - if name in self._widgets: - self._widgets[name].set_value(val[idx]) + self._forward_widget_command( + ["fid_X", "fid_Y", "fid_Z"], "set_value", val) def _update_parameters(self): with self._lock_plot(): # rotation self._forward_widget_command(["rX", "rY", "rZ"], "set_value", - list(np.rad2deg(self._coreg._rotation))) + np.rad2deg(self._coreg._rotation)) # translation self._forward_widget_command(["tX", "tY", "tZ"], "set_value", - list(self._coreg._translation * 1e3)) + self._coreg._translation * 1e3) # scale self._forward_widget_command(["sX", "sY", "sZ"], "set_value", - list(self._coreg._scale * 1e2)) + self._coreg._scale * 1e2) def _reset(self): self._reset_fitting_parameters() @@ -411,8 +407,8 @@ def _reset(self): self._update_parameters() def _forward_widget_command(self, names, command, value): - if not isinstance(names, list): - names = [names] + names = [names] if not isinstance(names, list) else names + value = list(value) if isinstance(value, np.ndarray) else value for idx, name in enumerate(names): val = value[idx] if isinstance(value, list) else value if name in self._widgets: From b94aea112eb6939a702791352d568dae271d3670 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 16:20:08 +0200 Subject: [PATCH 139/225] improve UX consistency [ci skip] --- mne/viz/_coreg/_coreg.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 6cf11300d6d..cd9c8af88e2 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -415,7 +415,8 @@ def _forward_widget_command(self, names, command, value): getattr(self._widgets[name], command)(val) def _set_sensors_visibility(self, state): - sensors = ["hpi_coils", "head_shape_points", "eeg_channels"] + sensors = ["head_fiducials", "hpi_coils", "head_shape_points", + "eeg_channels"] for sensor in sensors: if sensor in self._actors and self._actors[sensor] is not None: actors = self._actors[sensor] From 8e791b82267de31c024778fe863591cf4337cf84 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 7 Oct 2021 16:23:48 +0200 Subject: [PATCH 140/225] refactor [ci skip] --- mne/viz/_coreg/_coreg.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index cd9c8af88e2..671095ca3a7 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -266,10 +266,8 @@ def _grow_hair_changed(self, change=None): def _scale_mode_changed(self, change=None): mode = None if self._scale_mode == "None" else self._scale_mode self._coreg.set_scale_mode(mode) - for coord in ("X", "Y", "Z"): - name = f"s{coord}" - if name in self._widgets: - self._widgets[name].set_enabled(mode is not None) + self._forward_widget_command(["sX", "sY", "sZ"], "set_enabled", + mode is not None) @observe("_icp_fid_match") def _icp_fid_match_changed(self, change=None): From 18d5351aeb6785b0e7c3e40e0a2f88bf174531cd Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 11 Oct 2021 15:32:55 +0200 Subject: [PATCH 141/225] TST: add mne coreg --pyvista [ci skip] --- mne/commands/mne_coreg.py | 37 +++++++++++++++++++++++++------------ mne/viz/_coreg/_coreg.py | 17 +++++++++++------ 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index c902022dd4e..cb363f75b38 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -77,6 +77,9 @@ def run(): parser.add_option('--simple-rendering', action='store_false', dest='advanced_rendering', help='Use simplified OpenGL rendering') + parser.add_option("--pyvista", + action='store_true', default=False, dest="pyvista", + help="Use the new PyVista/PyQt5 interface.") _add_verbose_flag(parser) options, args = parser.parse_args() @@ -103,19 +106,29 @@ def run(): faulthandler.enable() except ImportError: pass # old Python2 - with ETSContext(): - mne.gui.coregistration( - options.tabbed, inst=options.inst, subject=options.subject, + if options.pyvista: + mne.viz._coreg.CoregistrationUI( + info_file=options.inst, subject=options.subject, subjects_dir=subjects_dir, - guess_mri_subject=options.guess_mri_subject, - head_opacity=options.head_opacity, head_high_res=head_high_res, - trans=trans, scrollable=True, project_eeg=options.project_eeg, - orient_to_surface=options.orient_to_surface, - scale_by_distance=options.scale_by_distance, - mark_inside=options.mark_inside, interaction=options.interaction, - scale=options.scale, - advanced_rendering=options.advanced_rendering, - verbose=options.verbose) + head_resolution=bool(head_high_res), + head_transparency=bool(options.head_opacity), + orient_glyphs=bool(options.orient_to_surface), + standalone=True, + ) + else: + with ETSContext(): + mne.gui.coregistration( + options.tabbed, inst=options.inst, subject=options.subject, + subjects_dir=subjects_dir, + guess_mri_subject=options.guess_mri_subject, + head_opacity=options.head_opacity, head_high_res=head_high_res, + trans=trans, scrollable=True, project_eeg=options.project_eeg, + orient_to_surface=options.orient_to_surface, + scale_by_distance=options.scale_by_distance, + mark_inside=options.mark_inside, + interaction=options.interaction, scale=options.scale, + advanced_rendering=options.advanced_rendering, + verbose=options.verbose) mne.utils.run_command_if_main() diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 671095ca3a7..6bc83bf8aeb 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -33,7 +33,9 @@ class CoregistrationUI(HasTraits): _scale_mode = Unicode() _icp_fid_match = Unicode() - def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): + def __init__(self, info_file, subject=None, subjects_dir=None, + fids='auto', head_resolution=False, head_transparency=False, + orient_glyphs=False, standalone=False): from ..backends.renderer import _get_renderer self._actors = dict() self._surfaces = dict() @@ -62,18 +64,19 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._renderer = _get_renderer(bgcolor="grey", toolbar=True) self._renderer._window_close_connect(self._clean) - self._coreg = Coregistration(info, subject, subjects_dir, fids) + self._info = read_info(info_file) + self._coreg = Coregistration(self._info, subject, subjects_dir, fids) self._fids = fids - self._info = info self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, raise_error=True) self._subject = subject if subject is not None else self._subjects[0] - self._orient_glyphs = False + self._info_file = info_file + self._orient_glyphs = orient_glyphs self._hpi_coils = True self._head_shape_point = True self._eeg_channels = False - self._head_resolution = False - self._head_transparency = False + self._head_resolution = head_resolution + self._head_transparency = head_transparency self._omit_hsp_distance = 10.0 self._icp_n_iterations = self._default_icp_n_iterations self._icp_fid_match = self._default_icp_fid_matches[0] @@ -88,6 +91,8 @@ def __init__(self, info, subject=None, subjects_dir=None, fids='auto'): self._current_fiducial = self._default_fiducials[0] self._lock_fids = True self._scale_mode = "None" + if standalone: + self._renderer.figure.store["app"].exec() def _set_subjects_dir(self, subjects_dir): self._subjects_dir = subjects_dir From 1a0cdc35e92e9dfef74413b8f74cbd4f5caa0bc0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 11 Oct 2021 15:37:05 +0200 Subject: [PATCH 142/225] fix [ci skip] --- mne/viz/_coreg/_coreg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 6bc83bf8aeb..eb32e3450ec 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -617,6 +617,7 @@ def _configure_dock(self): name="info_file", desc="Load", func=self._set_info_file, + value=self._info_file, placeholder="Path to info", layout=layout, ) From fdbe4ab07c20ae5da2d2998781e650e185f29a48 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 11 Oct 2021 16:48:54 +0200 Subject: [PATCH 143/225] fix opacity [ci skip] --- mne/commands/mne_coreg.py | 2 +- mne/viz/_coreg/_coreg.py | 14 ++++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index cb363f75b38..89fada68c67 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -111,7 +111,7 @@ def run(): info_file=options.inst, subject=options.subject, subjects_dir=subjects_dir, head_resolution=bool(head_high_res), - head_transparency=bool(options.head_opacity), + head_opacity=options.head_opacity, orient_glyphs=bool(options.orient_to_surface), standalone=True, ) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index eb32e3450ec..37c92752c22 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -35,7 +35,7 @@ class CoregistrationUI(HasTraits): def __init__(self, info_file, subject=None, subjects_dir=None, fids='auto', head_resolution=False, head_transparency=False, - orient_glyphs=False, standalone=False): + head_opacity=0.4, orient_glyphs=False, standalone=False): from ..backends.renderer import _get_renderer self._actors = dict() self._surfaces = dict() @@ -46,10 +46,11 @@ def __init__(self, info_file, subject=None, subjects_dir=None, self._coord_frame = "mri" self._mouse_no_mvt = -1 self._omit_hsp_distance = 0.0 - self._opacity = 1.0 + self._head_opacity = 1.0 self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) + self._default_head_opacity = head_opacity self._default_fiducials = ("LPA", "Nasion", "RPA") self._default_icp_fid_matches = ('nearest', 'matched') self._default_icp_n_iterations = 20 @@ -254,8 +255,9 @@ def _head_resolution_changed(self, change=None): @observe("_head_transparency") def _head_transparency_changed(self, change=None): - self._opacity = 0.4 if self._head_transparency else 1.0 - self._actors["head"].GetProperty().SetOpacity(self._opacity) + self._head_opacity = self._default_head_opacity \ + if self._head_transparency else 1.0 + self._actors["head"].GetProperty().SetOpacity(self._head_opacity) self._renderer._update() @observe("_grow_hair") @@ -495,11 +497,11 @@ def _add_head_surface(self): head_actor, head_surf = _plot_head_surface( self._renderer, surface, self._subject, self._subjects_dir, bem, self._coord_frame, to_cf_t, - alpha=self._opacity) + alpha=self._head_opacity) except IOError: head_actor, head_surf = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, - bem, self._coord_frame, to_cf_t, alpha=self._opacity) + bem, self._coord_frame, to_cf_t, alpha=self._head_opacity) self._update_actor("head", head_actor) self._surfaces["head"] = head_surf From 4414da7d6f9563856d1255684e03db3b664295c2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 11 Oct 2021 16:58:49 +0200 Subject: [PATCH 144/225] update default value --- mne/viz/_coreg/_coreg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 37c92752c22..ce0c77aed2f 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -586,6 +586,7 @@ def _configure_dock(self): name="fid_file", desc="Load", func=self._set_fiducials_file, + value=self._fiducials_file, placeholder="Path to fiducials", layout=layout, ) From d12501d611c257ef29c10decab89673e59f221d2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 11 Oct 2021 17:05:40 +0200 Subject: [PATCH 145/225] add support for trans option [ci skip] --- mne/commands/mne_coreg.py | 1 + mne/viz/_coreg/_coreg.py | 9 ++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index 89fada68c67..5b8ae7bd993 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -113,6 +113,7 @@ def run(): head_resolution=bool(head_high_res), head_opacity=options.head_opacity, orient_glyphs=bool(options.orient_to_surface), + trans=trans, standalone=True, ) else: diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index ce0c77aed2f..7be4fcc9f4a 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -35,7 +35,8 @@ class CoregistrationUI(HasTraits): def __init__(self, info_file, subject=None, subjects_dir=None, fids='auto', head_resolution=False, head_transparency=False, - head_opacity=0.4, orient_glyphs=False, standalone=False): + head_opacity=0.4, orient_glyphs=False, trans=None, + standalone=False): from ..backends.renderer import _get_renderer self._actors = dict() self._surfaces = dict() @@ -92,6 +93,8 @@ def __init__(self, info_file, subject=None, subjects_dir=None, self._current_fiducial = self._default_fiducials[0] self._lock_fids = True self._scale_mode = "None" + if trans is not None: + self._load_trans(trans) if standalone: self._renderer.figure.store["app"].exec() @@ -535,8 +538,8 @@ def _load_trans(self, fname): rot_x, rot_y, rot_z = rotation_angles(mri_head_t) x, y, z = mri_head_t[:3, 3] self._coreg._update_params( - rot=[rot_x, rot_y, rot_z], - tra=[x, y, z], + rot=np.array([rot_x, rot_y, rot_z]), + tra=np.array([x, y, z]), ) self._update_plot("sensors") self._update_parameters() From 37a88a6fd6c41c7f5d03f7b060dde0a98880d373 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 11 Oct 2021 17:15:22 +0200 Subject: [PATCH 146/225] refactor [ci skip] --- mne/commands/mne_coreg.py | 4 ++-- mne/viz/_coreg/_coreg.py | 15 +++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index 5b8ae7bd993..7be0761e2de 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -110,9 +110,9 @@ def run(): mne.viz._coreg.CoregistrationUI( info_file=options.inst, subject=options.subject, subjects_dir=subjects_dir, - head_resolution=bool(head_high_res), + head_resolution=head_high_res, head_opacity=options.head_opacity, - orient_glyphs=bool(options.orient_to_surface), + orient_glyphs=options.orient_to_surface, trans=trans, standalone=True, ) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 7be4fcc9f4a..9240091e504 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -34,10 +34,13 @@ class CoregistrationUI(HasTraits): _icp_fid_match = Unicode() def __init__(self, info_file, subject=None, subjects_dir=None, - fids='auto', head_resolution=False, head_transparency=False, - head_opacity=0.4, orient_glyphs=False, trans=None, + fids='auto', head_resolution=None, head_transparency=None, + head_opacity=None, orient_glyphs=None, trans=None, standalone=False): from ..backends.renderer import _get_renderer + + def _get_default(var, val): + return var if var is not None else val self._actors = dict() self._surfaces = dict() self._widgets = dict() @@ -51,7 +54,7 @@ def __init__(self, info_file, subject=None, subjects_dir=None, self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) - self._default_head_opacity = head_opacity + self._default_head_opacity = _get_default(head_opacity, 0.4) self._default_fiducials = ("LPA", "Nasion", "RPA") self._default_icp_fid_matches = ('nearest', 'matched') self._default_icp_n_iterations = 20 @@ -73,12 +76,12 @@ def __init__(self, info_file, subject=None, subjects_dir=None, raise_error=True) self._subject = subject if subject is not None else self._subjects[0] self._info_file = info_file - self._orient_glyphs = orient_glyphs + self._orient_glyphs = _get_default(orient_glyphs, False) self._hpi_coils = True self._head_shape_point = True self._eeg_channels = False - self._head_resolution = head_resolution - self._head_transparency = head_transparency + self._head_resolution = _get_default(head_resolution, False) + self._head_transparency = _get_default(head_transparency, False) self._omit_hsp_distance = 10.0 self._icp_n_iterations = self._default_icp_n_iterations self._icp_fid_match = self._default_icp_fid_matches[0] From 118f9bf0aab3f48270b8d9dcda04d7471255a5e9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 12 Oct 2021 16:12:52 +0200 Subject: [PATCH 147/225] revert toolbar changes --- mne/viz/_coreg/_coreg.py | 2 +- mne/viz/backends/_pyvista.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 9240091e504..3dde7e4e53e 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -67,7 +67,7 @@ def _get_default(var, val): "hpi": 1.0, } - self._renderer = _get_renderer(bgcolor="grey", toolbar=True) + self._renderer = _get_renderer(bgcolor="grey") self._renderer._window_close_connect(self._clean) self._info = read_info(info_file) self._coreg = Coregistration(self._info, subject, subjects_dir, fids) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 9b36fa9e038..b2cfe947abd 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -66,7 +66,7 @@ def __init__(self, self.store['window_size'] = size self.store['shape'] = shape self.store['off_screen'] = off_screen - self.store['border'] = kwargs.get("border", False) + self.store['border'] = False # multi_samples > 1 is broken on macOS + Intel Iris + volume rendering self.store['multi_samples'] = 1 if sys.platform == 'darwin' else 4 @@ -74,8 +74,7 @@ def __init__(self, self.store['show'] = show self.store['title'] = title self.store['auto_update'] = False - self.store['menu_bar'] = kwargs.get("menu_bar", False) - self.store['toolbar'] = kwargs.get("toolbar", False) + self.store['menu_bar'] = False self.store['update_app_icon'] = False self._nrows, self._ncols = self.store['shape'] @@ -149,12 +148,12 @@ class _PyVistaRenderer(_AbstractRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=True, **kwargs): + notebook=None, smooth_shading=True): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, background_color=bgcolor, notebook=notebook, - smooth_shading=smooth_shading, **kwargs) + smooth_shading=smooth_shading) self.font_family = "arial" self.tube_n_sides = 20 antialias = _get_3d_option('antialias') From bc09fa9ef20431c502062562dbe14adf61d1bf46 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 12 Oct 2021 16:24:34 +0200 Subject: [PATCH 148/225] mark head surface for picking --- mne/viz/_coreg/_coreg.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3dde7e4e53e..0f412f87872 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -320,6 +320,8 @@ def _on_pick(self, vtk_picker, event): mesh = vtk_picker.GetDataSet() if mesh is None or cell_id == -1 or not self._mouse_no_mvt: return + if not getattr(mesh, "_picking_target", False): + return pos = np.array(vtk_picker.GetPickPosition()) vtk_cell = mesh.GetCell(cell_id) cell = [vtk_cell.GetPointId(point_id) for point_id @@ -508,6 +510,8 @@ def _add_head_surface(self): head_actor, head_surf = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, bem, self._coord_frame, to_cf_t, alpha=self._head_opacity) + # mark head surface mesh to restrict picking + head_surf._picking_target = True self._update_actor("head", head_actor) self._surfaces["head"] = head_surf From 3d4cc30c57ed242e0fbfb51f299d993d928a1c77 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 12 Oct 2021 17:02:50 +0200 Subject: [PATCH 149/225] refactor --- mne/viz/_coreg/_coreg.py | 75 +++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 0f412f87872..db7a3687093 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -12,7 +12,7 @@ _plot_hpi_coils, _plot_sensors) from ...transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) -from ...utils import get_subjects_dir +from ...utils import get_subjects_dir, _check_fname from traitlets import observe, HasTraits, Unicode, Bool, Float @@ -25,7 +25,7 @@ class CoregistrationUI(HasTraits): _info_file = Unicode() _orient_glyphs = Bool() _hpi_coils = Bool() - _head_shape_point = Bool() + _head_shape_points = Bool() _eeg_channels = Bool() _head_resolution = Bool() _head_transparency = Bool() @@ -35,8 +35,9 @@ class CoregistrationUI(HasTraits): def __init__(self, info_file, subject=None, subjects_dir=None, fids='auto', head_resolution=None, head_transparency=None, - head_opacity=None, orient_glyphs=None, trans=None, - standalone=False): + head_opacity=None, hpi_coils=None, head_shape_points=None, + eeg_channels=None, orient_glyphs=None, + trans=None, standalone=False): from ..backends.renderer import _get_renderer def _get_default(var, val): @@ -58,6 +59,7 @@ def _get_default(var, val): self._default_fiducials = ("LPA", "Nasion", "RPA") self._default_icp_fid_matches = ('nearest', 'matched') self._default_icp_n_iterations = 20 + self._default_omit_hsp_distance = 10.0 self._default_weights = { "lpa": 1.0, "nasion": 10.0, @@ -66,43 +68,50 @@ def _get_default(var, val): "eeg": 1.0, "hpi": 1.0, } + for fid in self._default_weights.keys(): + setattr(self, f"_{fid}_weight", self._default_weights[fid]) + self._fids = fids + self._info = read_info(info_file) self._renderer = _get_renderer(bgcolor="grey") self._renderer._window_close_connect(self._clean) - self._info = read_info(info_file) self._coreg = Coregistration(self._info, subject, subjects_dir, fids) - self._fids = fids - self._subjects_dir = get_subjects_dir(subjects_dir=subjects_dir, - raise_error=True) - self._subject = subject if subject is not None else self._subjects[0] - self._info_file = info_file - self._orient_glyphs = _get_default(orient_glyphs, False) - self._hpi_coils = True - self._head_shape_point = True - self._eeg_channels = False - self._head_resolution = _get_default(head_resolution, False) - self._head_transparency = _get_default(head_transparency, False) - self._omit_hsp_distance = 10.0 - self._icp_n_iterations = self._default_icp_n_iterations - self._icp_fid_match = self._default_icp_fid_matches[0] - for fid in self._default_weights.keys(): - setattr(self, f"_{fid}_weight", self._default_weights[fid]) + # set main traits + self._set_subjects_dir( + get_subjects_dir(subjects_dir=subjects_dir, raise_error=True)) + self._set_subject(_get_default(subject, self._get_subjects()[0])) + self._set_info_file(info_file) + self._set_orient_glyphs(_get_default(orient_glyphs, False)) + self._set_hpi_coils(_get_default(hpi_coils, True)) + self._set_head_shape_points(_get_default(head_shape_points, True)) + self._set_eeg_channels(_get_default(eeg_channels, False)) + self._set_head_resolution(_get_default(head_resolution, False)) + self._set_head_transparency(_get_default(head_transparency, False)) + self._set_omit_hsp_distance(self._default_omit_hsp_distance) + self._set_icp_n_iterations(self._default_icp_n_iterations) + self._set_icp_fid_match(self._default_icp_fid_matches[0]) + + # configure UI self._reset_fitting_parameters() self._configure_dock() self._configure_picking() - self._renderer.show() - self._current_fiducial = self._default_fiducials[0] - self._lock_fids = True - self._scale_mode = "None" + # once the docks are initialized + self._set_current_fiducial(self._default_fiducials[0]) + self._set_lock_fids(True) + self._set_scale_mode("None") if trans is not None: self._load_trans(trans) + + # must be done last + self._renderer.show() if standalone: self._renderer.figure.store["app"].exec() def _set_subjects_dir(self, subjects_dir): - self._subjects_dir = subjects_dir + self._subjects_dir = _check_fname( + subjects_dir, overwrite=True, must_exist=True, need_dir=True) def _set_subject(self, subject): self._subject = subject @@ -111,13 +120,15 @@ def _set_lock_fids(self, state): self._lock_fids = bool(state) def _set_fiducials_file(self, fname): - self._fiducials_file = fname + self._fiducials_file = _check_fname( + fname, overwrite=True, must_exist=True, need_dir=False) def _set_current_fiducial(self, fid): self._current_fiducial = fid.lower() def _set_info_file(self, fname): - self._info_file = fname + self._info_file = _check_fname( + fname, overwrite=True, must_exist=True, need_dir=False) def _set_omit_hsp_distance(self, distance): self._omit_hsp_distance = distance @@ -129,7 +140,7 @@ def _set_hpi_coils(self, state): self._hpi_coils = bool(state) def _set_head_shape_points(self, state): - self._head_shape_point = bool(state) + self._head_shape_points = bool(state) def _set_eeg_channels(self, state): self._eeg_channels = bool(state) @@ -246,7 +257,7 @@ def _orient_glyphs_changed(self, change=None): def _hpi_coils_changed(self, change=None): self._add_hpi_coils() - @observe("_head_shape_point") + @observe("_head_shape_points") def _head_shape_point_changed(self, change=None): self._add_head_shape_points() @@ -470,7 +481,7 @@ def _add_hpi_coils(self): self._update_actor("hpi_coils", hpi_actors) def _add_head_shape_points(self): - if self._head_shape_point: + if self._head_shape_points: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( @@ -677,7 +688,7 @@ def _configure_dock(self): ) self._widgets["show_hsp"] = self._renderer._dock_add_check_box( name="Show Head Shape Points", - value=self._head_shape_point, + value=self._head_shape_points, callback=self._set_head_shape_points, layout=layout ) From dbbca80a8a5013ff3637b8bb126444655209f789 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 12 Oct 2021 17:06:39 +0200 Subject: [PATCH 150/225] fix fids [ci skip] --- mne/viz/_coreg/_coreg.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index db7a3687093..62cdc8f9137 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -162,7 +162,7 @@ def _set_fiducial(self, value, coord): coords = ["X", "Y", "Z"] idx = coords.index(coord) getattr(self._coreg, f"_{fid}")[0][idx] = value / 1e3 - self._update_plot("fids") + self._update_plot("mri_fids") def _set_parameter(self, value, mode_name, coord): params = dict( @@ -347,7 +347,7 @@ def _on_pick(self, vtk_picker, event): self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() self._update_fiducials() - self._update_plot("fids") + self._update_plot("mri_fids") def _reset_fitting_parameters(self): self._forward_widget_command("icp_n_iterations", "set_value", @@ -384,8 +384,9 @@ def _update_plot(self, changes="all"): self._add_hpi_coils() if "eeg" in changes or forced or sensors: self._add_eeg_channels() - if "fids" in changes or forced or sensors: + if "head_fids" in changes or forced or sensors: self._add_head_fiducials() + if "mri_fids" in changes or forced or sensors: self._add_mri_fiducials() @contextmanager From 4aa101305d4e2f701ff65b0bfe6c9eb463e197b6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 12 Oct 2021 17:40:47 +0200 Subject: [PATCH 151/225] use _defaults [ci skip] --- mne/viz/_coreg/_coreg.py | 106 +++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 62cdc8f9137..3ad5dbab138 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -55,42 +55,61 @@ def _get_default(var, val): self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) - self._default_head_opacity = _get_default(head_opacity, 0.4) - self._default_fiducials = ("LPA", "Nasion", "RPA") - self._default_icp_fid_matches = ('nearest', 'matched') - self._default_icp_n_iterations = 20 - self._default_omit_hsp_distance = 10.0 - self._default_weights = { - "lpa": 1.0, - "nasion": 10.0, - "rpa": 1.0, - "hsp": 1.0, - "eeg": 1.0, - "hpi": 1.0, - } - for fid in self._default_weights.keys(): - setattr(self, f"_{fid}_weight", self._default_weights[fid]) + self._defaults = dict( + orient_glyphs=False, + hpi_coils=True, + head_shape_points=True, + eeg_channels=False, + head_resolution=False, + head_transparency=False, + head_opacity=_get_default(head_opacity, 0.4), + fiducials=("LPA", "Nasion", "RPA"), + fiducial="LPA", + lock_fids=True, + scale_modes=["None", "uniform", "3-axis"], + scale_mode="None", + icp_fid_matches=('nearest', 'matched'), + icp_fid_match='nearest', + icp_n_iterations=20, + omit_hsp_distance=10.0, + weights=dict( + lpa=1.0, + nasion=10.0, + rpa=1.0, + hsp=1.0, + eeg=1.0, + hpi=1.0, + ), + ) self._fids = fids self._info = read_info(info_file) self._renderer = _get_renderer(bgcolor="grey") self._renderer._window_close_connect(self._clean) self._coreg = Coregistration(self._info, subject, subjects_dir, fids) + for fid in self._defaults["weights"].keys(): + setattr(self, f"_{fid}_weight", self._defaults["weights"][fid]) # set main traits self._set_subjects_dir( get_subjects_dir(subjects_dir=subjects_dir, raise_error=True)) self._set_subject(_get_default(subject, self._get_subjects()[0])) self._set_info_file(info_file) - self._set_orient_glyphs(_get_default(orient_glyphs, False)) - self._set_hpi_coils(_get_default(hpi_coils, True)) - self._set_head_shape_points(_get_default(head_shape_points, True)) - self._set_eeg_channels(_get_default(eeg_channels, False)) - self._set_head_resolution(_get_default(head_resolution, False)) - self._set_head_transparency(_get_default(head_transparency, False)) - self._set_omit_hsp_distance(self._default_omit_hsp_distance) - self._set_icp_n_iterations(self._default_icp_n_iterations) - self._set_icp_fid_match(self._default_icp_fid_matches[0]) + self._set_orient_glyphs(_get_default(orient_glyphs, + self._defaults["orient_glyphs"])) + self._set_hpi_coils(_get_default(hpi_coils, + self._defaults["hpi_coils"])) + self._set_head_shape_points(_get_default(head_shape_points, + self._defaults["head_shape_points"])) + self._set_eeg_channels(_get_default(eeg_channels, + self._defaults["eeg_channels"])) + self._set_head_resolution(_get_default(head_resolution, + self._defaults["head_resolution"])) + self._set_head_transparency(_get_default(head_transparency, + self._defaults["head_transparency"])) + self._set_omit_hsp_distance(self._defaults["omit_hsp_distance"]) + self._set_icp_n_iterations(self._defaults["icp_n_iterations"]) + self._set_icp_fid_match(self._defaults["icp_fid_match"]) # configure UI self._reset_fitting_parameters() @@ -98,9 +117,9 @@ def _get_default(var, val): self._configure_picking() # once the docks are initialized - self._set_current_fiducial(self._default_fiducials[0]) - self._set_lock_fids(True) - self._set_scale_mode("None") + self._set_current_fiducial(self._defaults["fiducial"]) + self._set_lock_fids(self._defaults["lock_fids"]) + self._set_scale_mode(self._defaults["scale_mode"]) if trans is not None: self._load_trans(trans) @@ -272,7 +291,7 @@ def _head_resolution_changed(self, change=None): @observe("_head_transparency") def _head_transparency_changed(self, change=None): - self._head_opacity = self._default_head_opacity \ + self._head_opacity = self._defaults["head_opacity"] \ if self._head_transparency else 1.0 self._actors["head"].GetProperty().SetOpacity(self._head_opacity) self._renderer._update() @@ -341,8 +360,8 @@ def _on_pick(self, vtk_picker, event): idx = np.argmin(abs(vertices - pos), axis=0) vertex_id = cell[idx[0]] - default_fiducials = [s.lower() for s in self._default_fiducials] - idx = default_fiducials.index(self._current_fiducial.lower()) + fiducials = [s.lower() for s in self._defaults["fiducials"]] + idx = fiducials.index(self._current_fiducial.lower()) # XXX: add coreg.set_fids self._coreg._fid_points[idx] = self._surfaces["head"].points[vertex_id] self._coreg._reset_fiducials() @@ -351,15 +370,16 @@ def _on_pick(self, vtk_picker, event): def _reset_fitting_parameters(self): self._forward_widget_command("icp_n_iterations", "set_value", - self._default_icp_n_iterations) + self._defaults["icp_n_iterations"]) self._forward_widget_command("icp_fid_match", "set_value", - self._default_icp_fid_matches[0]) - weights_widgets = [f"{w}_weight" for w in self._default_weights.keys()] + self._defaults["icp_fid_match"]) + weights_widgets = [f"{w}_weight" + for w in self._defaults["weights"].keys()] self._forward_widget_command(weights_widgets, "set_value", - list(self._default_weights.values())) + list(self._defaults["weights"].values())) def _reset_fiducials(self): - self._set_current_fiducial(self._default_fiducials[0]) + self._set_current_fiducial(self._defaults["fiducial"]) def _omit_hsp(self): self._coreg.omit_head_shape_points(self._omit_hsp_distance / 1e3) @@ -613,8 +633,8 @@ def _configure_dock(self): layout=layout, ) self._widgets["fids"] = self._renderer._dock_add_radio_buttons( - value=self._default_fiducials[0], - rng=self._default_fiducials, + value=self._defaults["fiducial"], + rng=self._defaults["fiducials"], callback=self._set_current_fiducial, vertical=False, layout=layout, @@ -716,8 +736,8 @@ def _configure_dock(self): self._renderer._dock_initialize(name="Parameters", area="right") self._widgets["scaling_mode"] = self._renderer._dock_add_combo_box( name="Scaling Mode", - value="None", - rng=["None", "uniform", "3-axis"], + value=self._defaults["scale_mode"], + rng=self._defaults["scale_modes"], callback=self._set_scale_mode, compact=True, ) @@ -776,7 +796,7 @@ def _configure_dock(self): self._renderer._layout_add_widget(layout, hlayout) self._widgets["icp_n_iterations"] = self._renderer._dock_add_spin_box( name="Number Of ICP Iterations", - value=self._default_icp_n_iterations, + value=self._defaults["icp_n_iterations"], rng=[1, 100], callback=self._set_icp_n_iterations, compact=True, @@ -785,8 +805,8 @@ def _configure_dock(self): ) self._widgets["icp_fid_match"] = self._renderer._dock_add_combo_box( name="Fiducial point matching", - value=self._default_icp_fid_matches[0], - rng=self._default_icp_fid_matches, + value=self._defaults["icp_fid_match"], + rng=self._defaults["icp_fid_matches"], callback=self._set_icp_fid_match, compact=True, layout=layout @@ -796,7 +816,7 @@ def _configure_dock(self): layout=layout, ) hlayout = self._renderer._dock_add_layout(vertical=False) - for fid in self._default_fiducials: + for fid in self._defaults["fiducials"]: fid_lower = fid.lower() name = f"{fid_lower}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( From 54643eddaecda84e17e706f44b9853c495648a54 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 12 Oct 2021 17:45:53 +0200 Subject: [PATCH 152/225] nitpick [ci skip] --- mne/viz/_coreg/_coreg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3ad5dbab138..b0011d6e9b1 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -66,6 +66,7 @@ def _get_default(var, val): fiducials=("LPA", "Nasion", "RPA"), fiducial="LPA", lock_fids=True, + grow_hair=0.0, scale_modes=["None", "uniform", "3-axis"], scale_mode="None", icp_fid_matches=('nearest', 'matched'), @@ -107,6 +108,7 @@ def _get_default(var, val): self._defaults["head_resolution"])) self._set_head_transparency(_get_default(head_transparency, self._defaults["head_transparency"])) + self._set_grow_hair(self._defaults["grow_hair"]) self._set_omit_hsp_distance(self._defaults["omit_hsp_distance"]) self._set_icp_n_iterations(self._defaults["icp_n_iterations"]) self._set_icp_fid_match(self._defaults["icp_fid_match"]) From 04fb3ffd3cb76ac8085445bfb4d86236d87bced9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 13 Oct 2021 15:04:43 +0200 Subject: [PATCH 153/225] change spin box step --- mne/viz/_coreg/_coreg.py | 5 +---- mne/viz/backends/_abstract.py | 2 +- mne/viz/backends/_notebook.py | 7 ++++--- mne/viz/backends/_qt.py | 13 +++++++------ 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index b0011d6e9b1..3755377da8a 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -654,7 +654,6 @@ def _configure_dock(self): ), compact=True, double=True, - decimals=1, layout=hlayout ) self._renderer._layout_add_widget(layout, hlayout) @@ -681,7 +680,6 @@ def _configure_dock(self): value=self._omit_hsp_distance, rng=[0.0, 100.0], callback=self._set_omit_hsp_distance, - decimals=1, layout=hlayout, ) self._widgets["omit"] = self._renderer._dock_add_button( @@ -759,7 +757,6 @@ def _configure_dock(self): ), compact=True, double=True, - decimals=1, layout=hlayout ) @@ -779,7 +776,7 @@ def _configure_dock(self): ), compact=True, double=True, - decimals=1, + step=1, layout=hlayout ) diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index 7538b79acd2..6aba2ec13d8 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -544,7 +544,7 @@ def _dock_add_check_box(self, name, value, callback, layout=None): @abstractmethod def _dock_add_spin_box(self, name, value, rng, callback, - compact=True, double=True, decimals=2, + compact=True, double=True, step=None, layout=None): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 83cfc90b681..259aeadd688 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -9,7 +9,7 @@ import pyvista from IPython.display import display -from ipywidgets import (Button, Dropdown, FloatSlider, FloatText, HBox, +from ipywidgets import (Button, Dropdown, FloatSlider, BoundedFloatText, HBox, IntSlider, IntText, Text, VBox, IntProgress, Play, Checkbox, RadioButtons, jsdlink) @@ -115,14 +115,15 @@ def _dock_add_check_box(self, name, value, callback, layout=None): return _IpyWidget(widget) def _dock_add_spin_box(self, name, value, rng, callback, - compact=True, double=True, decimals=2, + compact=True, double=True, step=None, layout=None): layout = self._dock_named_layout(name, layout, compact) - klass = FloatText if double else IntText + klass = BoundedFloatText if double else IntText widget = klass( value=value, min=rng[0], max=rng[1], + step=step, readout=False, ) widget.observe(_generate_callback(callback), names='value') diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 59a437030ef..b5f401d5d79 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -125,7 +125,7 @@ def _dock_add_check_box(self, name, value, callback, layout=None): return _QtWidget(widget) def _dock_add_spin_box(self, name, value, rng, callback, - compact=True, double=True, decimals=2, + compact=True, double=True, step=None, layout=None): layout = self._dock_named_layout(name, layout, compact) value = value if double else int(value) @@ -133,12 +133,13 @@ def _dock_add_spin_box(self, name, value, rng, callback, widget.setAlignment(Qt.AlignCenter) widget.setMinimum(rng[0]) widget.setMaximum(rng[1]) - if double: - widget.setDecimals(decimals) - inc = (rng[1] - rng[0]) / 20. - inc = max(int(round(inc)), 1) if not double else inc widget.setKeyboardTracking(False) - widget.setSingleStep(inc) + if step is None: + inc = (rng[1] - rng[0]) / 20. + inc = max(int(round(inc)), 1) if not double else inc + widget.setSingleStep(inc) + else: + widget.setSingleStep(step) widget.setValue(value) widget.valueChanged.connect(callback) self._layout_add_widget(layout, widget) From 5d1f53750e6165edde7e3dc3d10cfa896d7117ad Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 13 Oct 2021 15:17:34 +0200 Subject: [PATCH 154/225] update plot at each fit_icp iteration [ci skip] --- mne/coreg.py | 11 ++++++++--- mne/viz/_coreg/_coreg.py | 2 +- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 98dd0af0d75..5304a4b0053 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -1797,7 +1797,7 @@ def set_fid_match(self, match): @verbose def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., rpa_weight=1., hsp_weight=1., eeg_weight=1., hpi_weight=1., - verbose=None): + callback=None, verbose=None): """Find MRI scaling, translation, and rotation to match HSP. Parameters @@ -1816,6 +1816,9 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., Relative weight for EEG. The default value is 1. hpi_weight : float Relative weight for HPI. The default value is 1. + callback : callable | None + A function to call on each iteration. Useful for status message + updates. It will be passed the keyword argument ``iteration``. %(verbose)s Returns @@ -1838,7 +1841,7 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., est = est[:[6, 7, None, 9][n_scale_params]] # Do the fits, assigning and evaluating at each step - for ii in range(n_iterations): + for iteration in range(n_iterations): head_pts, mri_pts, weights = self._setup_icp(n_scale_params) est = fit_matched_points(mri_pts, head_pts, scale=n_scale_params, x0=est, out='params', weights=weights) @@ -1850,10 +1853,12 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., else: self._update_params(rot=est[:3], tra=est[3:6], sca=est[6:9]) angle, move, scale = self._changes - self._log_dig_mri_distance(f' ICP {ii + 1:2d} ') + self._log_dig_mri_distance(f' ICP {iteration + 1:2d} ') if angle <= self._icp_angle and move <= self._icp_distance and \ all(scale <= self._icp_scale): break + if callback is not None: + callback(iteration) self._log_dig_mri_distance('End ') return self diff --git a/mne/viz/_coreg/_coreg.py b/mne/viz/_coreg/_coreg.py index 3755377da8a..b68e32182bf 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/viz/_coreg/_coreg.py @@ -565,9 +565,9 @@ def _fit_icp(self): lpa_weight=self._lpa_weight, nasion_weight=self._nasion_weight, rpa_weight=self._rpa_weight, + callback=lambda x: self._update_plot("sensors"), verbose=self._verbose, ) - self._update_plot("sensors") self._update_parameters() def _save_trans(self, fname): From bcd3f65d6621a6fdecd2ff1993f3a1c4cbe234cf Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 13 Oct 2021 15:31:24 +0200 Subject: [PATCH 155/225] nitpick --- mne/viz/_3d.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index ba97340a213..4d548970b75 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1051,9 +1051,9 @@ def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, if (d['kind'] == FIFF.FIFFV_POINT_EXTRA and d['coord_frame'] == FIFF.FIFFV_COORD_HEAD)]) ext_loc = apply_trans(to_cf_t['head'], ext_loc) + ext_loc = ext_loc[mask] if mask is not None else ext_loc color = defaults['extra_color'] scale = defaults['extra_scale'] - ext_loc = ext_loc[mask] if mask is not None else ext_loc if orient_glyphs: glyph_height = defaults['eegp_height'] glyph_center = (0., -defaults['eegp_height'], 0) @@ -1105,7 +1105,8 @@ def _plot_forward(renderer, fwd, to_cf_t): def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, - warn_meg, head_surf, units, sensor_opacity=0.8): + warn_meg, head_surf, units, sensor_opacity=0.8, + orient_glyphs=False): """Render sensors in a 3D scene.""" defaults = DEFAULTS['coreg'] ch_pos, sources, detectors = _ch_pos_in_coord_frame( From 999cdffc4f05c961795782296aa26f67412087db Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 13 Oct 2021 15:54:20 +0200 Subject: [PATCH 156/225] move to mne/gui [ci skip] --- mne/commands/mne_coreg.py | 2 +- mne/gui/__init__.py | 1 + mne/{viz/_coreg => gui}/_coreg.py | 22 +++++++++++----------- mne/viz/__init__.py | 1 - mne/viz/_coreg/__init__.py | 1 - 5 files changed, 13 insertions(+), 14 deletions(-) rename mne/{viz/_coreg => gui}/_coreg.py (98%) delete mode 100644 mne/viz/_coreg/__init__.py diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index 7be0761e2de..436315f20b5 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -107,7 +107,7 @@ def run(): except ImportError: pass # old Python2 if options.pyvista: - mne.viz._coreg.CoregistrationUI( + mne.gui.CoregistrationUI( info_file=options.inst, subject=options.subject, subjects_dir=subjects_dir, head_resolution=head_high_res, diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 3e18053cdea..19b965dd89a 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -8,6 +8,7 @@ from ..utils import _check_mayavi_version, verbose, get_config from ._backend import _testing_mode +from ._coreg import CoregistrationUI def _initialize_gui(frame, view=None): diff --git a/mne/viz/_coreg/_coreg.py b/mne/gui/_coreg.py similarity index 98% rename from mne/viz/_coreg/_coreg.py rename to mne/gui/_coreg.py index b68e32182bf..4b224507c19 100644 --- a/mne/viz/_coreg/_coreg.py +++ b/mne/gui/_coreg.py @@ -3,16 +3,16 @@ import os.path as op import numpy as np from functools import partial -from ...defaults import DEFAULTS -from ...io import read_info, read_fiducials -from ...io.pick import pick_types -from ...coreg import Coregistration, _is_mri_subject -from ...viz._3d import (_plot_head_surface, _plot_head_fiducials, - _plot_head_shape_points, _plot_mri_fiducials, - _plot_hpi_coils, _plot_sensors) -from ...transforms import (read_trans, write_trans, _ensure_trans, - rotation_angles, _get_transforms_to_coord_frame) -from ...utils import get_subjects_dir, _check_fname +from ..defaults import DEFAULTS +from ..io import read_info, read_fiducials +from ..io.pick import pick_types +from ..coreg import Coregistration, _is_mri_subject +from ..viz._3d import (_plot_head_surface, _plot_head_fiducials, + _plot_head_shape_points, _plot_mri_fiducials, + _plot_hpi_coils, _plot_sensors) +from ..transforms import (read_trans, write_trans, _ensure_trans, + rotation_angles, _get_transforms_to_coord_frame) +from ..utils import get_subjects_dir, _check_fname from traitlets import observe, HasTraits, Unicode, Bool, Float @@ -38,7 +38,7 @@ def __init__(self, info_file, subject=None, subjects_dir=None, head_opacity=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, trans=None, standalone=False): - from ..backends.renderer import _get_renderer + from ..viz.backends.renderer import _get_renderer def _get_default(var, val): return var if var is not None else val diff --git a/mne/viz/__init__.py b/mne/viz/__init__.py index c6ded4a1a40..0c5cf1ff919 100644 --- a/mne/viz/__init__.py +++ b/mne/viz/__init__.py @@ -33,4 +33,3 @@ get_brain_class) from . import backends from ._brain import Brain -from ._coreg import CoregistrationUI diff --git a/mne/viz/_coreg/__init__.py b/mne/viz/_coreg/__init__.py deleted file mode 100644 index 5f6e26049de..00000000000 --- a/mne/viz/_coreg/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from ._coreg import CoregistrationUI From fbe72477d92e4b7db078c095884f18836a050642 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 13 Oct 2021 15:57:22 +0200 Subject: [PATCH 157/225] fix toolbar [ci skip] --- mne/viz/backends/_pyvista.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index b2cfe947abd..baf31a4e0ce 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -54,8 +54,7 @@ def __init__(self, background_color='black', smooth_shading=True, off_screen=False, - notebook=False, - **kwargs): + notebook=False): self.plotter = plotter self.display = None self.background_color = background_color @@ -75,6 +74,7 @@ def __init__(self, self.store['title'] = title self.store['auto_update'] = False self.store['menu_bar'] = False + self.store['toolbar'] = False self.store['update_app_icon'] = False self._nrows, self._ncols = self.store['shape'] From 01ea53457cfcb4600aea09c8cb93698650844906 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 13 Oct 2021 16:19:45 +0200 Subject: [PATCH 158/225] add _display_message [ci skip] --- mne/coreg.py | 5 +++-- mne/gui/_coreg.py | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 5304a4b0053..0ed20a53392 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -1818,7 +1818,8 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., Relative weight for HPI. The default value is 1. callback : callable | None A function to call on each iteration. Useful for status message - updates. It will be passed the keyword argument ``iteration``. + updates. It will be passed the keyword arguments ``iteration`` + and ``n_iterations``. %(verbose)s Returns @@ -1858,7 +1859,7 @@ def fit_icp(self, n_iterations=20, lpa_weight=1., nasion_weight=10., all(scale <= self._icp_scale): break if callback is not None: - callback(iteration) + callback(iteration, n_iterations) self._log_dig_mri_distance('End ') return self diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 4b224507c19..14a5bd09b18 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -420,6 +420,17 @@ def _lock_plot(self): finally: self._plot_locked = old_plot_locked + @contextmanager + def _display_message(self, msg): + old_msg = self._actors["msg"].GetInput() + self._actors["msg"].SetInput(msg) + self._renderer._update() + try: + yield + finally: + self._actors["msg"].SetInput(old_msg) + self._renderer._update() + def _follow_fiducial_view(self): fid = self._current_fiducial.lower() view = dict(lpa='left', rpa='right', nasion='front') @@ -550,24 +561,26 @@ def _add_head_surface(self): self._surfaces["head"] = head_surf def _fit_fiducials(self): - self._coreg.fit_fiducials( - lpa_weight=self._lpa_weight, - nasion_weight=self._nasion_weight, - rpa_weight=self._rpa_weight, - verbose=self._verbose, - ) + with self._display_message("Fitting..."): + self._coreg.fit_fiducials( + lpa_weight=self._lpa_weight, + nasion_weight=self._nasion_weight, + rpa_weight=self._rpa_weight, + verbose=self._verbose, + ) self._update_plot("sensors") self._update_parameters() def _fit_icp(self): - self._coreg.fit_icp( - n_iterations=self._icp_n_iterations, - lpa_weight=self._lpa_weight, - nasion_weight=self._nasion_weight, - rpa_weight=self._rpa_weight, - callback=lambda x: self._update_plot("sensors"), - verbose=self._verbose, - ) + with self._display_message("Fitting..."): + self._coreg.fit_icp( + n_iterations=self._icp_n_iterations, + lpa_weight=self._lpa_weight, + nasion_weight=self._nasion_weight, + rpa_weight=self._rpa_weight, + callback=lambda x, y: self._update_plot("sensors"), + verbose=self._verbose, + ) self._update_parameters() def _save_trans(self, fname): From ebc6ba582044cb2cf2ccefa9bba62f1dc8f45119 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 14 Oct 2021 11:17:12 +0200 Subject: [PATCH 159/225] fix notebook [ci skip] --- mne/viz/backends/_notebook.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index 259aeadd688..ad8f44b914a 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -123,9 +123,10 @@ def _dock_add_spin_box(self, name, value, rng, callback, value=value, min=rng[0], max=rng[1], - step=step, readout=False, ) + if step is not None: + widget.step = step widget.observe(_generate_callback(callback), names='value') self._layout_add_widget(layout, widget) return _IpyWidget(widget) From 392b0d10006fcca033096e88acdd69ba6ccc80c8 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 14 Oct 2021 14:23:57 +0200 Subject: [PATCH 160/225] allow micro adjustments during picking --- mne/gui/_coreg.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 14a5bd09b18..b76a98370a9 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -494,6 +494,9 @@ def _add_mri_fiducials(self): mri_fids_actors = _plot_mri_fiducials( self._renderer, self._coreg._fid_points, self._subjects_dir, self._subject, to_cf_t, self._fid_colors) + # disable picking on the markers + for actor in mri_fids_actors: + actor.SetPickable(False) self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): From 010f362545624b9df68aa022f9220c89d917dd05 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 14 Oct 2021 15:20:45 +0200 Subject: [PATCH 161/225] add doc --- mne/gui/_coreg.py | 66 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index b76a98370a9..9f48ba28737 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -12,11 +12,50 @@ _plot_hpi_coils, _plot_sensors) from ..transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) -from ..utils import get_subjects_dir, _check_fname +from ..utils import get_subjects_dir, _check_fname, fill_doc from traitlets import observe, HasTraits, Unicode, Bool, Float +@fill_doc class CoregistrationUI(HasTraits): + """Class for coregistration assisted by graphical interface. + + Parameters + ---------- + %(info_str)s + %(subject)s + %(subjects_dir)s + fiducials : list | dict | str + The fiducials given in the MRI (surface RAS) coordinate + system. If a dict is provided it must be a dict with 3 entries + with keys 'lpa', 'rpa' and 'nasion' with as values coordinates in m. + If a list it must be a list of DigPoint instances as returned + by the read_fiducials function. + If set to 'estimated', the fiducials are initialized + automatically using fiducials defined in MNI space on fsaverage + template. If set to 'auto', one tries to find the fiducials + in a file with the canonical name (``bem/{subject}-fiducials.fif``) + and if abstent one falls back to 'estimated'. Defaults to 'auto'. + head_resolution : bool + If True, use a high-resolution head surface. Defaults to False. + head_transparency : bool + If True, display the head surface with transparency. Defaults to False. + head_opacity : float + The opacity of the head surface between 0 and 1. Defaults to 0.4. + hpi_coils : bool + If True, display the HPI coils. Defaults to True. + head_shape_points : bool + If True, display the head shape points. Defaults to True. + eeg_channels : bool + If True, display the EEG channels. Defaults to False. + orient_glyphs : bool + If True, orient the sensors towards the head surface. Default to False. + trans : str + The path to the Head<->MRI transform FIF file ("-trans.fif"). + standalone : bool + If True, start the Qt application event loop. Default to False. + """ + _subject = Unicode() _subjects_dir = Unicode() _lock_fids = Bool() @@ -33,10 +72,10 @@ class CoregistrationUI(HasTraits): _scale_mode = Unicode() _icp_fid_match = Unicode() - def __init__(self, info_file, subject=None, subjects_dir=None, - fids='auto', head_resolution=None, head_transparency=None, - head_opacity=None, hpi_coils=None, head_shape_points=None, - eeg_channels=None, orient_glyphs=None, + def __init__(self, info_str, subject=None, subjects_dir=None, + fiducials='auto', head_resolution=None, + head_transparency=None, head_opacity=None, hpi_coils=None, + head_shape_points=None, eeg_channels=None, orient_glyphs=None, trans=None, standalone=False): from ..viz.backends.renderer import _get_renderer @@ -83,11 +122,18 @@ def _get_default(var, val): ), ) - self._fids = fids - self._info = read_info(info_file) + if isinstance(info_str, str): + info_file = info_str + self._info = read_info(info_file) + else: + info_file = None + self._info = info_str + + self._fiducials = fiducials self._renderer = _get_renderer(bgcolor="grey") self._renderer._window_close_connect(self._clean) - self._coreg = Coregistration(self._info, subject, subjects_dir, fids) + self._coreg = Coregistration( + self._info, subject, subjects_dir, fiducials) for fid in self._defaults["weights"].keys(): setattr(self, f"_{fid}_weight", self._defaults["weights"][fid]) @@ -148,6 +194,8 @@ def _set_current_fiducial(self, fid): self._current_fiducial = fid.lower() def _set_info_file(self, fname): + if fname is None: + return self._info_file = _check_fname( fname, overwrite=True, must_exist=True, need_dir=False) @@ -228,7 +276,7 @@ def _subject_changed(self, changed=None): # XXX: add coreg.set_subject() self._coreg._subject = self._subject self._coreg._setup_bem() - self._coreg._setup_fiducials(self._fids) + self._coreg._setup_fiducials(self._fiducials) self._reset() rr = (self._coreg._processed_low_res_mri_points * self._coreg._scale) From b17abe0eb1b99698b18bb431b01843ee697eb104 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 14 Oct 2021 15:38:54 +0200 Subject: [PATCH 162/225] TST: fix compat/minimal --- mne/commands/mne_coreg.py | 35 ++++++++++------------------ mne/gui/__init__.py | 49 +++++++++++++++++++++++++-------------- 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index 436315f20b5..32a3d35767a 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -106,30 +106,19 @@ def run(): faulthandler.enable() except ImportError: pass # old Python2 - if options.pyvista: - mne.gui.CoregistrationUI( - info_file=options.inst, subject=options.subject, + with ETSContext(): + mne.gui.coregistration( + options.tabbed, inst=options.inst, subject=options.subject, subjects_dir=subjects_dir, - head_resolution=head_high_res, - head_opacity=options.head_opacity, - orient_glyphs=options.orient_to_surface, - trans=trans, - standalone=True, - ) - else: - with ETSContext(): - mne.gui.coregistration( - options.tabbed, inst=options.inst, subject=options.subject, - subjects_dir=subjects_dir, - guess_mri_subject=options.guess_mri_subject, - head_opacity=options.head_opacity, head_high_res=head_high_res, - trans=trans, scrollable=True, project_eeg=options.project_eeg, - orient_to_surface=options.orient_to_surface, - scale_by_distance=options.scale_by_distance, - mark_inside=options.mark_inside, - interaction=options.interaction, scale=options.scale, - advanced_rendering=options.advanced_rendering, - verbose=options.verbose) + guess_mri_subject=options.guess_mri_subject, + head_opacity=options.head_opacity, head_high_res=head_high_res, + trans=trans, scrollable=True, project_eeg=options.project_eeg, + orient_to_surface=options.orient_to_surface, + scale_by_distance=options.scale_by_distance, + mark_inside=options.mark_inside, + interaction=options.interaction, scale=options.scale, + advanced_rendering=options.advanced_rendering, + pyvista=options.pyvista, verbose=options.verbose) mne.utils.run_command_if_main() diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 19b965dd89a..69584d3bb2a 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -8,7 +8,6 @@ from ..utils import _check_mayavi_version, verbose, get_config from ._backend import _testing_mode -from ._coreg import CoregistrationUI def _initialize_gui(frame, view=None): @@ -27,7 +26,8 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, trans=None, scrollable=True, project_eeg=None, orient_to_surface=None, scale_by_distance=None, mark_inside=None, interaction=None, scale=None, - advanced_rendering=None, head_inside=True, verbose=None): + advanced_rendering=None, head_inside=True, + pyvista=False, verbose=None): """Coregister an MRI with a subject's head shape. The recommended way to use the GUI is through bash with: @@ -113,12 +113,16 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, If True (default), add opaque inner scalp head surface to help occlude points behind the head. + .. versionadded:: 0.23 + pyvista : bool + If True, use the PyVista/PyQt5 interface. Defaults to False. + .. versionadded:: 0.23 %(verbose)s Returns ------- - frame : instance of CoregFrame + frame : instance of CoregFrame or CoregistrationUI The coregistration frame. Notes @@ -175,20 +179,31 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, width = int(width) height = int(height) scale = float(scale) - _check_mayavi_version() - from ._backend import _check_backend - _check_backend() - from ._coreg_gui import CoregFrame, _make_view - view = _make_view(tabbed, split, width, height, scrollable) - frame = CoregFrame(inst, subject, subjects_dir, guess_mri_subject, - head_opacity, head_high_res, trans, config, - project_eeg=project_eeg, - orient_to_surface=orient_to_surface, - scale_by_distance=scale_by_distance, - mark_inside=mark_inside, interaction=interaction, - scale=scale, advanced_rendering=advanced_rendering, - head_inside=head_inside) - return _initialize_gui(frame, view) + if pyvista: + from ._coreg import CoregistrationUI + return CoregistrationUI( + info_str=inst, subject=subject, + subjects_dir=subjects_dir, + head_resolution=head_high_res, + head_opacity=head_opacity, + orient_glyphs=orient_to_surface, + trans=trans, standalone=True, + ) + else: + _check_mayavi_version() + from ._backend import _check_backend + _check_backend() + from ._coreg_gui import CoregFrame, _make_view + view = _make_view(tabbed, split, width, height, scrollable) + frame = CoregFrame(inst, subject, subjects_dir, guess_mri_subject, + head_opacity, head_high_res, trans, config, + project_eeg=project_eeg, + orient_to_surface=orient_to_surface, + scale_by_distance=scale_by_distance, + mark_inside=mark_inside, interaction=interaction, + scale=scale, advanced_rendering=advanced_rendering, + head_inside=head_inside) + return _initialize_gui(frame, view) def fiducials(subject=None, fid_file=None, subjects_dir=None): From daf1cd7683b20c172f7370774d149dcb240950a0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 14 Oct 2021 15:39:59 +0200 Subject: [PATCH 163/225] nitpick --- mne/commands/mne_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index 32a3d35767a..9285daca15e 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -115,8 +115,8 @@ def run(): trans=trans, scrollable=True, project_eeg=options.project_eeg, orient_to_surface=options.orient_to_surface, scale_by_distance=options.scale_by_distance, - mark_inside=options.mark_inside, - interaction=options.interaction, scale=options.scale, + mark_inside=options.mark_inside, interaction=options.interaction, + scale=options.scale, advanced_rendering=options.advanced_rendering, pyvista=options.pyvista, verbose=options.verbose) From 8235f32010d1f7debe93ca79d72921a7595e5eb3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 10:22:57 +0200 Subject: [PATCH 164/225] reduce number of actors --- mne/viz/_3d.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index b74647e6753..df8954e9c66 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1115,6 +1115,7 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), ecog=list(), seeg=list(), dbs=list()) scalar = 1 if units == 'm' else 1e3 + sens_loc = list() for ch_name, ch_coord in ch_pos.items(): ch_type = channel_type(info, info.ch_names.index(ch_name)) # for default picking @@ -1135,11 +1136,7 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors[ch_type].append(actor) else: if plot_sensors: - actor, _ = renderer.sphere( - center=tuple(ch_coord * scalar), color=color, - scale=defaults[ch_type + '_scale'] * scalar, - opacity=sensor_opacity) - actors[ch_type].append(actor) + sens_loc.append(ch_coord) if ch_name in sources and 'sources' in fnirs: actor, _ = renderer.sphere( center=tuple(sources[ch_name] * scalar), @@ -1162,6 +1159,18 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, radius=0.001 * scalar) actors[ch_type].append(actor) + # add eeg + if len(sens_loc) > 0: + sens_loc = np.array(sens_loc) + if orient_glyphs: + pass + else: + actor, _ = renderer.sphere( + center=sens_loc * scalar, color=color, + scale=defaults['eeg_scale'] * scalar, + opacity=sensor_opacity) + actors['eeg'].append(actor) + # add projected eeg eeg_indices = pick_types(info, eeg=True) if eeg_indices.size > 0 and 'projected' in eeg: From 921c9550816116850fe3ea2c3093bb6d6461fb5c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 10:28:58 +0200 Subject: [PATCH 165/225] orient eeg channels glyphs --- mne/gui/_coreg.py | 4 +++- mne/viz/_3d.py | 18 +++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 9f48ba28737..bf686fc6326 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -321,6 +321,7 @@ def _info_file_changed(self, change=None): def _orient_glyphs_changed(self, change=None): self._add_hpi_coils() self._add_head_shape_points() + self._add_eeg_channels() @observe("_hpi_coils") def _hpi_coils_changed(self, change=None): @@ -586,7 +587,8 @@ def _add_eeg_channels(self): eeg_actors = _plot_sensors( self._renderer, self._info, to_cf_t, picks, meg=False, eeg=eeg, fnirs=False, warn_meg=False, head_surf=self._head_geo, - units='m', sensor_opacity=1.0) + units='m', sensor_opacity=1.0, + orient_glyphs=self._orient_glyphs, surf=self._head_geo) eeg_actors = eeg_actors["eeg"] else: eeg_actors = None diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index df8954e9c66..23b0ebacedf 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1106,7 +1106,7 @@ def _plot_forward(renderer, fwd, to_cf_t): def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, warn_meg, head_surf, units, sensor_opacity=0.8, - orient_glyphs=False): + orient_glyphs=False, surf=None): """Render sensors in a 3D scene.""" defaults = DEFAULTS['coreg'] ch_pos, sources, detectors = _ch_pos_in_coord_frame( @@ -1163,13 +1163,25 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, if len(sens_loc) > 0: sens_loc = np.array(sens_loc) if orient_glyphs: - pass + glyph_height = defaults['eegp_height'] + glyph_center = (0., -defaults['eegp_height'], 0) + resolution = glyph_resolution = 16 + scalars, vectors = _orient_glyphs(sens_loc, surf) + x, y, z = sens_loc.T + u, v, w = vectors.T + actor, _ = renderer.quiver3d( + x, y, z, u, v, w, color=color, scale=defaults['eeg_scale'], + mode="cylinder", + glyph_height=glyph_height, glyph_center=glyph_center, + resolution=resolution, glyph_resolution=glyph_resolution, + glyph_radius=None, opacity=sensor_opacity, scale_mode='vector', + scalars=scalars) else: actor, _ = renderer.sphere( center=sens_loc * scalar, color=color, scale=defaults['eeg_scale'] * scalar, opacity=sensor_opacity) - actors['eeg'].append(actor) + actors['eeg'].append(actor) # add projected eeg eeg_indices = pick_types(info, eeg=True) From 229eaaff3a6d35f7f0504f59fa01679f68a5e778 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 10:30:53 +0200 Subject: [PATCH 166/225] refactor --- mne/gui/_coreg.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index bf686fc6326..2cbc3995b9e 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -319,25 +319,23 @@ def _info_file_changed(self, change=None): @observe("_orient_glyphs") def _orient_glyphs_changed(self, change=None): - self._add_hpi_coils() - self._add_head_shape_points() - self._add_eeg_channels() + self._update_plot(["hpi", "hsp", "eeg"]) @observe("_hpi_coils") def _hpi_coils_changed(self, change=None): - self._add_hpi_coils() + self._update_plot("hpi") @observe("_head_shape_points") def _head_shape_point_changed(self, change=None): - self._add_head_shape_points() + self._update_plot("hsp") @observe("_eeg_channels") def _eeg_channels_changed(self, change=None): - self._add_eeg_channels() + self._update_plot("eeg") @observe("_head_resolution") def _head_resolution_changed(self, change=None): - self._add_head_surface() + self._update_plot("head") self._grow_hair_changed() @observe("_head_transparency") From 84cf036a0072a618ffc24eb58178c3046502e316 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 10:37:12 +0200 Subject: [PATCH 167/225] change default --- mne/gui/_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 2cbc3995b9e..b376138316f 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -47,7 +47,7 @@ class CoregistrationUI(HasTraits): head_shape_points : bool If True, display the head shape points. Defaults to True. eeg_channels : bool - If True, display the EEG channels. Defaults to False. + If True, display the EEG channels. Defaults to True. orient_glyphs : bool If True, orient the sensors towards the head surface. Default to False. trans : str @@ -98,7 +98,7 @@ def _get_default(var, val): orient_glyphs=False, hpi_coils=True, head_shape_points=True, - eeg_channels=False, + eeg_channels=True, head_resolution=False, head_transparency=False, head_opacity=_get_default(head_opacity, 0.4), From d9e5b47b92f7126c1d70ec3a00fea4f48f35bd27 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 11:07:13 +0200 Subject: [PATCH 168/225] refactor --- mne/viz/_3d.py | 59 +++++++++++++++++++------------------------------- 1 file changed, 22 insertions(+), 37 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 23b0ebacedf..eaedcf4b376 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -992,18 +992,8 @@ def _plot_hpi_coils(renderer, info, to_cf_t, opacity=0.5, color = defaults['hpi_color'] scale = defaults['hpi_scale'] if orient_glyphs: - glyph_height = defaults['eegp_height'] - glyph_center = (0., -defaults['eegp_height'], 0) - resolution = glyph_resolution = 16 - scalars, vectors = _orient_glyphs(hpi_loc, surf) - x, y, z = hpi_loc.T - u, v, w = vectors.T - actor, _ = renderer.quiver3d( - x, y, z, u, v, w, color=color, scale=scale, mode="cylinder", - glyph_height=glyph_height, glyph_center=glyph_center, - resolution=resolution, glyph_resolution=glyph_resolution, - glyph_radius=None, opacity=opacity, scale_mode='vector', - scalars=scalars) + actor, _ = _plot_oriented_glyphs( + renderer, hpi_loc, surf, color, scale, opacity=opacity) else: actor, _ = renderer.sphere(center=hpi_loc, color=color, scale=scale, opacity=opacity, backface_culling=True) @@ -1043,6 +1033,21 @@ def _orient_glyphs(pts, surf): return scalars, vectors +def _plot_oriented_glyphs(renderer, loc, surf, color, scale, mode="cylinder", + opacity=1): + defaults = DEFAULTS['coreg'] + scalars, vectors = _orient_glyphs(loc, surf) + x, y, z = loc.T + u, v, w = vectors.T + return renderer.quiver3d( + x, y, z, u, v, w, color=color, scale=scale, + mode=mode, glyph_height=defaults['eegp_height'], + glyph_center=(0., -defaults['eegp_height'], 0), + resolution=16, glyph_resolution=16, + glyph_radius=None, opacity=opacity, scale_mode='vector', + scalars=scalars) + + def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, orient_glyphs=False, surf=None, mask=None): defaults = DEFAULTS['coreg'] @@ -1055,18 +1060,8 @@ def _plot_head_shape_points(renderer, info, to_cf_t, opacity=0.25, color = defaults['extra_color'] scale = defaults['extra_scale'] if orient_glyphs: - glyph_height = defaults['eegp_height'] - glyph_center = (0., -defaults['eegp_height'], 0) - resolution = glyph_resolution = 16 - scalars, vectors = _orient_glyphs(ext_loc, surf) - x, y, z = ext_loc.T - u, v, w = vectors.T - actor, _ = renderer.quiver3d( - x, y, z, u, v, w, color=color, scale=scale, mode="cylinder", - glyph_height=glyph_height, glyph_center=glyph_center, - resolution=resolution, glyph_resolution=glyph_resolution, - glyph_radius=None, opacity=opacity, scale_mode='vector', - scalars=scalars) + actor, _ = _plot_oriented_glyphs( + renderer, ext_loc, surf, color, scale, opacity=opacity) else: actor, _ = renderer.sphere(center=ext_loc, color=color, scale=scale, opacity=opacity, @@ -1163,19 +1158,9 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, if len(sens_loc) > 0: sens_loc = np.array(sens_loc) if orient_glyphs: - glyph_height = defaults['eegp_height'] - glyph_center = (0., -defaults['eegp_height'], 0) - resolution = glyph_resolution = 16 - scalars, vectors = _orient_glyphs(sens_loc, surf) - x, y, z = sens_loc.T - u, v, w = vectors.T - actor, _ = renderer.quiver3d( - x, y, z, u, v, w, color=color, scale=defaults['eeg_scale'], - mode="cylinder", - glyph_height=glyph_height, glyph_center=glyph_center, - resolution=resolution, glyph_resolution=glyph_resolution, - glyph_radius=None, opacity=sensor_opacity, scale_mode='vector', - scalars=scalars) + actor, _ = _plot_oriented_glyphs( + renderer, sens_loc, surf, color, defaults['eeg_scale'], + opacity=sensor_opacity) else: actor, _ = renderer.sphere( center=sens_loc * scalar, color=color, From 01ed71ecafd2333c872f66f2c6cc40ded89ad3be Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 16:02:12 +0200 Subject: [PATCH 169/225] restore temporarily --- mne/viz/_3d.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index eaedcf4b376..dfa949d9bec 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1162,10 +1162,11 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, renderer, sens_loc, surf, color, defaults['eeg_scale'], opacity=sensor_opacity) else: - actor, _ = renderer.sphere( - center=sens_loc * scalar, color=color, - scale=defaults['eeg_scale'] * scalar, - opacity=sensor_opacity) + for loc in sens_loc: + actor, _ = renderer.sphere( + center=loc * scalar, color=color, + scale=defaults['eeg_scale'] * scalar, + opacity=sensor_opacity) actors['eeg'].append(actor) # add projected eeg From 18b9009c3b141552f058744f9b9a0f477d9e943c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 16:05:21 +0200 Subject: [PATCH 170/225] fix --- mne/viz/_3d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index dfa949d9bec..768c805521a 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1161,13 +1161,14 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actor, _ = _plot_oriented_glyphs( renderer, sens_loc, surf, color, defaults['eeg_scale'], opacity=sensor_opacity) + actors['eeg'].append(actor) else: for loc in sens_loc: actor, _ = renderer.sphere( center=loc * scalar, color=color, scale=defaults['eeg_scale'] * scalar, opacity=sensor_opacity) - actors['eeg'].append(actor) + actors['eeg'].append(actor) # add projected eeg eeg_indices = pick_types(info, eeg=True) From e790d18c5d2dfbbf8746b91bf0af046a72f540b8 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 16:39:42 +0200 Subject: [PATCH 171/225] update doc --- mne/gui/__init__.py | 10 ++++------ mne/gui/_coreg.py | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 69584d3bb2a..fca2a51ed32 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -182,12 +182,10 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, if pyvista: from ._coreg import CoregistrationUI return CoregistrationUI( - info_str=inst, subject=subject, - subjects_dir=subjects_dir, - head_resolution=head_high_res, - head_opacity=head_opacity, - orient_glyphs=orient_to_surface, - trans=trans, standalone=True, + info_str=inst, subject=subject, subjects_dir=subjects_dir, + head_resolution=head_high_res, head_opacity=head_opacity, + orient_glyphs=orient_to_surface, trans=trans, + size=(width, height), standalone=True, ) else: _check_mayavi_version() diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index b376138316f..90e9c0576dd 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -52,6 +52,13 @@ class CoregistrationUI(HasTraits): If True, orient the sensors towards the head surface. Default to False. trans : str The path to the Head<->MRI transform FIF file ("-trans.fif"). + size : tuple + The dimensions (width, height) of the rendering view. The default is + (800, 600). + bgcolor : tuple | str + The background color as a tuple (red, green, blue) of float + values between 0 and 1 or a valid color name (i.e. 'white' + or 'w'). Defaults to 'grey'. standalone : bool If True, start the Qt application event loop. Default to False. """ @@ -76,7 +83,7 @@ def __init__(self, info_str, subject=None, subjects_dir=None, fiducials='auto', head_resolution=None, head_transparency=None, head_opacity=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, - trans=None, standalone=False): + trans=None, size=None, bgcolor=None, standalone=False): from ..viz.backends.renderer import _get_renderer def _get_default(var, val): @@ -95,6 +102,8 @@ def _get_default(var, val): DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) self._defaults = dict( + size=_get_default(size, (800, 600)), + bgcolor=_get_default(bgcolor, "grey"), orient_glyphs=False, hpi_coils=True, head_shape_points=True, @@ -130,7 +139,8 @@ def _get_default(var, val): self._info = info_str self._fiducials = fiducials - self._renderer = _get_renderer(bgcolor="grey") + self._renderer = _get_renderer( + size=self._defaults["size"], bgcolor=self._defaults["bgcolor"]) self._renderer._window_close_connect(self._clean) self._coreg = Coregistration( self._info, subject, subjects_dir, fiducials) From 808dc4f41e339a7b6cd86aa5d497d49e812e0731 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 16:40:02 +0200 Subject: [PATCH 172/225] update doc conf --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index 044c00e0ebe..eaaa4fea005 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -259,7 +259,7 @@ 'mapping', 'to', 'any', # unlinkable 'mayavi.mlab.pipeline.surface', - 'CoregFrame', 'Kit2FiffFrame', 'FiducialsFrame', + 'CoregFrame', 'Kit2FiffFrame', 'FiducialsFrame', 'CoregistrationUI' 'IntracranialElectrodeLocator' } numpydoc_validate = True From 28f85514f7b9155f9866340a52269762ddff33bd Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 16:40:55 +0200 Subject: [PATCH 173/225] fix --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index eaaa4fea005..849ad85ad5e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -259,7 +259,7 @@ 'mapping', 'to', 'any', # unlinkable 'mayavi.mlab.pipeline.surface', - 'CoregFrame', 'Kit2FiffFrame', 'FiducialsFrame', 'CoregistrationUI' + 'CoregFrame', 'Kit2FiffFrame', 'FiducialsFrame', 'CoregistrationUI', 'IntracranialElectrodeLocator' } numpydoc_validate = True From 31727970c37efdf2243496f5deda74e3bc8baf86 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 16:46:20 +0200 Subject: [PATCH 174/225] restore --- mne/coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/coreg.py b/mne/coreg.py index 0ed20a53392..947d07cb718 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -26,6 +26,7 @@ # namespace, too) from ._freesurfer import (_read_mri_info, get_mni_fiducials, # noqa: F401 estimate_head_mri_t) # noqa: F401 +from .label import read_label, Label from .source_space import (add_source_space_distances, read_source_spaces, # noqa: E501,F401 write_source_spaces) from .surface import (read_surface, write_surface, _normalize_vectors, @@ -896,7 +897,6 @@ def scale_labels(subject_to, pattern=None, overwrite=False, subject_from=None, subjects_dir : None | str Override the SUBJECTS_DIR environment variable. """ - from .label import read_label, Label subjects_dir, subject_from, scale, _ = _scale_params( subject_to, subject_from, scale, subjects_dir) From 775296be5e8407b150bdaeb4fb3863373c3662f6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 17:02:25 +0200 Subject: [PATCH 175/225] update layout --- mne/gui/_coreg.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 90e9c0576dd..18165b1c827 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -888,34 +888,33 @@ def _configure_dock(self): name="Weights", layout=layout, ) - hlayout = self._renderer._dock_add_layout(vertical=False) - for fid in self._defaults["fiducials"]: - fid_lower = fid.lower() - name = f"{fid_lower}_weight" + for point, fid in zip(("HSP", "EEG", "HPI"), + self._defaults["fiducials"]): + hlayout = self._renderer._dock_add_layout(vertical=False) + point_lower = point.lower() + name = f"{point_lower}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( - name=fid, - value=getattr(self, f"_{fid_lower}_weight"), + name=point, + value=getattr(self, f"_{point_lower}_weight"), rng=[1., 100.], - callback=partial(self._set_point_weight, point=fid_lower), + callback=partial(self._set_point_weight, point=point_lower), compact=True, double=True, layout=hlayout ) - self._renderer._layout_add_widget(layout, hlayout) - hlayout = self._renderer._dock_add_layout(vertical=False) - for point in ("HSP", "EEG", "HPI"): - point_lower = point.lower() - name = f"{point_lower}_weight" + + fid_lower = fid.lower() + name = f"{fid_lower}_weight" self._widgets[name] = self._renderer._dock_add_spin_box( - name=point, - value=getattr(self, f"_{point_lower}_weight"), + name=fid, + value=getattr(self, f"_{fid_lower}_weight"), rng=[1., 100.], - callback=partial(self._set_point_weight, point=point_lower), + callback=partial(self._set_point_weight, point=fid_lower), compact=True, double=True, layout=hlayout ) - self._renderer._layout_add_widget(layout, hlayout) + self._renderer._layout_add_widget(layout, hlayout) self._renderer._dock_add_button( name="Reset Fitting Options", callback=self._reset_fitting_parameters, From d9a45bd379933c3763a1b75c800bb191a8194ccd Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 15 Oct 2021 17:04:23 +0200 Subject: [PATCH 176/225] revert 01ed71e --- mne/viz/_3d.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 768c805521a..eaedcf4b376 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1161,14 +1161,12 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actor, _ = _plot_oriented_glyphs( renderer, sens_loc, surf, color, defaults['eeg_scale'], opacity=sensor_opacity) - actors['eeg'].append(actor) else: - for loc in sens_loc: - actor, _ = renderer.sphere( - center=loc * scalar, color=color, - scale=defaults['eeg_scale'] * scalar, - opacity=sensor_opacity) - actors['eeg'].append(actor) + actor, _ = renderer.sphere( + center=sens_loc * scalar, color=color, + scale=defaults['eeg_scale'] * scalar, + opacity=sensor_opacity) + actors['eeg'].append(actor) # add projected eeg eeg_indices = pick_types(info, eeg=True) From e96a2ffb40b0ff0b1b3190de42b51f69e47ed4c7 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 16:18:43 +0200 Subject: [PATCH 177/225] add support for info_file=None [ci skip] --- mne/coreg.py | 35 +++++++++++++++++++++++------------ mne/gui/__init__.py | 2 +- mne/gui/_coreg.py | 32 ++++++++++++++++++++++---------- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/mne/coreg.py b/mne/coreg.py index 947d07cb718..cad0d1b5425 100644 --- a/mne/coreg.py +++ b/mne/coreg.py @@ -20,7 +20,6 @@ from .io import read_fiducials, write_fiducials, read_info from .io.constants import FIFF -from .io.meas_info import Info from .io._digitization import _get_data_as_dict_from_dig # keep get_mni_fiducials for backward compat (no burden to keep in this # namespace, too) @@ -1283,7 +1282,7 @@ class Coregistration(object): Parameters ---------- - info : instance of Info + info : instance of Info | None The measurement info. %(subject)s %(subjects_dir)s @@ -1321,7 +1320,6 @@ class Coregistration(object): """ def __init__(self, info, subject, subjects_dir=None, fiducials='auto'): - _validate_type(info, Info, 'info') self._info = info self._subject = _check_subject(subject, subject) self._subjects_dir = get_subjects_dir(subjects_dir, raise_error=True) @@ -1346,21 +1344,34 @@ def __init__(self, info, subject, subjects_dir=None, fiducials='auto'): self._hsp_weight = 1. self._eeg_weight = 1. self._hpi_weight = 1. - self._extra_points_filter = None - self._dig_dict = _get_data_as_dict_from_dig( - dig=self._info['dig'], - exclude_ref_channel=False - ) - # adjustments - self._dig_dict['rpa'] = np.array([self._dig_dict['rpa']], float) - self._dig_dict['nasion'] = np.array([self._dig_dict['nasion']], float) - self._dig_dict['lpa'] = np.array([self._dig_dict['lpa']], float) + self._setup_digs() self._setup_bem() self._setup_fiducials(fiducials) self.reset() + def _setup_digs(self): + if self._info is None: + self._dig_dict = dict( + hpi=np.zeros((1, 3)), + dig_ch_pos_location=np.zeros((1, 3)), + hsp=np.zeros((1, 3)), + rpa=np.zeros((1, 3)), + nasion=np.zeros((1, 3)), + lpa=np.zeros((1, 3)), + ) + else: + self._dig_dict = _get_data_as_dict_from_dig( + dig=self._info['dig'], + exclude_ref_channel=False + ) + # adjustments + self._dig_dict['rpa'] = np.array([self._dig_dict['rpa']], float) + self._dig_dict['nasion'] = \ + np.array([self._dig_dict['nasion']], float) + self._dig_dict['lpa'] = np.array([self._dig_dict['lpa']], float) + def _setup_bem(self): # find high-res head model (if possible) high_res_path = _find_head_bem(self._subject, self._subjects_dir, diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index fca2a51ed32..c7577a96604 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -182,7 +182,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, if pyvista: from ._coreg import CoregistrationUI return CoregistrationUI( - info_str=inst, subject=subject, subjects_dir=subjects_dir, + info_file=inst, subject=subject, subjects_dir=subjects_dir, head_resolution=head_high_res, head_opacity=head_opacity, orient_glyphs=orient_to_surface, trans=trans, size=(width, height), standalone=True, diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 18165b1c827..6b64da035ca 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -22,7 +22,8 @@ class CoregistrationUI(HasTraits): Parameters ---------- - %(info_str)s + info_file : None | str + The FIFF file with digitizer data for coregistration. %(subject)s %(subjects_dir)s fiducials : list | dict | str @@ -79,7 +80,7 @@ class CoregistrationUI(HasTraits): _scale_mode = Unicode() _icp_fid_match = Unicode() - def __init__(self, info_str, subject=None, subjects_dir=None, + def __init__(self, info_file, subject=None, subjects_dir=None, fiducials='auto', head_resolution=None, head_transparency=None, head_opacity=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, @@ -131,13 +132,10 @@ def _get_default(var, val): ), ) - if isinstance(info_str, str): - info_file = info_str - self._info = read_info(info_file) + if info_file is None: + self._info = None else: - info_file = None - self._info = info_str - + self._info = read_info(info_file) self._fiducials = fiducials self._renderer = _get_renderer( size=self._defaults["size"], bgcolor=self._defaults["bgcolor"]) @@ -325,6 +323,7 @@ def _info_file_changed(self, change=None): self._info = read_info(self._info_file) # XXX: add coreg.set_info() self._coreg._info = self._info + self._coreg._setup_digs() self._reset() @observe("_orient_glyphs") @@ -546,6 +545,8 @@ def _update_actor(self, actor_name, actor): self._renderer._update() def _add_mri_fiducials(self): + if self._info is None: + return to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) mri_fids_actors = _plot_mri_fiducials( @@ -557,6 +558,8 @@ def _add_mri_fiducials(self): self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): + if self._info is None: + return to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) head_fids_actors = _plot_head_fiducials( @@ -564,6 +567,8 @@ def _add_head_fiducials(self): self._update_actor("head_fiducials", head_fids_actors) def _add_hpi_coils(self): + if self._info is None: + return if self._hpi_coils: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) @@ -575,6 +580,8 @@ def _add_hpi_coils(self): self._update_actor("hpi_coils", hpi_actors) def _add_head_shape_points(self): + if self._info is None: + return if self._head_shape_points: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) @@ -587,6 +594,8 @@ def _add_head_shape_points(self): self._update_actor("head_shape_points", hsp_actors) def _add_eeg_channels(self): + if self._info is None: + return if self._eeg_channels: eeg = ["original"] to_cf_t = _get_transforms_to_coord_frame( @@ -605,8 +614,11 @@ def _add_eeg_channels(self): def _add_head_surface(self): bem = None surface = "head-dense" if self._head_resolution else "head" - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) + if self._info is None: + to_cf_t = dict(mri=np.eye(4), head=np.eye(4)) + else: + to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=self._coord_frame) try: head_actor, head_surf = _plot_head_surface( self._renderer, surface, self._subject, From 9b1fef6f480b1651a6a896dbaaabcf11d94687bb Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 16:37:05 +0200 Subject: [PATCH 178/225] refine requirements [ci skip] --- mne/gui/_coreg.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 6b64da035ca..163a4b0480a 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -132,10 +132,13 @@ def _get_default(var, val): ), ) - if info_file is None: - self._info = None - else: - self._info = read_info(info_file) + info = read_info(info_file) if info_file is not None else None + subjects_dir = get_subjects_dir( + subjects_dir=subjects_dir, raise_error=True) + subject = _get_default(subject, self._get_subjects(subjects_dir)[0]) + + # setup the model + self._info = info self._fiducials = fiducials self._renderer = _get_renderer( size=self._defaults["size"], bgcolor=self._defaults["bgcolor"]) @@ -146,9 +149,8 @@ def _get_default(var, val): setattr(self, f"_{fid}_weight", self._defaults["weights"][fid]) # set main traits - self._set_subjects_dir( - get_subjects_dir(subjects_dir=subjects_dir, raise_error=True)) - self._set_subject(_get_default(subject, self._get_subjects()[0])) + self._set_subjects_dir(subjects_dir) + self._set_subject(subject) self._set_info_file(info_file) self._set_orient_glyphs(_get_default(orient_glyphs, self._defaults["orient_glyphs"])) @@ -671,9 +673,9 @@ def _load_trans(self, fname): self._update_plot("sensors") self._update_parameters() - def _get_subjects(self): + def _get_subjects(self, sdir=None): # XXX: would be nice to move this function to util - sdir = self._subjects_dir + sdir = sdir if sdir is not None else self._subjects_dir is_dir = sdir and op.isdir(sdir) if is_dir: dir_content = os.listdir(sdir) From 8e800be44fcddbec4708534c9e08e8803acf46c0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 16:40:57 +0200 Subject: [PATCH 179/225] refactor [ci skip] --- mne/gui/_coreg.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 163a4b0480a..a06bcd10b90 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -137,12 +137,14 @@ def _get_default(var, val): subjects_dir=subjects_dir, raise_error=True) subject = _get_default(subject, self._get_subjects(subjects_dir)[0]) - # setup the model - self._info = info - self._fiducials = fiducials + # setup the window self._renderer = _get_renderer( size=self._defaults["size"], bgcolor=self._defaults["bgcolor"]) self._renderer._window_close_connect(self._clean) + + # setup the model + self._info = info + self._fiducials = fiducials self._coreg = Coregistration( self._info, subject, subjects_dir, fiducials) for fid in self._defaults["weights"].keys(): @@ -960,6 +962,9 @@ def _configure_dock(self): def _clean(self): self._renderer = None + self._coreg = None self._widgets.clear() self._actors.clear() self._surfaces.clear() + self._defaults.clear() + self._head_geo = None From 43e7ff8fa7178432fc0c1d9e585d99f5b897cdfb Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 16:49:20 +0200 Subject: [PATCH 180/225] refactor --- mne/gui/_coreg.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index a06bcd10b90..7c153673b3c 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -454,6 +454,8 @@ def _reset_omit_hsp_filter(self): def _update_plot(self, changes="all"): if self._plot_locked: return + if self._info is None: + changes = ["head"] if not isinstance(changes, list): changes = [changes] forced = "all" in changes @@ -549,8 +551,6 @@ def _update_actor(self, actor_name, actor): self._renderer._update() def _add_mri_fiducials(self): - if self._info is None: - return to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) mri_fids_actors = _plot_mri_fiducials( @@ -562,8 +562,6 @@ def _add_mri_fiducials(self): self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): - if self._info is None: - return to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) head_fids_actors = _plot_head_fiducials( @@ -571,8 +569,6 @@ def _add_head_fiducials(self): self._update_actor("head_fiducials", head_fids_actors) def _add_hpi_coils(self): - if self._info is None: - return if self._hpi_coils: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) @@ -584,8 +580,6 @@ def _add_hpi_coils(self): self._update_actor("hpi_coils", hpi_actors) def _add_head_shape_points(self): - if self._info is None: - return if self._head_shape_points: to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) @@ -598,8 +592,6 @@ def _add_head_shape_points(self): self._update_actor("head_shape_points", hsp_actors) def _add_eeg_channels(self): - if self._info is None: - return if self._eeg_channels: eeg = ["original"] to_cf_t = _get_transforms_to_coord_frame( From 9eb328623751aa613879272c7ddeede04c93e7ef Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 16:56:11 +0200 Subject: [PATCH 181/225] refactor to_cf_t --- mne/gui/_coreg.py | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 7c153673b3c..1d92592a8c5 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -97,6 +97,7 @@ def _get_default(var, val): self._head_geo = None self._coord_frame = "mri" self._mouse_no_mvt = -1 + self._to_cf_t = None self._omit_hsp_distance = 0.0 self._head_opacity = 1.0 self._fid_colors = tuple( @@ -456,6 +457,10 @@ def _update_plot(self, changes="all"): return if self._info is None: changes = ["head"] + self._to_cf_t = dict(mri=np.eye(4), head=np.eye(4)) + else: + self._to_cf_t = _get_transforms_to_coord_frame( + self._info, self._coreg.trans, coord_frame=self._coord_frame) if not isinstance(changes, list): changes = [changes] forced = "all" in changes @@ -551,29 +556,23 @@ def _update_actor(self, actor_name, actor): self._renderer._update() def _add_mri_fiducials(self): - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) mri_fids_actors = _plot_mri_fiducials( self._renderer, self._coreg._fid_points, self._subjects_dir, - self._subject, to_cf_t, self._fid_colors) + self._subject, self._to_cf_t, self._fid_colors) # disable picking on the markers for actor in mri_fids_actors: actor.SetPickable(False) self._update_actor("mri_fiducials", mri_fids_actors) def _add_head_fiducials(self): - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) head_fids_actors = _plot_head_fiducials( - self._renderer, self._info, to_cf_t, self._fid_colors) + self._renderer, self._info, self._to_cf_t, self._fid_colors) self._update_actor("head_fiducials", head_fids_actors) def _add_hpi_coils(self): if self._hpi_coils: - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) hpi_actors = _plot_hpi_coils( - self._renderer, self._info, to_cf_t, opacity=1.0, + self._renderer, self._info, self._to_cf_t, opacity=1.0, orient_glyphs=self._orient_glyphs, surf=self._head_geo) else: hpi_actors = None @@ -581,10 +580,8 @@ def _add_hpi_coils(self): def _add_head_shape_points(self): if self._head_shape_points: - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) hsp_actors = _plot_head_shape_points( - self._renderer, self._info, to_cf_t, opacity=1.0, + self._renderer, self._info, self._to_cf_t, opacity=1.0, orient_glyphs=self._orient_glyphs, surf=self._head_geo, mask=self._coreg._extra_points_filter) else: @@ -594,11 +591,9 @@ def _add_head_shape_points(self): def _add_eeg_channels(self): if self._eeg_channels: eeg = ["original"] - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) picks = pick_types(self._info, eeg=(len(eeg) > 0)) eeg_actors = _plot_sensors( - self._renderer, self._info, to_cf_t, picks, meg=False, + self._renderer, self._info, self._to_cf_t, picks, meg=False, eeg=eeg, fnirs=False, warn_meg=False, head_surf=self._head_geo, units='m', sensor_opacity=1.0, orient_glyphs=self._orient_glyphs, surf=self._head_geo) @@ -610,20 +605,16 @@ def _add_eeg_channels(self): def _add_head_surface(self): bem = None surface = "head-dense" if self._head_resolution else "head" - if self._info is None: - to_cf_t = dict(mri=np.eye(4), head=np.eye(4)) - else: - to_cf_t = _get_transforms_to_coord_frame( - self._info, self._coreg.trans, coord_frame=self._coord_frame) try: head_actor, head_surf = _plot_head_surface( self._renderer, surface, self._subject, - self._subjects_dir, bem, self._coord_frame, to_cf_t, + self._subjects_dir, bem, self._coord_frame, self._to_cf_t, alpha=self._head_opacity) except IOError: head_actor, head_surf = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, - bem, self._coord_frame, to_cf_t, alpha=self._head_opacity) + bem, self._coord_frame, self._to_cf_t, + alpha=self._head_opacity) # mark head surface mesh to restrict picking head_surf._picking_target = True self._update_actor("head", head_actor) From 209ebef0e154467ffaa21219a2cbba3ae293c329 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 16:58:58 +0200 Subject: [PATCH 182/225] add _sensor_opacity [ci skip] --- mne/gui/_coreg.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 1d92592a8c5..60f3899c715 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -100,6 +100,7 @@ def _get_default(var, val): self._to_cf_t = None self._omit_hsp_distance = 0.0 self._head_opacity = 1.0 + self._sensor_opacity = 1.0 self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) @@ -572,7 +573,8 @@ def _add_head_fiducials(self): def _add_hpi_coils(self): if self._hpi_coils: hpi_actors = _plot_hpi_coils( - self._renderer, self._info, self._to_cf_t, opacity=1.0, + self._renderer, self._info, self._to_cf_t, + opacity=self._sensor_opacity, orient_glyphs=self._orient_glyphs, surf=self._head_geo) else: hpi_actors = None @@ -581,7 +583,8 @@ def _add_hpi_coils(self): def _add_head_shape_points(self): if self._head_shape_points: hsp_actors = _plot_head_shape_points( - self._renderer, self._info, self._to_cf_t, opacity=1.0, + self._renderer, self._info, self._to_cf_t, + opacity=self._sensor_opacity, orient_glyphs=self._orient_glyphs, surf=self._head_geo, mask=self._coreg._extra_points_filter) else: @@ -595,7 +598,7 @@ def _add_eeg_channels(self): eeg_actors = _plot_sensors( self._renderer, self._info, self._to_cf_t, picks, meg=False, eeg=eeg, fnirs=False, warn_meg=False, head_surf=self._head_geo, - units='m', sensor_opacity=1.0, + units='m', sensor_opacity=self._sensor_opacity, orient_glyphs=self._orient_glyphs, surf=self._head_geo) eeg_actors = eeg_actors["eeg"] else: From 23efc86e9534709803b744b901af8fed3cb61f94 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 17:04:21 +0200 Subject: [PATCH 183/225] remove cruft --- mne/gui/_coreg.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 60f3899c715..5d9bafa8b11 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -624,13 +624,12 @@ def _add_head_surface(self): self._surfaces["head"] = head_surf def _fit_fiducials(self): - with self._display_message("Fitting..."): - self._coreg.fit_fiducials( - lpa_weight=self._lpa_weight, - nasion_weight=self._nasion_weight, - rpa_weight=self._rpa_weight, - verbose=self._verbose, - ) + self._coreg.fit_fiducials( + lpa_weight=self._lpa_weight, + nasion_weight=self._nasion_weight, + rpa_weight=self._rpa_weight, + verbose=self._verbose, + ) self._update_plot("sensors") self._update_parameters() From 507307e36de34fbc96545f5748fc53fac10fde94 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 17:19:53 +0200 Subject: [PATCH 184/225] refactor defaults [ci skip] --- mne/gui/_coreg.py | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 5d9bafa8b11..d80f8442938 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -107,12 +107,12 @@ def _get_default(var, val): self._defaults = dict( size=_get_default(size, (800, 600)), bgcolor=_get_default(bgcolor, "grey"), - orient_glyphs=False, - hpi_coils=True, - head_shape_points=True, - eeg_channels=True, - head_resolution=False, - head_transparency=False, + orient_glyphs=_get_default(orient_glyphs, False), + hpi_coils=_get_default(hpi_coils, True), + head_shape_points=_get_default(head_shape_points, True), + eeg_channels=_get_default(eeg_channels, True), + head_resolution=_get_default(head_resolution, False), + head_transparency=_get_default(head_transparency, False), head_opacity=_get_default(head_opacity, 0.4), fiducials=("LPA", "Nasion", "RPA"), fiducial="LPA", @@ -156,18 +156,12 @@ def _get_default(var, val): self._set_subjects_dir(subjects_dir) self._set_subject(subject) self._set_info_file(info_file) - self._set_orient_glyphs(_get_default(orient_glyphs, - self._defaults["orient_glyphs"])) - self._set_hpi_coils(_get_default(hpi_coils, - self._defaults["hpi_coils"])) - self._set_head_shape_points(_get_default(head_shape_points, - self._defaults["head_shape_points"])) - self._set_eeg_channels(_get_default(eeg_channels, - self._defaults["eeg_channels"])) - self._set_head_resolution(_get_default(head_resolution, - self._defaults["head_resolution"])) - self._set_head_transparency(_get_default(head_transparency, - self._defaults["head_transparency"])) + self._set_orient_glyphs(self._defaults["orient_glyphs"]) + self._set_hpi_coils(self._defaults["hpi_coils"]) + self._set_head_shape_points(self._defaults["head_shape_points"]) + self._set_eeg_channels(self._defaults["eeg_channels"]) + self._set_head_resolution(self._defaults["head_resolution"]) + self._set_head_transparency(self._defaults["head_transparency"]) self._set_grow_hair(self._defaults["grow_hair"]) self._set_omit_hsp_distance(self._defaults["omit_hsp_distance"]) self._set_icp_n_iterations(self._defaults["icp_n_iterations"]) From e95fcb69afd1d4cc7db7d387785831febd0eef6c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 17:28:04 +0200 Subject: [PATCH 185/225] expose sensor_opacity [ci skip] --- mne/gui/_coreg.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index d80f8442938..b7889fd4b5e 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -51,6 +51,8 @@ class CoregistrationUI(HasTraits): If True, display the EEG channels. Defaults to True. orient_glyphs : bool If True, orient the sensors towards the head surface. Default to False. + sensor_opacity : float + The opacity of the sensors between 0 and 1. Defaults to 1.0. trans : str The path to the Head<->MRI transform FIF file ("-trans.fif"). size : tuple @@ -84,7 +86,8 @@ def __init__(self, info_file, subject=None, subjects_dir=None, fiducials='auto', head_resolution=None, head_transparency=None, head_opacity=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, - trans=None, size=None, bgcolor=None, standalone=False): + sensor_opacity=None, trans=None, size=None, bgcolor=None, + standalone=False): from ..viz.backends.renderer import _get_renderer def _get_default(var, val): @@ -100,7 +103,6 @@ def _get_default(var, val): self._to_cf_t = None self._omit_hsp_distance = 0.0 self._head_opacity = 1.0 - self._sensor_opacity = 1.0 self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) @@ -114,6 +116,7 @@ def _get_default(var, val): head_resolution=_get_default(head_resolution, False), head_transparency=_get_default(head_transparency, False), head_opacity=_get_default(head_opacity, 0.4), + sensor_opacity=_get_default(sensor_opacity, 1.0), fiducials=("LPA", "Nasion", "RPA"), fiducial="LPA", lock_fids=True, @@ -134,6 +137,7 @@ def _get_default(var, val): ), ) + # process requirements info = read_info(info_file) if info_file is not None else None subjects_dir = get_subjects_dir( subjects_dir=subjects_dir, raise_error=True) @@ -568,7 +572,7 @@ def _add_hpi_coils(self): if self._hpi_coils: hpi_actors = _plot_hpi_coils( self._renderer, self._info, self._to_cf_t, - opacity=self._sensor_opacity, + opacity=self._defaults["sensor_opacity"], orient_glyphs=self._orient_glyphs, surf=self._head_geo) else: hpi_actors = None @@ -578,7 +582,7 @@ def _add_head_shape_points(self): if self._head_shape_points: hsp_actors = _plot_head_shape_points( self._renderer, self._info, self._to_cf_t, - opacity=self._sensor_opacity, + opacity=self._defaults["sensor_opacity"], orient_glyphs=self._orient_glyphs, surf=self._head_geo, mask=self._coreg._extra_points_filter) else: @@ -592,7 +596,7 @@ def _add_eeg_channels(self): eeg_actors = _plot_sensors( self._renderer, self._info, self._to_cf_t, picks, meg=False, eeg=eeg, fnirs=False, warn_meg=False, head_surf=self._head_geo, - units='m', sensor_opacity=self._sensor_opacity, + units='m', sensor_opacity=self._defaults["sensor_opacity"], orient_glyphs=self._orient_glyphs, surf=self._head_geo) eeg_actors = eeg_actors["eeg"] else: From c088f9fa5efb5a29ee2e040f448ce52a4167543f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 17:46:50 +0200 Subject: [PATCH 186/225] backward compatibility with mayavi opacity at init [ci skip] --- mne/gui/_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index b7889fd4b5e..68f6d6eb952 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -102,7 +102,7 @@ def _get_default(var, val): self._mouse_no_mvt = -1 self._to_cf_t = None self._omit_hsp_distance = 0.0 - self._head_opacity = 1.0 + self._head_opacity = _get_default(head_opacity, 1.0) self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) @@ -115,7 +115,7 @@ def _get_default(var, val): eeg_channels=_get_default(eeg_channels, True), head_resolution=_get_default(head_resolution, False), head_transparency=_get_default(head_transparency, False), - head_opacity=_get_default(head_opacity, 0.4), + head_opacity=0.5, sensor_opacity=_get_default(sensor_opacity, 1.0), fiducials=("LPA", "Nasion", "RPA"), fiducial="LPA", From 886e0a444d5add2f736d9706dcf6d0f5f3c8a15f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 17:47:34 +0200 Subject: [PATCH 187/225] fix [ci skip] --- mne/gui/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 68f6d6eb952..f9658b345af 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -42,7 +42,7 @@ class CoregistrationUI(HasTraits): head_transparency : bool If True, display the head surface with transparency. Defaults to False. head_opacity : float - The opacity of the head surface between 0 and 1. Defaults to 0.4. + The opacity of the head surface between 0 and 1. Defaults to 0.5. hpi_coils : bool If True, display the HPI coils. Defaults to True. head_shape_points : bool From bb363949b17846512182c95b117503cdde455f0e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 18 Oct 2021 18:09:41 +0200 Subject: [PATCH 188/225] finalize _lock_head_opacity [ci skip] --- mne/gui/_coreg.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index f9658b345af..0fda6f57a72 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -81,6 +81,7 @@ class CoregistrationUI(HasTraits): _grow_hair = Float() _scale_mode = Unicode() _icp_fid_match = Unicode() + _lock_head_opacity = Bool() def __init__(self, info_file, subject=None, subjects_dir=None, fiducials='auto', head_resolution=None, @@ -127,6 +128,7 @@ def _get_default(var, val): icp_fid_match='nearest', icp_n_iterations=20, omit_hsp_distance=10.0, + lock_head_opacity=self._head_opacity < 1.0, weights=dict( lpa=1.0, nasion=10.0, @@ -180,6 +182,7 @@ def _get_default(var, val): self._set_current_fiducial(self._defaults["fiducial"]) self._set_lock_fids(self._defaults["lock_fids"]) self._set_scale_mode(self._defaults["scale_mode"]) + self._set_lock_head_opacity(self._defaults["lock_head_opacity"]) if trans is not None: self._load_trans(trans) @@ -275,6 +278,9 @@ def _set_icp_fid_match(self, method): def _set_point_weight(self, weight, point): setattr(self, f"_{point}_weight", weight) + def _set_lock_head_opacity(self, state): + self._lock_head_opacity = bool(state) + @observe("_subjects_dir") def _subjects_dir_changed(self, change=None): # XXX: add coreg.set_subjects_dir @@ -387,6 +393,11 @@ def _configure_picking(self): ) self._actors["msg"] = self._renderer.text2d(0, 0, "") + @observe("_lock_head_opacity") + def _lock_head_opacity_changed(self, changes=None): + self._forward_widget_command( + "make_transparent", "set_enabled", not self._lock_head_opacity) + def _on_mouse_move(self, vtk_picker, event): if self._mouse_no_mvt: self._mouse_no_mvt -= 1 From 5083b15af71b938ee311ed53fb154ede0ffa1f24 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 11:50:24 +0200 Subject: [PATCH 189/225] use getOpenFileName [ci skip] --- mne/viz/backends/_qt.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index b5f401d5d79..02b334e75c0 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -214,13 +214,11 @@ def callback(): sync_text_widget(dname) func(dname) else: - dialog = FileDialog( - self.plotter.app_window, - callback=func, - ) + fname = QFileDialog.getOpenFileName() + fname = fname[0] if isinstance(fname, tuple) else fname if input_text_widget: - dialog.dlg_accepted.connect(sync_text_widget) - return dialog + sync_text_widget(fname) + func(fname) button_widget = self._dock_add_button( name=desc, From 9bf49ac81c948d9f4765c0f78aaf7f1251136022 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 12:00:35 +0200 Subject: [PATCH 190/225] refactor --- mne/viz/backends/_qt.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 02b334e75c0..a5bfd76bffa 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -209,16 +209,13 @@ def sync_text_widget(s): def callback(): if directory: - dname = QFileDialog.getExistingDirectory() - if input_text_widget: - sync_text_widget(dname) - func(dname) + name = QFileDialog.getExistingDirectory() else: - fname = QFileDialog.getOpenFileName() - fname = fname[0] if isinstance(fname, tuple) else fname - if input_text_widget: - sync_text_widget(fname) - func(fname) + name = QFileDialog.getOpenFileName() + name = name[0] if isinstance(name, tuple) else name + if input_text_widget: + sync_text_widget(name) + func(name) button_widget = self._dock_add_button( name=desc, From 0e2a06d2d484e324b820947afee5d5018d2311a3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 14:04:14 +0200 Subject: [PATCH 191/225] unlock fids at init --- mne/gui/_coreg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 0fda6f57a72..2e16b4ac907 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -120,7 +120,7 @@ def _get_default(var, val): sensor_opacity=_get_default(sensor_opacity, 1.0), fiducials=("LPA", "Nasion", "RPA"), fiducial="LPA", - lock_fids=True, + lock_fids=False, grow_hair=0.0, scale_modes=["None", "uniform", "3-axis"], scale_mode="None", From 83230e7a0d17273c8772aa9f90c507541c97c5c6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 14:32:32 +0200 Subject: [PATCH 192/225] fix head surf --- mne/gui/_coreg.py | 4 ++-- mne/viz/_3d.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 2e16b4ac907..4eeefc37ba4 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -618,12 +618,12 @@ def _add_head_surface(self): bem = None surface = "head-dense" if self._head_resolution else "head" try: - head_actor, head_surf = _plot_head_surface( + head_actor, head_surf, _ = _plot_head_surface( self._renderer, surface, self._subject, self._subjects_dir, bem, self._coord_frame, self._to_cf_t, alpha=self._head_opacity) except IOError: - head_actor, head_surf = _plot_head_surface( + head_actor, head_surf, _ = _plot_head_surface( self._renderer, "head", self._subject, self._subjects_dir, bem, self._coord_frame, self._to_cf_t, alpha=self._head_opacity) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index eaedcf4b376..1dcbdffbf88 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -702,7 +702,7 @@ def plot_alignment(info=None, trans=None, subject=None, subjects_dir=None, renderer.set_interaction('terrain') # plot head - _, head_surf = _plot_head_surface( + _, _, head_surf = _plot_head_surface( renderer, head, subject, subjects_dir, bem, coord_frame, to_cf_t, alpha=head_alpha) @@ -892,16 +892,16 @@ def _plot_head_surface(renderer, head, subject, subjects_dir, bem, """Render a head surface in a 3D scene.""" color = DEFAULTS['coreg']['head_color'] if color is None else color actor = None - surf = None + src_surf = dst_surf = None if head is not False: - surf = _get_head_surface(head, subject, subjects_dir, bem=bem) - surf = transform_surface_to( - surf, coord_frame, [to_cf_t['mri'], to_cf_t['head']], + src_surf = _get_head_surface(head, subject, subjects_dir, bem=bem) + src_surf = transform_surface_to( + src_surf, coord_frame, [to_cf_t['mri'], to_cf_t['head']], copy=True) - actor, surf = renderer.surface( - surface=surf, color=color, opacity=alpha, + actor, dst_surf = renderer.surface( + surface=src_surf, color=color, opacity=alpha, backface_culling=False) - return actor, surf + return actor, dst_surf, src_surf def _plot_axes(renderer, info, to_cf_t, head_mri_t): From c6d18bb1b396eadad86ebf5432958e2f803b9bf7 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 14:35:30 +0200 Subject: [PATCH 193/225] restore temporarily --- mne/viz/_3d.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 1dcbdffbf88..846a6c7b71e 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1161,12 +1161,14 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actor, _ = _plot_oriented_glyphs( renderer, sens_loc, surf, color, defaults['eeg_scale'], opacity=sensor_opacity) + actors['eeg'].append(actor) else: - actor, _ = renderer.sphere( - center=sens_loc * scalar, color=color, - scale=defaults['eeg_scale'] * scalar, - opacity=sensor_opacity) - actors['eeg'].append(actor) + for loc in sens_loc: + actor, _ = renderer.sphere( + center=loc * scalar, color=color, + scale=defaults['eeg_scale'] * scalar, + opacity=sensor_opacity) + actors['eeg'].append(actor) # add projected eeg eeg_indices = pick_types(info, eeg=True) From b60d51e8b848921550f30cece424654a69968914 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 14:56:28 +0200 Subject: [PATCH 194/225] fix sensors --- mne/viz/_3d.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 846a6c7b71e..55169bb81d8 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1109,8 +1109,9 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), ecog=list(), seeg=list(), dbs=list()) + locs = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), + ecog=list(), seeg=list(), dbs=list()) scalar = 1 if units == 'm' else 1e3 - sens_loc = list() for ch_name, ch_coord in ch_pos.items(): ch_type = channel_type(info, info.ch_names.index(ch_name)) # for default picking @@ -1131,7 +1132,7 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors[ch_type].append(actor) else: if plot_sensors: - sens_loc.append(ch_coord) + locs[ch_type].append(ch_coord) if ch_name in sources and 'sources' in fnirs: actor, _ = renderer.sphere( center=tuple(sources[ch_name] * scalar), @@ -1154,21 +1155,23 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, radius=0.001 * scalar) actors[ch_type].append(actor) - # add eeg - if len(sens_loc) > 0: - sens_loc = np.array(sens_loc) - if orient_glyphs: - actor, _ = _plot_oriented_glyphs( - renderer, sens_loc, surf, color, defaults['eeg_scale'], - opacity=sensor_opacity) - actors['eeg'].append(actor) - else: - for loc in sens_loc: - actor, _ = renderer.sphere( - center=loc * scalar, color=color, - scale=defaults['eeg_scale'] * scalar, + # add sensors + for ch_type in locs.keys(): + if len(locs[ch_type]) > 0: + sens_loc = np.array(locs[ch_type]) + if orient_glyphs: + actor, _ = _plot_oriented_glyphs( + renderer, sens_loc, surf, color, + defaults[f"{ch_type}_scale"], opacity=sensor_opacity) - actors['eeg'].append(actor) + actors[ch_type].append(actor) + else: + for loc in sens_loc: + actor, _ = renderer.sphere( + center=loc * scalar, color=color, + scale=defaults[f"{ch_type}_scale"] * scalar, + opacity=sensor_opacity) + actors[ch_type].append(actor) # add projected eeg eeg_indices = pick_types(info, eeg=True) From 86be997514b750baaa4650b352e6902dc438debe Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 15:55:54 +0200 Subject: [PATCH 195/225] disable eeg in test_plot_alignment_meg temporarily --- mne/viz/_3d.py | 42 +++++++++++++++------------------------- mne/viz/tests/test_3d.py | 6 +++--- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 55169bb81d8..d9f799865e6 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1109,8 +1109,7 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), ecog=list(), seeg=list(), dbs=list()) - locs = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), - ecog=list(), seeg=list(), dbs=list()) + locs = dict(eeg=list(), fnirs=list(), source=list(), detector=list()) scalar = 1 if units == 'm' else 1e3 for ch_name, ch_coord in ch_pos.items(): ch_type = channel_type(info, info.ch_names.index(ch_name)) @@ -1134,19 +1133,9 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, if plot_sensors: locs[ch_type].append(ch_coord) if ch_name in sources and 'sources' in fnirs: - actor, _ = renderer.sphere( - center=tuple(sources[ch_name] * scalar), - color=defaults['source_color'], - scale=defaults['source_scale'] * scalar, - opacity=sensor_opacity) - actors[ch_type].append(actor) + locs['source'].append(sources[ch_name]) if ch_name in detectors and 'detectors' in fnirs: - actor, _ = renderer.sphere( - center=tuple(detectors[ch_name] * scalar), - color=defaults['detector_color'], - scale=defaults['detector_scale'] * scalar, - opacity=sensor_opacity) - actors[ch_type].append(actor) + locs['detector'].append(detectors[ch_name]) if ch_name in sources and ch_name in detectors and \ 'pairs' in fnirs: actor, _ = renderer.tube( # array of origin and dest points @@ -1156,22 +1145,23 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors[ch_type].append(actor) # add sensors - for ch_type in locs.keys(): - if len(locs[ch_type]) > 0: - sens_loc = np.array(locs[ch_type]) + for sensor_type in locs.keys(): + if len(locs[sensor_type]) > 0: + sens_loc = np.array(locs[sensor_type]) + color = defaults[sensor_type + '_color'] + scale = defaults[sensor_type + '_scale'] if orient_glyphs: actor, _ = _plot_oriented_glyphs( - renderer, sens_loc, surf, color, - defaults[f"{ch_type}_scale"], + renderer, sens_loc, surf, color, scale, opacity=sensor_opacity) - actors[ch_type].append(actor) else: - for loc in sens_loc: - actor, _ = renderer.sphere( - center=loc * scalar, color=color, - scale=defaults[f"{ch_type}_scale"] * scalar, - opacity=sensor_opacity) - actors[ch_type].append(actor) + actor, _ = renderer.sphere( + center=sens_loc * scalar, color=color, + scale=scale * scalar, + opacity=sensor_opacity) + if sensor_type in ('source', 'detector'): + sensor_type = 'fnirs' + actors[sensor_type].append(actor) # add projected eeg eeg_indices = pick_types(info, eeg=True) diff --git a/mne/viz/tests/test_3d.py b/mne/viz/tests/test_3d.py index e3802e4e640..58da3ad248c 100644 --- a/mne/viz/tests/test_3d.py +++ b/mne/viz/tests/test_3d.py @@ -199,10 +199,10 @@ def test_plot_alignment_meg(renderer, system): meg.append('ref') fig = plot_alignment( this_info, read_trans(trans_fname), subject='sample', - subjects_dir=subjects_dir, meg=meg, eeg=True) + subjects_dir=subjects_dir, meg=meg, eeg=False) # count the number of objects: should be n_meg_ch + 1 (helmet) + 1 (head) use_info = pick_info(this_info, pick_types( - this_info, meg=True, eeg=True, ref_meg='ref' in meg, exclude=())) + this_info, meg=True, eeg=False, ref_meg='ref' in meg, exclude=())) n_actors = use_info['nchan'] + 2 _assert_n_actors(fig, renderer, n_actors) @@ -449,7 +449,7 @@ def test_plot_alignment_fnirs(renderer, tmpdir): fig = plot_alignment( info, fnirs=['channels', 'sources', 'detectors'], **kwargs) - _assert_n_actors(fig, renderer, 3 * info['nchan']) + _assert_n_actors(fig, renderer, 3) @pytest.mark.slowtest # can be slow on OSX From b5863e5666656933d8f7e7709b9e52296134e686 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 16:01:40 +0200 Subject: [PATCH 196/225] do not support --head-opacity --- mne/gui/__init__.py | 5 ++--- mne/gui/_coreg.py | 16 ++-------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index c7577a96604..a1f29525dff 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -183,9 +183,8 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, from ._coreg import CoregistrationUI return CoregistrationUI( info_file=inst, subject=subject, subjects_dir=subjects_dir, - head_resolution=head_high_res, head_opacity=head_opacity, - orient_glyphs=orient_to_surface, trans=trans, - size=(width, height), standalone=True, + head_resolution=head_high_res, orient_glyphs=orient_to_surface, + trans=trans, size=(width, height), standalone=True, ) else: _check_mayavi_version() diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 4eeefc37ba4..25069e463c5 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -41,8 +41,6 @@ class CoregistrationUI(HasTraits): If True, use a high-resolution head surface. Defaults to False. head_transparency : bool If True, display the head surface with transparency. Defaults to False. - head_opacity : float - The opacity of the head surface between 0 and 1. Defaults to 0.5. hpi_coils : bool If True, display the HPI coils. Defaults to True. head_shape_points : bool @@ -81,11 +79,10 @@ class CoregistrationUI(HasTraits): _grow_hair = Float() _scale_mode = Unicode() _icp_fid_match = Unicode() - _lock_head_opacity = Bool() def __init__(self, info_file, subject=None, subjects_dir=None, fiducials='auto', head_resolution=None, - head_transparency=None, head_opacity=None, hpi_coils=None, + head_transparency=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, sensor_opacity=None, trans=None, size=None, bgcolor=None, standalone=False): @@ -103,7 +100,7 @@ def _get_default(var, val): self._mouse_no_mvt = -1 self._to_cf_t = None self._omit_hsp_distance = 0.0 - self._head_opacity = _get_default(head_opacity, 1.0) + self._head_opacity = 1.0 self._fid_colors = tuple( DEFAULTS['coreg'][f'{key}_color'] for key in ('lpa', 'nasion', 'rpa')) @@ -182,7 +179,6 @@ def _get_default(var, val): self._set_current_fiducial(self._defaults["fiducial"]) self._set_lock_fids(self._defaults["lock_fids"]) self._set_scale_mode(self._defaults["scale_mode"]) - self._set_lock_head_opacity(self._defaults["lock_head_opacity"]) if trans is not None: self._load_trans(trans) @@ -278,9 +274,6 @@ def _set_icp_fid_match(self, method): def _set_point_weight(self, weight, point): setattr(self, f"_{point}_weight", weight) - def _set_lock_head_opacity(self, state): - self._lock_head_opacity = bool(state) - @observe("_subjects_dir") def _subjects_dir_changed(self, change=None): # XXX: add coreg.set_subjects_dir @@ -393,11 +386,6 @@ def _configure_picking(self): ) self._actors["msg"] = self._renderer.text2d(0, 0, "") - @observe("_lock_head_opacity") - def _lock_head_opacity_changed(self, changes=None): - self._forward_widget_command( - "make_transparent", "set_enabled", not self._lock_head_opacity) - def _on_mouse_move(self, vtk_picker, event): if self._mouse_no_mvt: self._mouse_no_mvt -= 1 From d398be3c17b3e9a36e3f39bf71078d3aaad4fa67 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 16:10:21 +0200 Subject: [PATCH 197/225] allow fids picking when info is none --- mne/gui/_coreg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 25069e463c5..52dbed97e75 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -454,8 +454,8 @@ def _update_plot(self, changes="all"): if self._plot_locked: return if self._info is None: - changes = ["head"] - self._to_cf_t = dict(mri=np.eye(4), head=np.eye(4)) + changes = ["head", "mri_fids"] + self._to_cf_t = dict(mri=dict(trans=np.eye(4)), head=None) else: self._to_cf_t = _get_transforms_to_coord_frame( self._info, self._coreg.trans, coord_frame=self._coord_frame) From a9899582f317c64d8ad5f902d08148f299630ce4 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 16:19:44 +0200 Subject: [PATCH 198/225] update sensor list --- mne/viz/_3d.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index d9f799865e6..c6c22080f4f 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1109,7 +1109,8 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), ecog=list(), seeg=list(), dbs=list()) - locs = dict(eeg=list(), fnirs=list(), source=list(), detector=list()) + locs = dict(eeg=list(), fnirs=list(), ecog=list(), + source=list(), detector=list()) scalar = 1 if units == 'm' else 1e3 for ch_name, ch_coord in ch_pos.items(): ch_type = channel_type(info, info.ch_names.index(ch_name)) From fe22e8263d9bc894e4a8397875221f613efaf0c1 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 16:21:17 +0200 Subject: [PATCH 199/225] update sensor list --- mne/viz/_3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index c6c22080f4f..6ab7b64e407 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -1109,7 +1109,7 @@ def _plot_sensors(renderer, info, to_cf_t, picks, meg, eeg, fnirs, actors = dict(meg=list(), ref_meg=list(), eeg=list(), fnirs=list(), ecog=list(), seeg=list(), dbs=list()) - locs = dict(eeg=list(), fnirs=list(), ecog=list(), + locs = dict(eeg=list(), fnirs=list(), ecog=list(), seeg=list(), source=list(), detector=list()) scalar = 1 if units == 'm' else 1e3 for ch_name, ch_coord in ch_pos.items(): From 148013a7442399819759bfa78656a1fbc079f6a5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 16:37:50 +0200 Subject: [PATCH 200/225] use getSaveFileName --- mne/gui/_coreg.py | 1 + mne/viz/backends/_abstract.py | 2 +- mne/viz/backends/_notebook.py | 2 +- mne/viz/backends/_qt.py | 4 +++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 52dbed97e75..109a8271631 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -928,6 +928,7 @@ def _configure_dock(self): self._widgets["save_trans"] = self._renderer._dock_add_file_button( name="save_trans", desc="Save...", + save=True, func=self._save_trans, input_text_widget=False, layout=hlayout, diff --git a/mne/viz/backends/_abstract.py b/mne/viz/backends/_abstract.py index 6aba2ec13d8..cd05960e9ca 100644 --- a/mne/viz/backends/_abstract.py +++ b/mne/viz/backends/_abstract.py @@ -567,7 +567,7 @@ def _dock_add_text(self, name, value, placeholder, layout=None): pass @abstractmethod - def _dock_add_file_button(self, name, desc, func, value=None, + def _dock_add_file_button(self, name, desc, func, value=None, save=False, directory=False, input_text_widget=True, placeholder="Type a file name", layout=None): pass diff --git a/mne/viz/backends/_notebook.py b/mne/viz/backends/_notebook.py index ad8f44b914a..06d7c12ffe7 100644 --- a/mne/viz/backends/_notebook.py +++ b/mne/viz/backends/_notebook.py @@ -167,7 +167,7 @@ def _dock_add_text(self, name, value, placeholder, layout=None): self._layout_add_widget(layout, widget) return _IpyWidget(widget) - def _dock_add_file_button(self, name, desc, func, value=None, + def _dock_add_file_button(self, name, desc, func, value=None, save=False, directory=False, input_text_widget=True, placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index a5bfd76bffa..672cd784755 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -189,7 +189,7 @@ def _dock_add_text(self, name, value, placeholder, layout=None): self._layout_add_widget(layout, widget) return _QtWidget(widget) - def _dock_add_file_button(self, name, desc, func, value=None, + def _dock_add_file_button(self, name, desc, func, value=None, save=False, directory=False, input_text_widget=True, placeholder="Type a file name", layout=None): layout = self._dock_layout if layout is None else layout @@ -210,6 +210,8 @@ def sync_text_widget(s): def callback(): if directory: name = QFileDialog.getExistingDirectory() + elif save: + name = QFileDialog.getSaveFileName() else: name = QFileDialog.getOpenFileName() name = name[0] if isinstance(name, tuple) else name From 58ecb13ebb56dd556a0419bd1a364552960ff5e6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 19 Oct 2021 16:44:48 +0200 Subject: [PATCH 201/225] fix --- mne/viz/backends/_qt.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mne/viz/backends/_qt.py b/mne/viz/backends/_qt.py index 672cd784755..8630f69f3c7 100644 --- a/mne/viz/backends/_qt.py +++ b/mne/viz/backends/_qt.py @@ -215,6 +215,9 @@ def callback(): else: name = QFileDialog.getOpenFileName() name = name[0] if isinstance(name, tuple) else name + # handle the cancel button + if len(name) == 0: + return if input_text_widget: sync_text_widget(name) func(name) From 759c0b7abd6738ecf050e4d81176aa264cedfdae Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:10:58 +0200 Subject: [PATCH 202/225] improve coverage --- mne/gui/_coreg.py | 7 ++- mne/gui/tests/test_coreg_ui.py | 87 ++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 mne/gui/tests/test_coreg_ui.py diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 109a8271631..5484da52ada 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -60,6 +60,8 @@ class CoregistrationUI(HasTraits): The background color as a tuple (red, green, blue) of float values between 0 and 1 or a valid color name (i.e. 'white' or 'w'). Defaults to 'grey'. + show : bool + Display the window as soon as it is ready. Defaults to True. standalone : bool If True, start the Qt application event loop. Default to False. """ @@ -85,7 +87,7 @@ def __init__(self, info_file, subject=None, subjects_dir=None, head_transparency=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, sensor_opacity=None, trans=None, size=None, bgcolor=None, - standalone=False): + show=True, standalone=False): from ..viz.backends.renderer import _get_renderer def _get_default(var, val): @@ -183,7 +185,8 @@ def _get_default(var, val): self._load_trans(trans) # must be done last - self._renderer.show() + if show: + self._renderer.show() if standalone: self._renderer.figure.store["app"].exec() diff --git a/mne/gui/tests/test_coreg_ui.py b/mne/gui/tests/test_coreg_ui.py new file mode 100644 index 00000000000..fc71e7d184f --- /dev/null +++ b/mne/gui/tests/test_coreg_ui.py @@ -0,0 +1,87 @@ +import pytest +import os +import os.path as op +from mne.datasets import testing +from mne.gui._coreg import CoregistrationUI + +data_path = testing.data_path(download=False) +subjects_dir = os.path.join(data_path, 'subjects') +fid_fname = op.join(subjects_dir, 'sample', 'bem', 'sample-fiducials.fif') +raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif') +trans_fname = op.join(data_path, 'MEG', 'sample', + 'sample_audvis_trunc-trans.fif') + + +class TstVTKPicker(object): + """Class to test cell picking.""" + + def __init__(self, mesh, cell_id, event_pos): + self.mesh = mesh + self.cell_id = cell_id + self.point_id = None + self.event_pos = event_pos + + def GetCellId(self): + """Return the picked cell.""" + return self.cell_id + + def GetDataSet(self): + """Return the picked mesh.""" + return self.mesh + + def GetPickPosition(self): + """Return the picked position.""" + vtk_cell = self.mesh.GetCell(self.cell_id) + cell = [vtk_cell.GetPointId(point_id) for point_id + in range(vtk_cell.GetNumberOfPoints())] + self.point_id = cell[0] + return self.mesh.points[self.point_id] + + def GetEventPosition(self): + """Return event position.""" + return self.event_pos + + +@pytest.mark.slowtest +@testing.requires_testing_data +def test_coregistration_gui(tmpdir): + """Test that using Coregistration matches mne coreg.""" + tempdir = str(tmpdir) + tmp_trans = op.join(tempdir, 'tmp-trans.fif') + CoregistrationUI(info_file=None, subject='sample', + subjects_dir=subjects_dir, trans=trans_fname, show=False) + coreg = CoregistrationUI(info_file=raw_fname, subject='sample', + subjects_dir=subjects_dir, show=False) + coreg._set_fiducials_file(fid_fname) + assert coreg._fiducials_file == fid_fname + # picking + vtk_picker = TstVTKPicker(coreg._surfaces['head'], 0, (0, 0)) + coreg._on_mouse_move(vtk_picker, None) + coreg._on_button_press(vtk_picker, None) + coreg._on_pick(vtk_picker, None) + coreg._on_button_release(vtk_picker, None) + coreg._set_lock_fids(True) + assert coreg._lock_fids + coreg._on_pick(vtk_picker, None) # also pick when locked + coreg._set_lock_fids(False) + assert not coreg._lock_fids + coreg._set_lock_fids(True) + assert coreg._lock_fids + assert coreg._nasion_weight == 10. + coreg._set_point_weight(11., 'nasion') + assert coreg._nasion_weight == 11. + coreg._fit_fiducials() + coreg._fit_icp() + coreg._omit_hsp() + assert coreg._coreg._extra_points_filter is not None + coreg._reset_omit_hsp_filter() + assert coreg._coreg._extra_points_filter is None + coreg._set_orient_glyphs(True) + assert coreg._orient_glyphs + coreg._set_head_resolution(True) + assert coreg._head_resolution + coreg._set_head_transparency(True) + assert coreg._head_transparency + coreg._save_trans(tmp_trans) + assert op.isfile(tmp_trans) + coreg._clean() From 874b34d0068e677c726e151976bdc75be7b83782 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:18:11 +0200 Subject: [PATCH 203/225] add a close function --- mne/gui/_coreg.py | 4 ++++ mne/gui/tests/test_coreg_ui.py | 12 +++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 5484da52ada..691d0984f07 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -954,3 +954,7 @@ def _clean(self): self._surfaces.clear() self._defaults.clear() self._head_geo = None + + def close(self): + """Close interface and cleanup data structure.""" + self._renderer.close() diff --git a/mne/gui/tests/test_coreg_ui.py b/mne/gui/tests/test_coreg_ui.py index fc71e7d184f..86e1d0e8c1b 100644 --- a/mne/gui/tests/test_coreg_ui.py +++ b/mne/gui/tests/test_coreg_ui.py @@ -44,12 +44,14 @@ def GetEventPosition(self): @pytest.mark.slowtest @testing.requires_testing_data -def test_coregistration_gui(tmpdir): - """Test that using Coregistration matches mne coreg.""" +def test_coregistration_ui(tmpdir): + """Test that using CoregistrationUI matches mne coreg.""" tempdir = str(tmpdir) tmp_trans = op.join(tempdir, 'tmp-trans.fif') - CoregistrationUI(info_file=None, subject='sample', - subjects_dir=subjects_dir, trans=trans_fname, show=False) + coreg = CoregistrationUI(info_file=None, subject='sample', + subjects_dir=subjects_dir, trans=trans_fname, + show=False) + coreg.close() coreg = CoregistrationUI(info_file=raw_fname, subject='sample', subjects_dir=subjects_dir, show=False) coreg._set_fiducials_file(fid_fname) @@ -84,4 +86,4 @@ def test_coregistration_gui(tmpdir): assert coreg._head_transparency coreg._save_trans(tmp_trans) assert op.isfile(tmp_trans) - coreg._clean() + coreg.close() From b3f20d93df1d342f6f796da053a00cf7899d4473 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:20:28 +0200 Subject: [PATCH 204/225] fix import --- mne/gui/tests/test_coreg_ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/gui/tests/test_coreg_ui.py b/mne/gui/tests/test_coreg_ui.py index 86e1d0e8c1b..9c9f626a709 100644 --- a/mne/gui/tests/test_coreg_ui.py +++ b/mne/gui/tests/test_coreg_ui.py @@ -2,7 +2,6 @@ import os import os.path as op from mne.datasets import testing -from mne.gui._coreg import CoregistrationUI data_path = testing.data_path(download=False) subjects_dir = os.path.join(data_path, 'subjects') @@ -46,6 +45,7 @@ def GetEventPosition(self): @testing.requires_testing_data def test_coregistration_ui(tmpdir): """Test that using CoregistrationUI matches mne coreg.""" + from mne.gui._coreg import CoregistrationUI tempdir = str(tmpdir) tmp_trans = op.join(tempdir, 'tmp-trans.fif') coreg = CoregistrationUI(info_file=None, subject='sample', From ade5c682f55c11289dd202ac6b437cfe139c630f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:23:03 +0200 Subject: [PATCH 205/225] requires pyvistaqt --- mne/gui/tests/test_coreg_ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/gui/tests/test_coreg_ui.py b/mne/gui/tests/test_coreg_ui.py index 9c9f626a709..43c5e3789ef 100644 --- a/mne/gui/tests/test_coreg_ui.py +++ b/mne/gui/tests/test_coreg_ui.py @@ -43,7 +43,7 @@ def GetEventPosition(self): @pytest.mark.slowtest @testing.requires_testing_data -def test_coregistration_ui(tmpdir): +def test_coregistration_ui(tmpdir, renderer_interactive_pyvistaqt): """Test that using CoregistrationUI matches mne coreg.""" from mne.gui._coreg import CoregistrationUI tempdir = str(tmpdir) From 46bcd533eaae8ffa6e1c9f45ae8b4ebd684e5a54 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:32:06 +0200 Subject: [PATCH 206/225] move to existing file --- mne/gui/tests/test_coreg_gui.py | 80 +++++++++++++++++++++++++++++ mne/gui/tests/test_coreg_ui.py | 89 --------------------------------- 2 files changed, 80 insertions(+), 89 deletions(-) delete mode 100644 mne/gui/tests/test_coreg_ui.py diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index 3ae34adc5a1..3aff8fe998e 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -24,6 +24,7 @@ 'sample_audvis_trunc-trans.fif') kit_raw_path = op.join(kit_data_dir, 'test_bin_raw.fif') subjects_dir = op.join(data_path, 'subjects') +fid_fname = op.join(subjects_dir, 'sample', 'bem', 'sample-fiducials.fif') @testing.requires_testing_data @@ -336,3 +337,82 @@ def test_coreg_gui_automation(): errs_nearest = np.median( dig_mri_distances(info, fname_trans, subject, subjects_dir)) assert 1e-3 < errs_nearest < 2e-3 + + +class TstVTKPicker(object): + """Class to test cell picking.""" + + def __init__(self, mesh, cell_id, event_pos): + self.mesh = mesh + self.cell_id = cell_id + self.point_id = None + self.event_pos = event_pos + + def GetCellId(self): + """Return the picked cell.""" + return self.cell_id + + def GetDataSet(self): + """Return the picked mesh.""" + return self.mesh + + def GetPickPosition(self): + """Return the picked position.""" + vtk_cell = self.mesh.GetCell(self.cell_id) + cell = [vtk_cell.GetPointId(point_id) for point_id + in range(vtk_cell.GetNumberOfPoints())] + self.point_id = cell[0] + return self.mesh.points[self.point_id] + + def GetEventPosition(self): + """Return event position.""" + return self.event_pos + + +@pytest.mark.slowtest +@testing.requires_testing_data +def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): + """Test that using CoregistrationUI matches mne coreg.""" + from mne.gui._coreg import CoregistrationUI + tempdir = str(tmpdir) + tmp_trans = op.join(tempdir, 'tmp-trans.fif') + coreg = CoregistrationUI(info_file=None, subject='sample', + subjects_dir=subjects_dir, trans=fname_trans, + show=False) + coreg._reset_fiducials() + coreg.close() + coreg = CoregistrationUI(info_file=raw_path, subject='sample', + subjects_dir=subjects_dir, show=False) + coreg._set_fiducials_file(fid_fname) + assert coreg._fiducials_file == fid_fname + # picking + vtk_picker = TstVTKPicker(coreg._surfaces['head'], 0, (0, 0)) + coreg._on_mouse_move(vtk_picker, None) + coreg._on_button_press(vtk_picker, None) + coreg._on_pick(vtk_picker, None) + coreg._on_button_release(vtk_picker, None) + coreg._set_lock_fids(True) + assert coreg._lock_fids + coreg._on_pick(vtk_picker, None) # also pick when locked + coreg._set_lock_fids(False) + assert not coreg._lock_fids + coreg._set_lock_fids(True) + assert coreg._lock_fids + assert coreg._nasion_weight == 10. + coreg._set_point_weight(11., 'nasion') + assert coreg._nasion_weight == 11. + coreg._fit_fiducials() + coreg._fit_icp() + coreg._omit_hsp() + assert coreg._coreg._extra_points_filter is not None + coreg._reset_omit_hsp_filter() + assert coreg._coreg._extra_points_filter is None + coreg._set_orient_glyphs(True) + assert coreg._orient_glyphs + coreg._set_head_resolution(True) + assert coreg._head_resolution + coreg._set_head_transparency(True) + assert coreg._head_transparency + coreg._save_trans(tmp_trans) + assert op.isfile(tmp_trans) + coreg.close() diff --git a/mne/gui/tests/test_coreg_ui.py b/mne/gui/tests/test_coreg_ui.py deleted file mode 100644 index 43c5e3789ef..00000000000 --- a/mne/gui/tests/test_coreg_ui.py +++ /dev/null @@ -1,89 +0,0 @@ -import pytest -import os -import os.path as op -from mne.datasets import testing - -data_path = testing.data_path(download=False) -subjects_dir = os.path.join(data_path, 'subjects') -fid_fname = op.join(subjects_dir, 'sample', 'bem', 'sample-fiducials.fif') -raw_fname = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif') -trans_fname = op.join(data_path, 'MEG', 'sample', - 'sample_audvis_trunc-trans.fif') - - -class TstVTKPicker(object): - """Class to test cell picking.""" - - def __init__(self, mesh, cell_id, event_pos): - self.mesh = mesh - self.cell_id = cell_id - self.point_id = None - self.event_pos = event_pos - - def GetCellId(self): - """Return the picked cell.""" - return self.cell_id - - def GetDataSet(self): - """Return the picked mesh.""" - return self.mesh - - def GetPickPosition(self): - """Return the picked position.""" - vtk_cell = self.mesh.GetCell(self.cell_id) - cell = [vtk_cell.GetPointId(point_id) for point_id - in range(vtk_cell.GetNumberOfPoints())] - self.point_id = cell[0] - return self.mesh.points[self.point_id] - - def GetEventPosition(self): - """Return event position.""" - return self.event_pos - - -@pytest.mark.slowtest -@testing.requires_testing_data -def test_coregistration_ui(tmpdir, renderer_interactive_pyvistaqt): - """Test that using CoregistrationUI matches mne coreg.""" - from mne.gui._coreg import CoregistrationUI - tempdir = str(tmpdir) - tmp_trans = op.join(tempdir, 'tmp-trans.fif') - coreg = CoregistrationUI(info_file=None, subject='sample', - subjects_dir=subjects_dir, trans=trans_fname, - show=False) - coreg.close() - coreg = CoregistrationUI(info_file=raw_fname, subject='sample', - subjects_dir=subjects_dir, show=False) - coreg._set_fiducials_file(fid_fname) - assert coreg._fiducials_file == fid_fname - # picking - vtk_picker = TstVTKPicker(coreg._surfaces['head'], 0, (0, 0)) - coreg._on_mouse_move(vtk_picker, None) - coreg._on_button_press(vtk_picker, None) - coreg._on_pick(vtk_picker, None) - coreg._on_button_release(vtk_picker, None) - coreg._set_lock_fids(True) - assert coreg._lock_fids - coreg._on_pick(vtk_picker, None) # also pick when locked - coreg._set_lock_fids(False) - assert not coreg._lock_fids - coreg._set_lock_fids(True) - assert coreg._lock_fids - assert coreg._nasion_weight == 10. - coreg._set_point_weight(11., 'nasion') - assert coreg._nasion_weight == 11. - coreg._fit_fiducials() - coreg._fit_icp() - coreg._omit_hsp() - assert coreg._coreg._extra_points_filter is not None - coreg._reset_omit_hsp_filter() - assert coreg._coreg._extra_points_filter is None - coreg._set_orient_glyphs(True) - assert coreg._orient_glyphs - coreg._set_head_resolution(True) - assert coreg._head_resolution - coreg._set_head_transparency(True) - assert coreg._head_transparency - coreg._save_trans(tmp_trans) - assert op.isfile(tmp_trans) - coreg.close() From 6111b898e743e38d8f213d2b6f1ec1bf2fb127c5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:37:15 +0200 Subject: [PATCH 207/225] nitpick --- mne/gui/tests/test_coreg_gui.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index 3aff8fe998e..d5e3819123a 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -403,6 +403,7 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): assert coreg._nasion_weight == 11. coreg._fit_fiducials() coreg._fit_icp() + assert coreg._coreg._extra_points_filter is None coreg._omit_hsp() assert coreg._coreg._extra_points_filter is not None coreg._reset_omit_hsp_filter() From f5458bc540af9995ee5fb5ee242459332876a09a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 11:43:40 +0200 Subject: [PATCH 208/225] check defaults too --- mne/gui/tests/test_coreg_gui.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index d5e3819123a..1006968b586 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -408,10 +408,21 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): assert coreg._coreg._extra_points_filter is not None coreg._reset_omit_hsp_filter() assert coreg._coreg._extra_points_filter is None + assert coreg._grow_hair == 0 + coreg._set_grow_hair(0.1) + assert coreg._grow_hair == 0.1 + assert coreg._hpi_coils + assert coreg._eeg_channels + assert coreg._head_shape_points + assert coreg._scale_mode == 'None' + assert coreg._icp_fid_match == 'nearest' + assert not coreg._orient_glyphs coreg._set_orient_glyphs(True) assert coreg._orient_glyphs + assert not coreg._head_resolution coreg._set_head_resolution(True) assert coreg._head_resolution + assert not coreg._head_transparency coreg._set_head_transparency(True) assert coreg._head_transparency coreg._save_trans(tmp_trans) From 773a58f79334e17ec7af30df1fa82db0c036c343 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 15:00:30 +0200 Subject: [PATCH 209/225] use _get_3d_backend --- mne/commands/mne_coreg.py | 5 +---- mne/gui/__init__.py | 7 ++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/mne/commands/mne_coreg.py b/mne/commands/mne_coreg.py index 9285daca15e..c902022dd4e 100644 --- a/mne/commands/mne_coreg.py +++ b/mne/commands/mne_coreg.py @@ -77,9 +77,6 @@ def run(): parser.add_option('--simple-rendering', action='store_false', dest='advanced_rendering', help='Use simplified OpenGL rendering') - parser.add_option("--pyvista", - action='store_true', default=False, dest="pyvista", - help="Use the new PyVista/PyQt5 interface.") _add_verbose_flag(parser) options, args = parser.parse_args() @@ -118,7 +115,7 @@ def run(): mark_inside=options.mark_inside, interaction=options.interaction, scale=options.scale, advanced_rendering=options.advanced_rendering, - pyvista=options.pyvista, verbose=options.verbose) + verbose=options.verbose) mne.utils.run_command_if_main() diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index a1f29525dff..7a5ba0a6f0b 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -113,10 +113,6 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, If True (default), add opaque inner scalp head surface to help occlude points behind the head. - .. versionadded:: 0.23 - pyvista : bool - If True, use the PyVista/PyQt5 interface. Defaults to False. - .. versionadded:: 0.23 %(verbose)s @@ -137,6 +133,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, subjects for which no MRI is available `_. """ + from ..viz.backends.renderer import _get_3d_backend config = get_config(home_dir=os.environ.get('_MNE_FAKE_HOME_DIR')) if guess_mri_subject is None: guess_mri_subject = config.get( @@ -179,7 +176,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, width = int(width) height = int(height) scale = float(scale) - if pyvista: + if _get_3d_backend() == 'pyvistaqt': from ._coreg import CoregistrationUI return CoregistrationUI( info_file=inst, subject=subject, subjects_dir=subjects_dir, From 08db55cd65012788d4d30ad53f63444c7f5775ae Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 15:09:00 +0200 Subject: [PATCH 210/225] use coregistration --- mne/gui/__init__.py | 5 ++++- mne/gui/tests/test_coreg_gui.py | 15 ++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 7a5ba0a6f0b..8d1da2752c1 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -177,11 +177,14 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, height = int(height) scale = float(scale) if _get_3d_backend() == 'pyvistaqt': + from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING from ._coreg import CoregistrationUI + show = not MNE_3D_BACKEND_TESTING + standalone = not MNE_3D_BACKEND_TESTING return CoregistrationUI( info_file=inst, subject=subject, subjects_dir=subjects_dir, head_resolution=head_high_res, orient_glyphs=orient_to_surface, - trans=trans, size=(width, height), standalone=True, + trans=trans, size=(width, height), show=show, standalone=standalone, ) else: _check_mayavi_version() diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index 1006968b586..489e41bdbeb 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -373,16 +373,15 @@ def GetEventPosition(self): @testing.requires_testing_data def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): """Test that using CoregistrationUI matches mne coreg.""" - from mne.gui._coreg import CoregistrationUI + from mne.gui import coregistration tempdir = str(tmpdir) tmp_trans = op.join(tempdir, 'tmp-trans.fif') - coreg = CoregistrationUI(info_file=None, subject='sample', - subjects_dir=subjects_dir, trans=fname_trans, - show=False) + coreg = coregistration(subject='sample', subjects_dir=subjects_dir, + trans=fname_trans) coreg._reset_fiducials() coreg.close() - coreg = CoregistrationUI(info_file=raw_path, subject='sample', - subjects_dir=subjects_dir, show=False) + coreg = coregistration(inst=raw_path, subject='sample', + subjects_dir=subjects_dir) coreg._set_fiducials_file(fid_fname) assert coreg._fiducials_file == fid_fname # picking @@ -411,14 +410,12 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): assert coreg._grow_hair == 0 coreg._set_grow_hair(0.1) assert coreg._grow_hair == 0.1 + assert coreg._orient_glyphs assert coreg._hpi_coils assert coreg._eeg_channels assert coreg._head_shape_points assert coreg._scale_mode == 'None' assert coreg._icp_fid_match == 'nearest' - assert not coreg._orient_glyphs - coreg._set_orient_glyphs(True) - assert coreg._orient_glyphs assert not coreg._head_resolution coreg._set_head_resolution(True) assert coreg._head_resolution From c04a85996b3eaced8ddf6c58de1a50d05049bf39 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 15:11:03 +0200 Subject: [PATCH 211/225] reorder imports --- mne/gui/_coreg.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 691d0984f07..71ee41c29a6 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -1,8 +1,11 @@ from contextlib import contextmanager +from functools import partial import os import os.path as op + import numpy as np -from functools import partial +from traitlets import observe, HasTraits, Unicode, Bool, Float + from ..defaults import DEFAULTS from ..io import read_info, read_fiducials from ..io.pick import pick_types @@ -13,7 +16,6 @@ from ..transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) from ..utils import get_subjects_dir, _check_fname, fill_doc -from traitlets import observe, HasTraits, Unicode, Bool, Float @fill_doc From 09b18f2e13b2755fd4a22534d90313b50a59b236 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 16:29:33 +0200 Subject: [PATCH 212/225] fix --- mne/gui/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 8d1da2752c1..c3b56f7d4e3 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -27,7 +27,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, orient_to_surface=None, scale_by_distance=None, mark_inside=None, interaction=None, scale=None, advanced_rendering=None, head_inside=True, - pyvista=False, verbose=None): + verbose=None): """Coregister an MRI with a subject's head shape. The recommended way to use the GUI is through bash with: From e0c49606b99b2480b2a0339f6209b92cb13c28fc Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 16:30:32 +0200 Subject: [PATCH 213/225] restore --- mne/gui/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index c3b56f7d4e3..0e1468fd388 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -26,8 +26,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, trans=None, scrollable=True, project_eeg=None, orient_to_surface=None, scale_by_distance=None, mark_inside=None, interaction=None, scale=None, - advanced_rendering=None, head_inside=True, - verbose=None): + advanced_rendering=None, head_inside=True, verbose=None): """Coregister an MRI with a subject's head shape. The recommended way to use the GUI is through bash with: From f4deaa8a97884c6dbca6c8b9885f447524aa71a3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 17:11:56 +0200 Subject: [PATCH 214/225] add basic support for verbose --- mne/gui/_coreg.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 71ee41c29a6..41014ace142 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -66,6 +66,7 @@ class CoregistrationUI(HasTraits): Display the window as soon as it is ready. Defaults to True. standalone : bool If True, start the Qt application event loop. Default to False. + %(verbose)s """ _subject = Unicode() @@ -89,7 +90,7 @@ def __init__(self, info_file, subject=None, subjects_dir=None, head_transparency=None, hpi_coils=None, head_shape_points=None, eeg_channels=None, orient_glyphs=None, sensor_opacity=None, trans=None, size=None, bgcolor=None, - show=True, standalone=False): + show=True, standalone=False, verbose=None): from ..viz.backends.renderer import _get_renderer def _get_default(var, val): @@ -97,7 +98,7 @@ def _get_default(var, val): self._actors = dict() self._surfaces = dict() self._widgets = dict() - self._verbose = True + self._verbose = verbose self._plot_locked = False self._head_geo = None self._coord_frame = "mri" From 5ba4527d6c94ae9fbb2f89e7e72ab35f8c0e271c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 17:13:07 +0200 Subject: [PATCH 215/225] fix --- mne/gui/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 0e1468fd388..0b263cb864f 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -184,6 +184,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, info_file=inst, subject=subject, subjects_dir=subjects_dir, head_resolution=head_high_res, orient_glyphs=orient_to_surface, trans=trans, size=(width, height), show=show, standalone=standalone, + verbose=verbose ) else: _check_mayavi_version() From f77c172d97dfa013ab8fd0ad0afed732013b1f51 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 25 Oct 2021 17:39:03 +0200 Subject: [PATCH 216/225] warn unsupported parameters --- mne/gui/__init__.py | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 0b263cb864f..6203192cd5a 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -6,7 +6,7 @@ import os -from ..utils import _check_mayavi_version, verbose, get_config +from ..utils import _check_mayavi_version, verbose, get_config, warn from ._backend import _testing_mode @@ -133,6 +133,31 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, `_. """ from ..viz.backends.renderer import _get_3d_backend + pyvistaqt = _get_3d_backend() == 'pyvistaqt' + if pyvistaqt: + # unsupported parameters + params = { + 'tabbed': (tabbed, False), + 'split': (split, True), + 'scrollable': (scrollable, True), + 'head_inside': (head_inside, True), + 'guess_mri_subject': guess_mri_subject, + 'head_opacity': head_opacity, + 'project_eeg': project_eeg, + 'scale_by_distance': scale_by_distance, + 'mark_inside': mark_inside, + 'interaction': interaction, + 'scale': scale, + 'advanced_rendering': advanced_rendering, + } + for key, val in params.items(): + if isinstance(val, tuple): + to_raise = val[0] != val[1] + else: + to_raise = val is not None + if to_raise: + warn(f"The parameter {key} is not supported with" + " the pyvistaqt 3d backend. It will be ignored.") config = get_config(home_dir=os.environ.get('_MNE_FAKE_HOME_DIR')) if guess_mri_subject is None: guess_mri_subject = config.get( @@ -175,7 +200,7 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, width = int(width) height = int(height) scale = float(scale) - if _get_3d_backend() == 'pyvistaqt': + if pyvistaqt: from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING from ._coreg import CoregistrationUI show = not MNE_3D_BACKEND_TESTING From 3b7b28f8c54c7d424a6a4e1eea8586f012595c75 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 11:45:34 +0200 Subject: [PATCH 217/225] TST: show the renderer --- mne/gui/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 6203192cd5a..09682eb8446 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -203,12 +203,11 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, if pyvistaqt: from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING from ._coreg import CoregistrationUI - show = not MNE_3D_BACKEND_TESTING standalone = not MNE_3D_BACKEND_TESTING return CoregistrationUI( info_file=inst, subject=subject, subjects_dir=subjects_dir, head_resolution=head_high_res, orient_glyphs=orient_to_surface, - trans=trans, size=(width, height), show=show, standalone=standalone, + trans=trans, size=(width, height), standalone=standalone, verbose=verbose ) else: From bbf00cea14ebf3895f180b9f1c6d9ff84e49e06a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 13:19:11 +0200 Subject: [PATCH 218/225] Revert "TST: show the renderer" This reverts commit 3b7b28f8c54c7d424a6a4e1eea8586f012595c75. --- mne/gui/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mne/gui/__init__.py b/mne/gui/__init__.py index 09682eb8446..6203192cd5a 100644 --- a/mne/gui/__init__.py +++ b/mne/gui/__init__.py @@ -203,11 +203,12 @@ def coregistration(tabbed=False, split=True, width=None, inst=None, if pyvistaqt: from ..viz.backends.renderer import MNE_3D_BACKEND_TESTING from ._coreg import CoregistrationUI + show = not MNE_3D_BACKEND_TESTING standalone = not MNE_3D_BACKEND_TESTING return CoregistrationUI( info_file=inst, subject=subject, subjects_dir=subjects_dir, head_resolution=head_high_res, orient_glyphs=orient_to_surface, - trans=trans, size=(width, height), standalone=standalone, + trans=trans, size=(width, height), show=show, standalone=standalone, verbose=verbose ) else: From 1eb4448a77152c3c82bb8decc61b61a4215049f2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 13:31:35 +0200 Subject: [PATCH 219/225] TST: use get_config --- mne/gui/tests/test_coreg_gui.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index 489e41bdbeb..46bab8ccc2f 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -16,7 +16,7 @@ from mne.io.kit.tests import data_dir as kit_data_dir from mne.surface import dig_mri_distances from mne.transforms import invert_transform -from mne.utils import requires_mayavi, traits_test, modified_env +from mne.utils import requires_mayavi, traits_test, modified_env, get_config data_path = testing.data_path(download=False) raw_path = op.join(data_path, 'MEG', 'sample', 'sample_audvis_trunc_raw.fif') @@ -375,6 +375,7 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): """Test that using CoregistrationUI matches mne coreg.""" from mne.gui import coregistration tempdir = str(tmpdir) + config = get_config(home_dir=os.environ.get('_MNE_FAKE_HOME_DIR')) tmp_trans = op.join(tempdir, 'tmp-trans.fif') coreg = coregistration(subject='sample', subjects_dir=subjects_dir, trans=fname_trans) @@ -410,7 +411,9 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): assert coreg._grow_hair == 0 coreg._set_grow_hair(0.1) assert coreg._grow_hair == 0.1 - assert coreg._orient_glyphs + orient_to_surface = (config.get('MNE_COREG_ORIENT_TO_SURFACE', '') == + 'true') + assert coreg._orient_glyphs == orient_to_surface assert coreg._hpi_coils assert coreg._eeg_channels assert coreg._head_shape_points From 1fa6e51307cba07e14457e7997463cdd6ce23ef9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 14:46:15 +0200 Subject: [PATCH 220/225] use config --- mne/gui/tests/test_coreg_gui.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index 46bab8ccc2f..8405d14abe8 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -411,17 +411,15 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): assert coreg._grow_hair == 0 coreg._set_grow_hair(0.1) assert coreg._grow_hair == 0.1 - orient_to_surface = (config.get('MNE_COREG_ORIENT_TO_SURFACE', '') == - 'true') - assert coreg._orient_glyphs == orient_to_surface + assert coreg._orient_glyphs == \ + config.get('MNE_COREG_ORIENT_TO_SURFACE', '') == 'true' assert coreg._hpi_coils assert coreg._eeg_channels assert coreg._head_shape_points assert coreg._scale_mode == 'None' assert coreg._icp_fid_match == 'nearest' - assert not coreg._head_resolution - coreg._set_head_resolution(True) - assert coreg._head_resolution + assert coreg._head_resolution == \ + config.get('MNE_COREG_HEAD_HIGH_RES', 'true') == 'true' assert not coreg._head_transparency coreg._set_head_transparency(True) assert coreg._head_transparency From 6379605cdbb2c2638890b938f4dff2a3048226f5 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 14:47:32 +0200 Subject: [PATCH 221/225] fix --- mne/gui/tests/test_coreg_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/gui/tests/test_coreg_gui.py b/mne/gui/tests/test_coreg_gui.py index 8405d14abe8..b36f2c5e860 100644 --- a/mne/gui/tests/test_coreg_gui.py +++ b/mne/gui/tests/test_coreg_gui.py @@ -412,14 +412,14 @@ def test_coreg_gui_pyvista(tmpdir, renderer_interactive_pyvistaqt): coreg._set_grow_hair(0.1) assert coreg._grow_hair == 0.1 assert coreg._orient_glyphs == \ - config.get('MNE_COREG_ORIENT_TO_SURFACE', '') == 'true' + (config.get('MNE_COREG_ORIENT_TO_SURFACE', '') == 'true') assert coreg._hpi_coils assert coreg._eeg_channels assert coreg._head_shape_points assert coreg._scale_mode == 'None' assert coreg._icp_fid_match == 'nearest' assert coreg._head_resolution == \ - config.get('MNE_COREG_HEAD_HIGH_RES', 'true') == 'true' + (config.get('MNE_COREG_HEAD_HIGH_RES', 'true') == 'true') assert not coreg._head_transparency coreg._set_head_transparency(True) assert coreg._head_transparency From 6bcc9a89dfc5c83791521d184ff7f9e948599a9b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 14:49:35 +0200 Subject: [PATCH 222/225] restore lighting --- mne/viz/backends/_pyvista.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index b905696a92b..3201b626203 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -191,7 +191,7 @@ def __init__(self, fig=None, size=(600, 600), bgcolor='black', if not hasattr(self.plotter, "iren"): self.plotter.iren = None - self.plotter.enable_3_lights() + self.update_lighting() @property def _all_plotters(self): @@ -235,6 +235,25 @@ def subplot(self, x, y): def scene(self): return self.figure + def update_lighting(self): + # Inspired from Mayavi's version of Raymond Maple 3-lights illumination + for renderer in self._all_renderers: + lights = list(renderer.GetLights()) + headlight = lights.pop(0) + headlight.SetSwitch(False) + # below and centered, left and above, right and above + az_el_in = ((0, -45, 0.7), (-60, 30, 0.7), (60, 30, 0.7)) + for li, light in enumerate(lights): + if li < len(az_el_in): + light.SetSwitch(True) + light.SetPosition(_to_pos(*az_el_in[li][:2])) + light.SetIntensity(az_el_in[li][2]) + else: + light.SetSwitch(False) + light.SetPosition(_to_pos(0.0, 0.0)) + light.SetIntensity(0.0) + light.SetColor(1.0, 1.0, 1.0) + def set_interaction(self, interaction): if not hasattr(self.plotter, "iren") or self.plotter.iren is None: return From 87100975f519416976cd71617420839a3e0e09af Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 16:03:58 +0200 Subject: [PATCH 223/225] add _check_fif --- mne/gui/_coreg.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index 41014ace142..cf31b1b1a12 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -15,7 +15,7 @@ _plot_hpi_coils, _plot_sensors) from ..transforms import (read_trans, write_trans, _ensure_trans, rotation_angles, _get_transforms_to_coord_frame) -from ..utils import get_subjects_dir, _check_fname, fill_doc +from ..utils import get_subjects_dir, check_fname, _check_fname, fill_doc, warn @fill_doc @@ -204,6 +204,8 @@ def _set_lock_fids(self, state): self._lock_fids = bool(state) def _set_fiducials_file(self, fname): + if self._check_fif('fiducials', fname): + return self._fiducials_file = _check_fname( fname, overwrite=True, must_exist=True, need_dir=False) @@ -213,6 +215,8 @@ def _set_current_fiducial(self, fid): def _set_info_file(self, fname): if fname is None: return + if self._check_fif('info', fname): + return self._info_file = _check_fname( fname, overwrite=True, must_exist=True, need_dir=False) @@ -676,6 +680,15 @@ def _get_subjects(self, sdir=None): subjects = [''] return sorted(subjects) + def _check_fif(self, filetype, fname): + try: + check_fname(fname, filetype, ('.fif'), ('.fif')) + except IOError: + warn(f"The filename {fname} for {filetype} must end with '.fif'.") + self._widgets[f"{filetype}_file"].set_value(0, '') + return True + return False + def _configure_dock(self): self._renderer._dock_initialize(name="Input", area="left") layout = self._renderer._dock_add_group_box("MRI Subject") @@ -704,8 +717,8 @@ def _configure_dock(self): callback=self._set_lock_fids, layout=layout ) - self._widgets["fid_file"] = self._renderer._dock_add_file_button( - name="fid_file", + self._widgets["fiducials_file"] = self._renderer._dock_add_file_button( + name="fiducials_file", desc="Load", func=self._set_fiducials_file, value=self._fiducials_file, From dafb4fc543ddd4b2aa97d0a6a737a5816359e788 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 16:08:23 +0200 Subject: [PATCH 224/225] fix style --- mne/utils/docs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 83d7457a41c..23c086187ca 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -2756,6 +2756,7 @@ docdict_indented = {} + def fill_doc(f): """Fill a docstring with docdict entries. From bc3a739628ee75c517d345435ce7489f8cd966a9 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 26 Oct 2021 17:46:05 +0200 Subject: [PATCH 225/225] reverse _check_fif logic --- mne/gui/_coreg.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mne/gui/_coreg.py b/mne/gui/_coreg.py index cf31b1b1a12..7637b6dd4b4 100644 --- a/mne/gui/_coreg.py +++ b/mne/gui/_coreg.py @@ -204,7 +204,7 @@ def _set_lock_fids(self, state): self._lock_fids = bool(state) def _set_fiducials_file(self, fname): - if self._check_fif('fiducials', fname): + if not self._check_fif('fiducials', fname): return self._fiducials_file = _check_fname( fname, overwrite=True, must_exist=True, need_dir=False) @@ -215,7 +215,7 @@ def _set_current_fiducial(self, fid): def _set_info_file(self, fname): if fname is None: return - if self._check_fif('info', fname): + if not self._check_fif('info', fname): return self._info_file = _check_fname( fname, overwrite=True, must_exist=True, need_dir=False) @@ -686,8 +686,8 @@ def _check_fif(self, filetype, fname): except IOError: warn(f"The filename {fname} for {filetype} must end with '.fif'.") self._widgets[f"{filetype}_file"].set_value(0, '') - return True - return False + return False + return True def _configure_dock(self): self._renderer._dock_initialize(name="Input", area="left")