Skip to content

Commit

Permalink
TST: Reproduce paintEvent failures (#7294)
Browse files Browse the repository at this point in the history
* Revert "MRG: Add toggle to save_movie in _TimeViewer (#7257)"

This reverts commit 7f6120b.

* Revert _interpolate_data signature
  • Loading branch information
GuillaumeFavelier authored Mar 17, 2020
1 parent 6915a36 commit 95ad0ee
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 382 deletions.
1 change: 0 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ For full functionality, some functions require:
- Picard >= 0.3
- CuPy >= 4.0 (for NVIDIA CUDA acceleration)
- DIPY >= 0.10.1
- Imageio >= 2.6.1
- PyVista >= 0.23.1

Contributing to MNE-Python
Expand Down
2 changes: 0 additions & 2 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ Changelog
- Expose the number of ICA iterations during the fitting procedure via the ``n_iter_`` attribute of :class:`mne.preprocessing.ICA` by `Richard Höchenberger`_
- Support for saving movies of source time courses (STCs) with ``brain.save_movie`` method and from graphical user interface by `Guillaume Favelier`_
- :func:`mne.grand_average` now produces a warning when only a single dataset was passed, instead of raising an error by `Richard Höchenberger`_
Bug
Expand Down
1 change: 0 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ dependencies:
- traits>=4.6.0
- pyface>=6
- traitsui>=6
- imageio>=2.6.1
- vtk
- pip:
- mne
Expand Down
6 changes: 0 additions & 6 deletions mne/utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,12 +728,6 @@
If True, use a linear transparency between fmin and fmid.
None will choose automatically based on colormap type.
"""
docdict["brain_time_interpolation"] = """
interpolation : str | None
Interpolation method (:func:`scipy.interpolate.interp1d` parameter).
Must be one of 'linear', 'nearest', 'zero', 'slinear', 'quadratic',
or 'cubic'.
"""

# STC label time course
docdict['eltc_labels'] = """
Expand Down
242 changes: 44 additions & 198 deletions mne/viz/_brain/_brain.py

Large diffs are not rendered by default.

150 changes: 9 additions & 141 deletions mne/viz/_brain/_timeviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@
# License: Simplified BSD

from itertools import cycle
from functools import partial
import os
import time
import traceback
import numpy as np

from . import _Brain
from ..utils import _check_option, _show_help, _get_color_list, tight_layout
from ...utils import warn, copy_doc
from ...source_space import vertex_to_mni


Expand Down Expand Up @@ -320,12 +314,8 @@ def __init__(self, brain, show_traces=False):
# Direct access parameters:
self.brain = brain
self.brain.time_viewer = self
self.brain._save_movie = self.brain.save_movie
self.brain.save_movie = self.save_movie
self.plotter = brain._renderer.plotter
self.main_menu = self.plotter.main_menu
self.window = self.plotter.app_window
self.status_bar = self.window.statusBar()
self.interactor = self.plotter
self.interactor.keyPressEvent = self.keyPressEvent

Expand All @@ -345,18 +335,14 @@ def __init__(self, brain, show_traces=False):
self.configure_playback()
self.configure_point_picking()
self.configure_menu()
self.configure_status_bar()

def keyPressEvent(self, event):
callback = self.key_bindings.get(event.text())
if callback is not None:
callback()

def toggle_interface(self, value=None):
if value is None:
self.visibility = not self.visibility
else:
self.visibility = value
def toggle_interface(self):
self.visibility = not self.visibility

# manage sliders
for slider in self.plotter.slider_widgets:
Expand All @@ -372,6 +358,7 @@ def toggle_interface(self, value=None):
if self.visibility:
self.time_actor.VisibilityOff()
else:
self.time_actor.SetInput(time_label(self.brain._current_time))
self.time_actor.VisibilityOn()

def apply_auto_scaling(self):
Expand All @@ -392,7 +379,7 @@ def toggle_playback(self):
time_data = self.brain._data['time']
max_time = np.max(time_data)
if self.brain._current_time == max_time: # start over
self.brain.set_time_point(0) # first index
self.brain.set_time_point(np.min(time_data))
self._last_tick = time.time()

def set_playback_speed(self, speed):
Expand All @@ -417,82 +404,6 @@ def play(self):
self.playback = False
self.plotter.update() # critical for smooth animation

def _save_movie(self, filename, **kwargs):
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QCursor

def frame_callback(frame, n_frames):
if frame == n_frames:
# On the ImageIO step
self.status_msg.setText(
_sanitize("💾 Saving with ImageIO: %s"
% filename)
)
self.status_msg.show()
self.status_progress.hide()
self.status_bar.layout().update()
else:
self.status_msg.setText(
_sanitize("📽 Rendering images (frame %d / %d) ..."
% (frame + 1, n_frames))
)
self.status_msg.show()
self.status_progress.show()
self.status_progress.setRange(0, n_frames - 1)
self.status_progress.setValue(frame)
self.status_progress.update()
self.status_progress.repaint()
self.status_msg.update()
self.status_msg.parent().update()
self.status_msg.repaint()

# temporarily hide interface
default_visibility = self.visibility
self.toggle_interface(value=False)
# set cursor to busy
default_cursor = self.interactor.cursor()
self.interactor.setCursor(QCursor(Qt.WaitCursor))

try:
self.brain._save_movie(
filename=filename,
time_dilation=(1. / self.playback_speed),
callback=frame_callback,
**kwargs
)
except (Exception, KeyboardInterrupt):
warn('Movie saving aborted:\n' + traceback.format_exc())

# restore visibility
self.toggle_interface(value=default_visibility)
# restore cursor
self.interactor.setCursor(default_cursor)

@copy_doc(_Brain.save_movie)
def save_movie(self, filename=None, **kwargs):
from pyvista.plotting.qt_plotting import FileDialog

if filename is None:
self.status_msg.setText(_sanitize("📁 Choose movie path ..."))
self.status_msg.show()
self.status_progress.setValue(0)

def _clean(unused):
del unused
self.status_msg.hide()
self.status_progress.hide()

dialog = FileDialog(
self.plotter.app_window,
callback=partial(self._save_movie, **kwargs)
)
dialog.setDirectory(os.getcwd())
dialog.finished.connect(_clean)
return dialog
else:
self._save_movie(filename=filename, **kwargs)
return

def set_slider_style(self, slider, show_label=True):
if slider is not None:
slider_rep = slider.GetRepresentation()
Expand Down Expand Up @@ -759,51 +670,19 @@ def configure_point_picking(self):
self.on_pick
)

def configure_status_bar(self):
from PyQt5.QtWidgets import QLabel, QProgressBar
self.status_msg = QLabel()
self.status_progress = QProgressBar()
self.status_bar.layout().addWidget(self.status_msg, 1)
self.status_bar.layout().addWidget(self.status_progress, 0)
self.status_msg.hide()
self.status_progress.hide()

# display help message for 3 seconds
self.status_bar.showMessage("Press ? for help", 3000)

def configure_menu(self):
main_menu = self.plotter.main_menu
file_menu = None

# add help menu
help_menu = main_menu.addMenu('Help')
help_menu.addAction('Show MNE key bindings', self.help, '?')

# remove default picking menu
to_remove = list()
for action in self.main_menu.actions():
if action.text() == "Tools":
to_remove.append(action)
elif action.text() == "File":
file_menu = action.menu()
for action in to_remove:
main_menu.removeAction(action)
to_remove.clear()

# order the file menu
if file_menu is not None:
for action in file_menu.actions():
if action.text() == "Take Screenshot":
movie_action = file_menu.addAction(
'Save movie...',
self.save_movie,
"ctrl+shift+s"
)
# insert at the right place
file_menu.insertAction(action, movie_action)
break
self.main_menu.removeAction(action)

# add help menu
menu = self.main_menu.addMenu('Help')
menu.addAction('Show MNE key bindings\t?', self.help)

def on_mouse_move(self, vtk_picker, event):
if self._mouse_no_mvt:
self._mouse_no_mvt -= 1
Expand Down Expand Up @@ -944,7 +823,6 @@ def help(self):
('s', 'Apply auto-scaling'),
('r', 'Restore original clim'),
('c', 'Clear all traces'),
('ctrl+shift+s', 'Save movie'),
('Space', 'Start/Pause playback'),
]
text1, text2 = zip(*pairs)
Expand Down Expand Up @@ -1010,19 +888,9 @@ def link_sliders(self, name, callback, event_type):


def _get_range(brain):
data = [brain._data.get(hemi, {}).get('array') for hemi in ('lh', 'rh')]
data = np.concatenate([d for d in data if d is not None])
val = np.abs(data)
val = np.abs(brain._data['array'])
return [np.min(val), np.max(val)]


def _normalize(point, shape):
return (point[0] / shape[1], point[1] / shape[0])


def _sanitize(text):
from PyQt5.Qt import PYQT_VERSION_STR
from distutils.version import LooseVersion
if LooseVersion(PYQT_VERSION_STR) < LooseVersion('5.12'):
text = text[2:]
return text
28 changes: 1 addition & 27 deletions mne/viz/_brain/tests/test_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,20 +160,7 @@ def test_brain_add_text(renderer):


@testing.requires_testing_data
def test_brain_save_movie(tmpdir, renderer):
"""Test saving a movie of a _Brain instance."""
if renderer.get_3d_backend() == "mayavi":
pytest.skip()
brain_data = _create_testing_brain(hemi='lh')
filename = str(path.join(tmpdir, "brain_test.gif"))
brain_data.save_movie(filename, time_dilation=1,
interpolation='nearest')
assert path.isfile(filename)
brain_data.close()


@testing.requires_testing_data
def test_brain_timeviewer(tmpdir, renderer_interactive):
def test_brain_timeviewer(renderer_interactive):
"""Test _TimeViewer primitives."""
brain_data = _create_testing_brain(hemi='both')

Expand All @@ -193,19 +180,6 @@ def test_brain_timeviewer(tmpdir, renderer_interactive):
time_viewer.apply_auto_scaling()
time_viewer.restore_user_scaling()

filename = str(path.join(tmpdir, "timeviewer_test.gif"))

def _check_result():
assert path.isfile(filename)

dialog = time_viewer.save_movie(
tmin=0.1,
tmax=0.15,
)
dialog.selectFile(filename)
dialog.accepted.connect(_check_result)
dialog.accept()


@testing.requires_testing_data
@pytest.mark.parametrize('hemi', ['lh', 'rh', 'split', 'both'])
Expand Down
4 changes: 2 additions & 2 deletions mne/viz/backends/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,14 @@ def set_3d_backend(backend_name, verbose=None):
+--------------------------------------+--------+---------+
| Subplotting | ✓ | ✓ |
+--------------------------------------+--------+---------+
| Save offline movie | ✓ | ✓ |
+--------------------------------------+--------+---------+
| Point picking | | ✓ |
+--------------------------------------+--------+---------+
| Linked cameras | | |
+--------------------------------------+--------+---------+
| Eye-dome lighting | | |
+--------------------------------------+--------+---------+
| Exports to movie/GIF | | |
+--------------------------------------+--------+---------+
"""
global MNE_3D_BACKEND
try:
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,4 @@ xlrd
pydocstyle
flake8
https://api.github.com/repos/mcmtroffaes/sphinxcontrib-bibtex/zipball/29694f215b39d64a31b845aafd9ff2ae9329494f
imageio>=2.6.1
pyvista>=0.23.1
4 changes: 1 addition & 3 deletions tutorials/source-modeling/plot_visualize_stc.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@
###############################################################################
#
# Note that here we used ``initial_time=0.1``, but we can also browse through
# time using ``time_viewer=True``. It's also possible to produce a movie by
# selecting in the menu: ``File > Save movie`` or alternatively with:
# brain.save_movie("movie.mp4")
# time using ``time_viewer=True``.
#
# In case ``mayavi`` is not available, we also offer a ``matplotlib``
# backend. Here we use verbose='error' to ignore a warning that not all
Expand Down

0 comments on commit 95ad0ee

Please sign in to comment.