Skip to content

Commit

Permalink
ENH: Working [skip travis] [skip github]
Browse files Browse the repository at this point in the history
  • Loading branch information
larsoner authored and agramfort committed Sep 23, 2020
1 parent 7201cf8 commit 67427a4
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 20 deletions.
3 changes: 3 additions & 0 deletions doc/_static/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,9 @@ img.logo {
max-width: 360px;
width: 100%;
}
img.hidden {
visibility: hidden;
}
div.border-top {
border-top: 1px solid #ccc;
}
Expand Down
2 changes: 1 addition & 1 deletion doc/carousel.inc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<div class="carousel-inner">

<div class="item active">
<div class="chopper container" style="background-image: url(_images/sphx_glr_plot_mne_dspm_source_localization_008.png)" title="dSPM">
<div class="chopper container" style="background-image: url(_images/sphx_glr_plot_mne_dspm_source_localization_008.gif)" title="dSPM">
<div class="carousel-caption">
<h3>Source estimation</h3>
<p>Distributed, sparse, mixed-norm, beamformers, dipole fitting, and more.
Expand Down
5 changes: 5 additions & 0 deletions mne/tests/test_docstring_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ def test_docstring_parameters():
for submod in name.split('.')[1:]:
module = getattr(module, submod)
classes = inspect.getmembers(module, inspect.isclass)
# XXX eventually this should be public
# XXX this should be correct but it's not
# if name == 'mne.viz':
# from mne.viz._brain import _Brain
# classes.append(('Brain', _Brain))
for cname, cls in classes:
if cname.startswith('_'):
continue
Expand Down
28 changes: 20 additions & 8 deletions mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#
# License: Simplified BSD

from functools import partial
import os
import os.path as op

Expand Down Expand Up @@ -1515,7 +1516,7 @@ def hemis(self):
@fill_doc
def save_movie(self, filename, time_dilation=4., tmin=None, tmax=None,
framerate=24, interpolation=None, codec=None,
bitrate=None, callback=None, **kwargs):
bitrate=None, callback=None, time_viewer=False, **kwargs):
"""Save a movie (for data with a time axis).
The movie is created through the :mod:`imageio` module. The format is
Expand Down Expand Up @@ -1557,8 +1558,9 @@ def save_movie(self, filename, time_dilation=4., tmin=None, tmax=None,
Specify additional options for :mod:`imageio`.
"""
import imageio
from math import floor

images, kwargs = self._make_movie_frames(
time_dilation, tmin, tmax, framerate, interpolation, callback,
time_viewer)
# find imageio FFMPEG parameters
if 'fps' not in kwargs:
kwargs['fps'] = framerate
Expand All @@ -1567,6 +1569,12 @@ def save_movie(self, filename, time_dilation=4., tmin=None, tmax=None,
if bitrate is not None:
kwargs['bitrate'] = bitrate

imageio.mimwrite(filename, images)

def _make_movie_frames(self, time_dilation, tmin, tmax, framerate,
interpolation, callback, time_viewer):
from math import floor

# find tmin
if tmin is None:
tmin = self._times[0]
Expand Down Expand Up @@ -1601,14 +1609,14 @@ def save_movie(self, filename, time_dilation=4., tmin=None, tmax=None,
try:
images = [
self.screenshot(time_viewer=time_viewer)
for _ in self._iter_time(time_idx, callback)]
for _ in self._iter_time(time_idx, callback, time_viewer)]
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)
return images

def _iter_time(self, time_idx, callback):
def _iter_time(self, time_idx, callback, time_viewer=False):
"""Iterate through time points, then reset to current time.
Parameters
Expand All @@ -1617,6 +1625,8 @@ def _iter_time(self, time_idx, callback):
Time point indexes through which to iterate.
callback : callable | None
Callback to call before yielding each frame.
time_viewer : bool
If True, route through self.time_viewer.
Yields
------
Expand All @@ -1627,15 +1637,17 @@ def _iter_time(self, time_idx, callback):
-----
Used by movie and image sequence saving functions.
"""
func = partial(self.time_viewer.callbacks["time"], update_widget=True)
# func = self.set_time_point
current_time_idx = self._data["time_idx"]
for ii, idx in enumerate(time_idx):
self.set_time_point(idx)
func(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)
func(current_time_idx)

def _show(self):
"""Request rendering of the window."""
Expand Down
76 changes: 66 additions & 10 deletions mne/viz/_brain/_scraper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import os.path as op
from distutils.version import LooseVersion

from ._brain import _Brain

Expand All @@ -11,20 +11,76 @@ def __repr__(self):

def __call__(self, block, block_vars, gallery_conf):
rst = ''
for brain in block_vars['example_globals'].values():
for brain in list(block_vars['example_globals'].values()):
# Only need to process if it's a brain with a time_viewer
# with traces on and shown in the same window, otherwise
# PyVista and matplotlib scrapers can just do the work
if (not isinstance(brain, _Brain)) or brain._closed:
continue
from matplotlib.image import imsave
from sphinx_gallery.scrapers import figure_rst
img_fname = next(block_vars['image_path_iterator'])
import matplotlib
from matplotlib import animation, pyplot as plt
from sphinx_gallery.scrapers import matplotlib_scraper
img = brain.screenshot(time_viewer=True)
assert img.size > 0
imsave(img_fname, img)
assert op.isfile(img_fname)
rst += figure_rst(
[img_fname], gallery_conf['src_dir'], brain._title)
dpi = 100.
figsize = (img.shape[1] / dpi, img.shape[0] / dpi)
fig = plt.figure(figsize=figsize, dpi=dpi)
ax = plt.Axes(fig, [0, 0, 1, 1])
fig.add_axes(ax)
img = ax.imshow(img)
movie_key = '# brain.save_movie'
if movie_key in block[1]:
kwargs = dict()
# Parse our parameters
lines = block[1].splitlines()
for li, line in enumerate(block[1].splitlines()):
if line.startswith(movie_key):
line = line[len(movie_key):].replace('..., ', '')
for ni in range(1, 5): # should be enough
if len(lines) > li + ni and \
lines[li + ni].startswith('# '):
line = line + lines[li + ni][1:].strip()
else:
break
assert line.startswith('(') and line.endswith(')')
kwargs.update(eval(f'dict{line}'))
for key, default in [('time_dilation', 4),
('framerate', 24),
('tmin', None),
('tmax', None),
('interpolation', None),
('time_viewer', False)]:
if key not in kwargs:
kwargs[key] = default
frames = brain._make_movie_frames(callback=None, **kwargs)

# Turn them into an animation
def func(frame):
img.set_data(frame)
return [img]

anim = animation.FuncAnimation(
fig, func=func, frames=frames, blit=True,
interval=1000. / kwargs['framerate'])

# Out to sphinx-gallery:
#
# 1. A static image but hide it (useful for carousel)
if LooseVersion(matplotlib.__version__) >= \
LooseVersion('3.3.1') and \
animation.FFMpegWriter.isAvailable():
writer = 'ffmpeg'
elif animation.ImageMagickWriter.isAvailable():
writer = 'imagemagick'
else:
writer = None
static_fname = next(block_vars['image_path_iterator'])
static_fname = static_fname[:-4] + '.gif'
anim.save(static_fname, writer=writer, dpi=dpi)
rst += f'\n.. image:: /{static_fname}\n :class: hidden\n'

# 2. An animation that will be embedded and visible
block_vars['example_globals']['_brain_anim_'] = anim

brain.close()
rst += matplotlib_scraper(block, block_vars, gallery_conf)
return rst
6 changes: 5 additions & 1 deletion tutorials/source-modeling/plot_beamformer_lcmv.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,17 @@
# as small vectors in the visualization (in the 2D plotters, only the
# magnitude can be shown):

# sphinx_gallery_thumbnail_number = 7
# sphinx_gallery_thumbnail_number = 8

brain = stc_vec.plot_3d(
clim=dict(kind='value', lims=lims), hemi='both',
views=['coronal', 'sagittal', 'axial'], size=(800, 300),
view_layout='horizontal', show_traces=0.3, **kwargs)

# The movie on the MNE website can be generated with:
# brain.save_movie(..., interpolation='linear', time_dilation=20,
# framerate=10, time_viewer=True)

###############################################################################
# Visualize the activity of the maximum voxel with all three components
# ---------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
brain.add_text(0.1, 0.9, 'dSPM (plus location of maximal activation)', 'title',
font_size=14)

# The documentation website's movie is generated with:
# brain.save_movie(..., tmin=0.05, tmax=0.15, interpolation='linear',
# time_dilation=20, framerate=10, time_viewer=True)

###############################################################################
# There are many other ways to visualize and work with source data, see
# for example:
Expand Down

0 comments on commit 67427a4

Please sign in to comment.