Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into gui
Browse files Browse the repository at this point in the history
* upstream/main:
  FIX: close_connect (mne-tools#10388) [circle deploy]
  FIX: Bump deps (mne-tools#10376)
  [DOC] Small fixes [skip azure] [skip actions] (mne-tools#10381)
  FIX: coreg status bar (mne-tools#10368)
  • Loading branch information
larsoner committed Feb 23, 2022
2 parents 20f3597 + 61f10d5 commit ba67e64
Show file tree
Hide file tree
Showing 15 changed files with 78 additions and 39 deletions.
2 changes: 1 addition & 1 deletion doc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ html-noplot:
@echo "Build finished. The HTML pages are in _build/html_stable."

html_dev-front:
@PATTERN="\(plot_mne_dspm_source_localization.py\|plot_receptive_field.py\|plot_mne_inverse_label_connectivity.py\|plot_sensors_decoding.py\|plot_stats_cluster_spatio_temporal.py\|plot_20_visualize_evoked.py\)" make html_dev-pattern;
@PATTERN="\(30_mne_dspm_loreta.py\|50_decoding.py\|30_strf.py\|20_cluster_1samp_spatiotemporal.py\|20_visualize_evoked.py\)" make html_dev-pattern;

dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) _build/dirhtml
Expand Down
2 changes: 1 addition & 1 deletion doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ Enhancements

- :meth:`mne.preprocessing.ICA.plot_sources()` is now also supported by the ``qt`` backend (:gh:`10330` by `Martin Schulz`_)

- Added :meth:`mne.viz.Brain.add_dipole` and :meth:`mne.viz.Brain.add_forward` to plot dipoles on a brain (:gh:`10373` by `Alex Rockhill`_)
- Added :meth:`mne.viz.Brain.add_dipole` and :meth:`mne.viz.Brain.add_forward` to plot dipoles on a brain as well as :meth:`mne.viz.Brain.remove_dipole` and :meth:`mne.viz.Brain.remove_forward` (:gh:`10373` by `Alex Rockhill`_)

Bugs
~~~~
Expand Down
5 changes: 3 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html

from datetime import datetime, timezone
import faulthandler
import gc
import os
import subprocess
import sys
import time
import warnings
from datetime import datetime, timezone
import faulthandler

import numpy as np
import matplotlib
Expand Down Expand Up @@ -732,6 +732,7 @@ def append_attr_meth_examples(app, what, name, obj, options, lines):
size=xxl),
],
# \u00AD is an optional hyphen (not rendered unless needed)
# If these are changed, the Makefile should be updated, too
'carousel': [
dict(title='Source Estimation',
text='Distributed, sparse, mixed-norm, beam\u00ADformers, dipole fitting, and more.', # noqa E501
Expand Down
20 changes: 9 additions & 11 deletions mne/gui/_coreg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from functools import partial
import os
import os.path as op
import platform
from pathlib import Path
import time
import queue
Expand Down Expand Up @@ -178,7 +179,6 @@ def _get_default(var, val):
self._mri_fids_modified = False
self._mri_scale_modified = False
self._accept_close_event = True
self._auto_cleanup = True
self._fid_colors = tuple(
DEFAULTS['coreg'][f'{key}_color'] for key in
('lpa', 'nasion', 'rpa'))
Expand Down Expand Up @@ -230,9 +230,9 @@ def _get_default(var, val):
# setup the window
self._renderer = _get_renderer(
size=self._defaults["size"], bgcolor=self._defaults["bgcolor"])
self._renderer._window_close_connect(self._close_callback)
self._renderer._window_close_connect(self._clean)
self._renderer._window_close_connect(self._close_callback, after=False)
self._renderer.set_interaction(interaction)
self._renderer._status_bar_initialize()

# coregistration model setup
self._immediate_redraw = (self._renderer._kind != 'qt')
Expand Down Expand Up @@ -728,7 +728,9 @@ def _redraw(self, verbose=None):
draw_map[key]()
self._redraws_pending.clear()
self._renderer._update()
self._renderer._process_events() # necessary for MacOS?
# necessary for MacOS
if platform.system() == 'Darwin':
self._renderer._process_events()

def _on_mouse_move(self, vtk_picker, event):
if self._mouse_no_mvt:
Expand Down Expand Up @@ -1707,18 +1709,17 @@ def _configure_dock(self):
self._renderer._dock_add_stretch()

def _configure_status_bar(self):
self._renderer._status_bar_initialize()
self._widgets['status_message'] = self._renderer._status_bar_add_label(
"", stretch=1
)
self._forward_widget_command(
'status_message', 'hide', value=None, input_value=False
)

def _set_automatic_cleanup(self, state):
"""Enable/Disable automatic cleanup (for testing purposes only)."""
self._auto_cleanup = state

def _clean(self):
if not self._accept_close_event:
return
self._renderer = None
self._widgets.clear()
self._actors.clear()
Expand Down Expand Up @@ -1789,7 +1790,4 @@ def _close_callback(self):
modal=not MNE_3D_BACKEND_TESTING,
)
self._widgets["close_dialog"].show()

if self._accept_close_event and self._auto_cleanup:
self._clean()
return self._accept_close_event
3 changes: 2 additions & 1 deletion mne/gui/tests/test_coreg_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,9 @@ def test_coreg_gui_pyvista(tmp_path, renderer_interactive_pyvistaqt):
assert not coreg._trans_modified
assert op.isfile(tmp_trans)

# first, disable auto cleanup
coreg._renderer._window_close_disconnect(after=True)
# test _close_callback()
coreg._set_automatic_cleanup(False)
coreg.close()
coreg._widgets['close_dialog'].trigger('Discard') # do not save
coreg._clean() # finally, cleanup internal structures
Expand Down
8 changes: 8 additions & 0 deletions mne/gui/tests/test_gui_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,15 @@ def _check_widget_trigger(widget, mock, before, after, call_count=True,
# --- END: dialog ---

renderer.show()

renderer._window_close_connect(lambda: mock('first'), after=False)
renderer._window_close_connect(lambda: mock('last'))
old_call_count = mock.call_count
renderer.close()
if renderer._kind == 'qt':
assert mock.call_count == old_call_count + 2
assert mock.call_args_list[-1].args == ('last',)
assert mock.call_args_list[-2].args == ('first',)


def test_gui_api_qt(renderer_interactive_pyvistaqt):
Expand Down
13 changes: 11 additions & 2 deletions mne/utils/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ def _file_like(obj):
return all(callable(getattr(obj, name, None)) for name in ('read', 'seek'))


def _fullname(obj):
klass = obj.__class__
module = klass.__module__
if module == 'builtins':
return klass.__qualname__
return module + '.' + klass.__qualname__


def _assert_no_instances(cls, when=''):
__tracebackhide__ = True
n = 0
Expand All @@ -350,14 +358,15 @@ def _assert_no_instances(cls, when=''):
if isinstance(r, (list, dict)):
rep = f'len={len(r)}'
r_ = gc.get_referrers(r)
types = (x.__class__.__name__ for x in r_)
types = (_fullname(x) for x in r_)
types = "/".join(sorted(set(
x for x in types if x is not None)))
rep += f', {len(r_)} referrers: {types}'
del r_
else:
rep = repr(r)[:100].replace('\n', ' ')
ref.append(f'{r.__class__.__name__}: {rep}')
name = _fullname(r)
ref.append(f'{name}: {rep}')
count += 1
del r
del rr
Expand Down
4 changes: 2 additions & 2 deletions mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -745,7 +745,7 @@ def _clean(self):
for renderer in self._renderer._all_renderers:
renderer.RemoveAllLights()
# app_window cannot be set to None because it is used in __del__
for key in ('lighting', 'interactor'):
for key in ('lighting', 'interactor', '_RenderWindow'):
setattr(self.plotter, key, None)
# Qt LeaveEvent requires _Iren so we use _FakeIren instead of None
# to resolve the ref to vtkGenericRenderWindowInteractor
Expand Down Expand Up @@ -2441,7 +2441,7 @@ def add_dipole(self, dipole, trans, colors='red', alpha=1, scales=None):
Parameters
----------
dipole : instance of Dipole | instance of Forward
dipole : instance of Dipole
Dipole object containing position, orientation and amplitude of
one or more dipoles or in the forward solution.
%(trans_not_none)s
Expand Down
6 changes: 5 additions & 1 deletion mne/viz/backends/_abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -844,7 +844,11 @@ def _window_initialize(self):
self._interactor_fraction = None

@abstractmethod
def _window_close_connect(self, func):
def _window_close_connect(self, func, *, after=True):
pass

@abstractmethod
def _window_close_disconnect(self, after=True):
pass

@abstractmethod
Expand Down
11 changes: 7 additions & 4 deletions mne/viz/backends/_notebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -337,17 +337,17 @@ def _menu_add_button(self, menu_name, name, desc, func):

class _IpyStatusBar(_AbstractStatusBar, _IpyLayout):
def _status_bar_initialize(self, window=None):
self._status_bar = self._status_bar_layout = HBox()
self._status_bar = HBox()
self._layout_initialize(None)

def _status_bar_add_label(self, value, *, stretch=0):
widget = Text(value=value, disabled=True)
self._layout_add_widget(self._status_bar_layout, widget)
self._layout_add_widget(self._status_bar, widget)
return _IpyWidget(widget)

def _status_bar_add_progress_bar(self, stretch=0):
widget = IntProgress()
self._layout_add_widget(self._status_bar_layout, widget)
self._layout_add_widget(self._status_bar, widget)
return _IpyWidget(widget)

def _status_bar_update(self):
Expand Down Expand Up @@ -388,7 +388,10 @@ def __init__(self, brain, width, height, dpi):


class _IpyWindow(_AbstractWindow):
def _window_close_connect(self, func):
def _window_close_connect(self, func, *, after=True):
pass

def _window_close_disconnect(self, after=True):
pass

def _window_get_dpi(self):
Expand Down
31 changes: 23 additions & 8 deletions mne/viz/backends/_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,20 +485,19 @@ class _QtStatusBar(_AbstractStatusBar, _QtLayout):
def _status_bar_initialize(self, window=None):
window = self._window if window is None else window
self._status_bar = window.statusBar()
self._status_bar_layout = self._status_bar.layout()

def _status_bar_add_label(self, value, *, stretch=0):
widget = QLabel(value)
self._layout_add_widget(self._status_bar_layout, widget, stretch)
self._layout_add_widget(self._status_bar.layout(), widget, stretch)
return _QtWidget(widget)

def _status_bar_add_progress_bar(self, stretch=0):
widget = QProgressBar()
self._layout_add_widget(self._status_bar_layout, widget, stretch)
self._layout_add_widget(self._status_bar.layout(), widget, stretch)
return _QtWidget(widget)

def _status_bar_update(self):
self._status_bar_layout.update()
self._status_bar.layout().update()


class _QtPlayback(_AbstractPlayback):
Expand Down Expand Up @@ -544,29 +543,45 @@ def _window_initialize(self):
self._window = self.figure.plotter.app_window
self._window.setLocale(QLocale(QLocale.Language.English))
self._window.signal_close.connect(self._window_clean)
self._window_close_callbacks = list()
self._window_before_close_callbacks = list()
self._window_after_close_callbacks = list()

# patch closeEvent
def closeEvent(event):
# functions to call before closing
accept_close_event = True
for callback in self._window_close_callbacks:
for callback in self._window_before_close_callbacks:
ret = callback()
# check if one of the callbacks ignores the close event
if isinstance(ret, bool) and not ret:
accept_close_event = False

if accept_close_event:
self._window.signal_close.emit()
event.accept()
else:
event.ignore()

# functions to call after closing
for callback in self._window_after_close_callbacks:
callback()
self._window.closeEvent = closeEvent

def _window_clean(self):
self.figure._plotter = None
self._interactor = None

def _window_close_connect(self, func):
self._window_close_callbacks.append(func)
def _window_close_connect(self, func, *, after=True):
if after:
self._window_after_close_callbacks.append(func)
else:
self._window_before_close_callbacks.append(func)

def _window_close_disconnect(self, after=True):
if after:
self._window_after_close_callbacks.clear()
else:
self._window_before_close_callbacks.clear()

def _window_get_dpi(self):
return self._window.windowHandle().screen().logicalDotsPerInch()
Expand Down
6 changes: 3 additions & 3 deletions tools/azure_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ elif [ "${TEST_MODE}" == "pip-pre" ]; then
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" python-dateutil pytz joblib threadpoolctl six cycler kiwisolver pyparsing patsy
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps --extra-index-url https://www.riverbankcomputing.com/pypi/simple PyQt5 PyQt5-sip PyQt5-Qt5
# SciPy Windows build is missing from conda nightly builds, and statsmodels does not work
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" "numpy<1.23.dev0+730"
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps scipy statsmodels
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" pandas scikit-learn dipy
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" numpy
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps scipy
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" pandas scikit-learn dipy statsmodels
python -m pip install --progress-bar off --upgrade --pre --only-binary ":all:" --no-deps -f "https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" h5py Pillow matplotlib
python -m pip install --progress-bar off --upgrade --pre --only-binary "vtk" vtk
python -m pip install --progress-bar off https://github.com/pyvista/pyvista/zipball/main
Expand Down
2 changes: 1 addition & 1 deletion tools/github_actions_dependencies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ else
echo "NumPy/SciPy/pandas etc."
# TODO: Currently missing dipy for 3.10 https://github.com/dipy/dipy/issues/2489
# TODO: Revert this and Azure once https://github.com/numpy/numpy/pull/21054 is merged and a new wheel is out
pip install $STD_ARGS --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" "numpy<1.23.dev0+730" scipy pandas "scikit-learn>=0.24.2" statsmodels
pip install $STD_ARGS --pre --only-binary ":all:" --no-deps -i "https://pypi.anaconda.org/scipy-wheels-nightly/simple" numpy scipy pandas scikit-learn statsmodels
echo "H5py, pillow, matplotlib"
pip install $STD_ARGS --pre --only-binary ":all:" --no-deps -f "https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com" h5py pillow matplotlib
# We don't install Numba here because it forces an old NumPy version
Expand Down
2 changes: 1 addition & 1 deletion tutorials/clinical/10_ieeg_localize.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def plot_overlay(image, compare, title, thresh=None):
# reg_affine, _ = mne.transforms.compute_volume_registration(
# CT_orig, T1, pipeline='rigids', zooms=dict(translation=5)))
#
# And instead we just hard-code the resulting 4x4 matrix:
# Instead we just hard-code the resulting 4x4 matrix:

reg_affine = np.array([
[0.99270756, -0.03243313, 0.11610254, -133.094156],
Expand Down
2 changes: 1 addition & 1 deletion tutorials/preprocessing/70_fnirs_processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@
mne.viz.plot_evoked_topo(epochs['Right'].average(picks='hbo'), color='r',
axes=axes, legend=False)

# Tidy the legend.
# Tidy the legend:
leg_lines = [line for line in axes.lines if line.get_c() == 'b'][:1]
leg_lines.append([line for line in axes.lines if line.get_c() == 'r'][0])
fig.legend(leg_lines, ['Left', 'Right'], loc='lower right')

0 comments on commit ba67e64

Please sign in to comment.