From fe70356a9e82ef082a69ba0b5795fd34f956cdb6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 17 Mar 2020 11:00:26 +0100 Subject: [PATCH 1/2] Revert "MRG: Add toggle to save_movie in _TimeViewer (#7257)" This reverts commit 7f6120bf020ec39dfb58a5f172f5a9c1984093cd. --- README.rst | 1 - doc/changes/latest.inc | 2 - environment.yml | 1 - mne/utils/docs.py | 6 - mne/viz/_brain/_brain.py | 240 ++++-------------- mne/viz/_brain/_timeviewer.py | 150 +---------- mne/viz/_brain/tests/test_brain.py | 28 +- mne/viz/backends/renderer.py | 4 +- requirements.txt | 1 - .../source-modeling/plot_visualize_stc.py | 4 +- 10 files changed, 56 insertions(+), 381 deletions(-) diff --git a/README.rst b/README.rst index c9b15bd4187..f1d8850df16 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/doc/changes/latest.inc b/doc/changes/latest.inc index 24df8a2fdc0..52ddb91ea92 100644 --- a/doc/changes/latest.inc +++ b/doc/changes/latest.inc @@ -135,8 +135,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 diff --git a/environment.yml b/environment.yml index ba3530056a8..2663395ad93 100644 --- a/environment.yml +++ b/environment.yml @@ -29,7 +29,6 @@ dependencies: - traits>=4.6.0 - pyface>=6 - traitsui>=6 -- imageio>=2.6.1 - vtk - pip: - mne diff --git a/mne/utils/docs.py b/mne/utils/docs.py index 289c5a9cb31..ec9481c44de 100644 --- a/mne/utils/docs.py +++ b/mne/utils/docs.py @@ -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'] = """ diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 53908d0b2a1..44596a4d962 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -16,7 +16,7 @@ from .view import lh_views_dict, rh_views_dict, View from .surface import Surface from .utils import mesh_edges, smoothing_matrix -from ..utils import _check_option, logger, verbose, fill_doc +from ..utils import _check_option, logger, verbose class _Brain(object): @@ -101,13 +101,13 @@ class _Brain(object): +---------------------------+--------------+-----------------------+ | 3D function: | surfer.Brain | mne.viz._brain._Brain | +===========================+==============+=======================+ - | add_data | ✓ | ✓ | + | add_data | ✓ | - | +---------------------------+--------------+-----------------------+ - | add_foci | ✓ | ✓ | + | add_foci | ✓ | - | +---------------------------+--------------+-----------------------+ - | add_label | ✓ | ✓ | + | add_label | ✓ | - | +---------------------------+--------------+-----------------------+ - | add_text | ✓ | ✓ | + | add_text | ✓ | - | +---------------------------+--------------+-----------------------+ | close | ✓ | ✓ | +---------------------------+--------------+-----------------------+ @@ -127,11 +127,9 @@ class _Brain(object): +---------------------------+--------------+-----------------------+ | save_image | ✓ | ✓ | +---------------------------+--------------+-----------------------+ - | save_movie | ✓ | ✓ | - +---------------------------+--------------+-----------------------+ | screenshot | ✓ | ✓ | +---------------------------+--------------+-----------------------+ - | show_view | ✓ | ✓ | + | show_view | ✓ | - | +---------------------------+--------------+-----------------------+ | TimeViewer | ✓ | ✓ | +---------------------------+--------------+-----------------------+ @@ -181,7 +179,7 @@ def __init__(self, subject_id, hemi, surf, title=None, self._subject_id = subject_id self._subjects_dir = subjects_dir self._views = views - self._times = None + self._n_times = None # for now only one color bar can be added # since it is the same for all figures self._colorbar_added = False @@ -191,7 +189,8 @@ def __init__(self, subject_id, hemi, surf, title=None, # array of data used by TimeViewer self._data = {} self.geo, self._hemi_meshes, self._overlays = {}, {}, {} - self.set_time_interpolation('nearest') + # can by anything scipy.interpolate.interp1d accepts + self.interp_kind = 'linear' # load geometry for one or both hemispheres as necessary offset = None if (not offset or hemi != 'both') else 0.0 @@ -354,6 +353,7 @@ def add_data(self, array, fmin=None, fmid=None, fmax=None, hemi = self._check_hemi(hemi) array = np.asarray(array) + self._data['array'] = array # Create time array and add label if > 1D if array.ndim <= 1: @@ -371,6 +371,7 @@ def add_data(self, array, fmin=None, fmid=None, fmax=None, self._data["time"] = time if self._n_times is None: + self._n_times = len(time) self._times = time elif len(time) != self._n_times: raise ValueError("New n_times is different from previous " @@ -411,7 +412,6 @@ def time_label(x): self._data[hemi]['mesh'] = list() self._data[hemi]['array'] = array self._data[hemi]['vertices'] = vertices - self.set_time_interpolation(self.time_interpolation) self._data['alpha'] = alpha self._data['colormap'] = colormap @@ -819,71 +819,39 @@ def set_data_smoothing(self, n_steps): self._data[hemi]['smooth_mat'] = smooth_mat self.set_time_point(self._data['time_idx']) - @property - def _n_times(self): - return len(self._times) if self._times is not None else None - - @property - def time_interpolation(self): - """The interpolation mode.""" - return self._time_interpolation - - @fill_doc - def set_time_interpolation(self, interpolation): - """Set the interpolation mode. - - Parameters - ---------- - %(brain_time_interpolation)s - """ - _check_option('interpolation', interpolation, - ('linear', 'nearest', 'zero', 'slinear', 'quadratic', - 'cubic')) - self._time_interpolation = str(interpolation) - del interpolation - self._time_interp_funcs = dict() - self._time_interp_inv = None - if self._times is not None: - idx = np.arange(self._n_times) - for hemi in ['lh', 'rh']: - hemi_data = self._data.get(hemi) - if hemi_data is not None: - array = hemi_data['array'] - self._time_interp_funcs[hemi] = _safe_interp1d( - idx, array, self._time_interpolation, axis=1, - assume_sorted=True) - self._time_interp_inv = _safe_interp1d(idx, self._times) - - def _interpolate_data(self, hemi, time_idx): - act_data = self._time_interp_funcs[hemi](time_idx) - act_time = self._time_interp_inv(time_idx) + def _interpolate_data(self, array, time_idx): + from scipy.interpolate import interp1d + times = np.arange(self._n_times) + act_data = interp1d( + times, array, self.interp_kind, axis=1, + assume_sorted=True)(time_idx) + ifunc = interp1d(times, self._data['time']) + act_time = ifunc(time_idx) return act_data, act_time def set_time_point(self, time_idx): - """Set the time point shown (can be a float to interpolate).""" + """Set the time point shown.""" from ..backends._pyvista import _set_mesh_scalars - current_act_data = list() - time_actor = self._data.get('time_actor', None) - time_label = self._data.get('time_label', None) + time = self._data['time'] for hemi in ['lh', 'rh']: hemi_data = self._data.get(hemi) if hemi_data is not None: array = hemi_data['array'] for mesh in hemi_data['mesh']: - # interpolate in time + # interpolation if array.ndim == 2: - act_data = self._time_interp_funcs[hemi](time_idx) - self._current_time = self._time_interp_inv(time_idx) - current_act_data.append(act_data) - if time_actor is not None and time_label is not None: - time_actor.SetInput(time_label(self._current_time)) + if isinstance(time_idx, int): + act_data = array[:, time_idx] + self._current_time = time[time_idx] + else: + act_data, act_time = self._interpolate_data( + array, time_idx) + self._current_time = act_time - # interpolate in space smooth_mat = hemi_data['smooth_mat'] if smooth_mat is not None: act_data = smooth_mat.dot(act_data) _set_mesh_scalars(mesh, act_data, 'Data') - self._current_act_data = np.concatenate(current_act_data) self._data['time_idx'] = time_idx def update_fmax(self, fmax): @@ -975,6 +943,9 @@ def update_fscale(self, fscale): def update_auto_scaling(self, restore=False): from ..backends._pyvista import _set_colormap_range + from scipy.interpolate import interp1d + # XXX this should be refactored with set_time_point so that interp1d + # is only used in one place... user_clim = self._data['clim'] if user_clim is not None and 'lims' in user_clim: allow_pos_lims = False @@ -986,9 +957,17 @@ def update_auto_scaling(self, restore=False): clim = 'auto' colormap = self._data['colormap'] transparent = self._data['transparent'] - mapdata = _process_clim( - clim, colormap, transparent, self._current_act_data, - allow_pos_lims) + time_idx = self._data['time_idx'] + array = self._data['array'] + if isinstance(time_idx, int): + act_data = array[:, time_idx] + else: + times = np.arange(self._n_times) + act_data = interp1d( + times, array, self.interp_kind, axis=1, + assume_sorted=True)(time_idx) + mapdata = _process_clim(clim, colormap, transparent, act_data, + allow_pos_lims) diverging = 'pos_lims' in mapdata['clim'] colormap = mapdata['colormap'] scale_pts = mapdata['clim']['pos_lims' if diverging else 'lims'] @@ -1015,99 +994,6 @@ def update_auto_scaling(self, restore=False): _set_colormap_range(actor, ctable, scalar_bar, rng) self._data['ctable'] = ctable - def save_movie(self, filename, time_dilation=4., tmin=None, tmax=None, - framerate=24, interpolation=None, codec=None, - bitrate=None, callback=None, **kwargs): - """Save a movie (for data with a time axis). - - The movie is created through the :mod:`imageio` module. The format is - determined by the extension, and additional options can be specified - through keyword arguments that depend on the format. For available - formats and corresponding parameters see the imageio documentation: - http://imageio.readthedocs.io/en/latest/formats.html#multiple-images - - .. Warning:: - This method assumes that time is specified in seconds when adding - data. If time is specified in milliseconds this will result in - movies 1000 times longer than expected. - - Parameters - ---------- - filename : str - Path at which to save the movie. The extension determines the - format (e.g., `'*.mov'`, `'*.gif'`, ...; see the :mod:`imageio` - documenttion for available formats). - time_dilation : float - Factor by which to stretch time (default 4). For example, an epoch - from -100 to 600 ms lasts 700 ms. With ``time_dilation=4`` this - would result in a 2.8 s long movie. - tmin : float - First time point to include (default: all data). - tmax : float - Last time point to include (default: all data). - framerate : float - Framerate of the movie (frames per second, default 24). - %(brain_time_interpolation)s - If None, it uses the current ``brain.interpolation``, - which defaults to ``'nearest'``. Defaults to None. - callback : callable | None - A function to call on each iteration. Useful for status message - updates. It will be passed keyword arguments ``frame`` and - ``n_frames``. - **kwargs : - Specify additional options for :mod:`imageio`. - """ - import imageio - from math import floor - - # find imageio FFMPEG parameters - if 'fps' not in kwargs: - kwargs['fps'] = framerate - if codec is not None: - kwargs['codec'] = codec - if bitrate is not None: - kwargs['bitrate'] = bitrate - - # find tmin - if tmin is None: - tmin = self._times[0] - elif tmin < self._times[0]: - raise ValueError("tmin=%r is smaller than the first time point " - "(%r)" % (tmin, self._times[0])) - - # find indexes at which to create frames - if tmax is None: - tmax = self._times[-1] - elif tmax > self._times[-1]: - raise ValueError("tmax=%r is greater than the latest time point " - "(%r)" % (tmax, self._times[-1])) - n_frames = floor((tmax - tmin) * time_dilation * framerate) - times = np.arange(n_frames, dtype=float) - times /= framerate * time_dilation - times += tmin - time_idx = np.interp(times, self._times, np.arange(self._n_times)) - - n_times = len(time_idx) - if n_times == 0: - raise ValueError("No time points selected") - - logger.debug("Save movie for time points/samples\n%s\n%s" - % (times, time_idx)) - # Sometimes the first screenshot is rendered with a different - # resolution on OS X - self.screenshot() - old_mode = self.time_interpolation - if interpolation is not None: - self.set_time_interpolation(interpolation) - try: - images = [ - self.screenshot() for _ in self._iter_time(time_idx, callback)] - finally: - self.set_time_interpolation(old_mode) - if callback is not None: - callback(frame=len(time_idx), n_frames=len(time_idx)) - imageio.mimwrite(filename, images, **kwargs) - def _to_time_index(self, value): """Return the interpolated time index of the given time value.""" time = self._data['time'] @@ -1148,46 +1034,6 @@ def _check_hemi(self, hemi): extra + ", got " + str(hemi)) return hemi - def _iter_time(self, time_idx, callback): - """Iterate through time points, then reset to current time. - - Parameters - ---------- - time_idx : array_like - Time point indexes through which to iterate. - callback : callable | None - Callback to call before yielding each frame. - - Yields - ------ - idx : int | float - Current index. - - Notes - ----- - Used by movie and image sequence saving functions. - """ - current_time_idx = self._data["time_idx"] - for ii, idx in enumerate(time_idx): - self.set_time_point(idx) - if callback is not None: - callback(frame=ii, n_frames=len(time_idx)) - yield idx - - # Restore original time index - self.set_time_point(current_time_idx) - - -def _safe_interp1d(x, y, kind='linear', axis=-1, assume_sorted=False): - """Work around interp1d not liking singleton dimensions.""" - from scipy.interpolate import interp1d - if y.shape[axis] == 1: - def func(x): - return y.copy() - return func - else: - return interp1d(x, y, kind, axis=axis, assume_sorted=assume_sorted) - def _update_limits(fmin, fmid, fmax, center, array): if center is None: diff --git a/mne/viz/_brain/_timeviewer.py b/mne/viz/_brain/_timeviewer.py index 4bbf0702d05..342a0a760b3 100644 --- a/mne/viz/_brain/_timeviewer.py +++ b/mne/viz/_brain/_timeviewer.py @@ -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 @@ -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 @@ -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: @@ -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): @@ -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): @@ -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() @@ -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 @@ -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) @@ -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 diff --git a/mne/viz/_brain/tests/test_brain.py b/mne/viz/_brain/tests/test_brain.py index a960caf1985..a11c2e3df31 100644 --- a/mne/viz/_brain/tests/test_brain.py +++ b/mne/viz/_brain/tests/test_brain.py @@ -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') @@ -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']) diff --git a/mne/viz/backends/renderer.py b/mne/viz/backends/renderer.py index 9cdfb045e20..ccafbda6d65 100644 --- a/mne/viz/backends/renderer.py +++ b/mne/viz/backends/renderer.py @@ -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: diff --git a/requirements.txt b/requirements.txt index 8b5a63d264f..9e1d0308764 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 diff --git a/tutorials/source-modeling/plot_visualize_stc.py b/tutorials/source-modeling/plot_visualize_stc.py index 9419e52046c..b51c55d4ba3 100644 --- a/tutorials/source-modeling/plot_visualize_stc.py +++ b/tutorials/source-modeling/plot_visualize_stc.py @@ -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 From 17dd2655fa1ec3eef04a40ab82b3948542e8f8ad Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 17 Mar 2020 12:18:53 +0100 Subject: [PATCH 2/2] Revert _interpolate_data signature --- mne/viz/_brain/_brain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 44596a4d962..87eb34e6bf1 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -422,7 +422,7 @@ def time_label(x): if time is not None and len(array.shape) == 2: # we have scalar_data with time dimension - act_data, act_time = self._interpolate_data(hemi, time_idx) + act_data, act_time = self._interpolate_data(array, time_idx) self._current_time = act_time else: # we have scalar data without time