Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Changelog

- Add function :func:`mne.preprocessing.annotate_muscle_zscore` to annotate periods with muscle artifacts. by `Adonay Nunes`_

- Support for saving movies of source time courses (STCs) with ``brain.save_movie`` method by `Guillaume Favelier`_
- Support for saving movies of source time courses (STCs) with ``brain.save_movie`` method and from graphical user interface by `Guillaume Favelier`_

Bug
~~~
Expand Down
1 change: 1 addition & 0 deletions mne.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@
<file alias="restore.svg">mne/images/restore-black-18dp.svg</file>
<file alias="clear.svg">mne/images/clear-black-18dp.svg</file>
<file alias="screenshot.svg">mne/images/screenshot-black-18dp.svg</file>
<file alias="movie.svg">mne/images/movie-black-18dp.svg</file>
</qresource>
</RCC>
1 change: 1 addition & 0 deletions mne/images/movie-black-18dp.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
98 changes: 95 additions & 3 deletions mne/viz/_brain/_timeviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
# License: Simplified BSD

import warnings
from functools import partial
import os
import time
import traceback
import sys

import numpy as np

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

# all icons are stored in mne/viz/_brain/resources.py, which must be
# automatically generated with:
Expand Down Expand Up @@ -356,6 +359,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
Expand Down Expand Up @@ -388,8 +393,11 @@ def __init__(self, brain, show_traces=False):
self.toggle_interface()
self.brain.show()

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

# update tool bar icon
if self.visibility:
Expand Down Expand Up @@ -419,6 +427,82 @@ def toggle_interface(self):

self.plotter.update()

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(
"Saving with ImageIO: %s"
% filename
)
self.status_msg.show()
self.status_progress.hide()
self.status_bar.layout().update()
else:
self.status_msg.setText(
"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("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 apply_auto_scaling(self):
self.brain.update_auto_scaling()
self.fmin_slider_rep.SetValue(self.brain._data['fmin'])
Expand Down Expand Up @@ -773,6 +857,7 @@ def load_icons(self):
self.icons["pause"] = QIcon(":/pause.svg")
self.icons["scale"] = QIcon(":/scale.svg")
self.icons["clear"] = QIcon(":/clear.svg")
self.icons["movie"] = QIcon(":/movie.svg")
self.icons["restore"] = QIcon(":/restore.svg")
self.icons["screenshot"] = QIcon(":/screenshot.svg")
self.icons["visibility_on"] = QIcon(":/visibility_on.svg")
Expand All @@ -784,6 +869,11 @@ def configure_tool_bar(self):
"Take a screenshot",
self.plotter._qt_screenshot
)
self.actions["movie"] = self.tool_bar.addAction(
self.icons["movie"],
"Save movie...",
self.save_movie
)
self.actions["visibility"] = self.tool_bar.addAction(
self.icons["visibility_on"],
"Toggle Visibility",
Expand Down Expand Up @@ -815,6 +905,7 @@ def configure_tool_bar(self):
self.help
)

self.actions["movie"].setShortcut("ctrl+shift+s")
self.actions["visibility"].setShortcut("i")
self.actions["play"].setShortcut(" ")
self.actions["scale"].setShortcut("s")
Expand Down Expand Up @@ -1036,6 +1127,7 @@ def clean(self):
self.plotter = None
self.main_menu = None
self.window = None
self.tool_bar = None
self.status_bar = None
self.interactor = None
if hasattr(self, "mpl_canvas"):
Expand Down
Loading