Skip to content

Commit

Permalink
MRG, ENH: Add flatmap (#8082)
Browse files Browse the repository at this point in the history
* WIP: Add flatmap [skip travis]

* FIX: Add tests

* FIX: Check

* ENH: Add options for add_data

* FIX: Test

* FIX: Try again

* FIX: Maybe?

* BUG: Fix window sizes

* ENH: Example [skip travis]
  • Loading branch information
larsoner authored Aug 10, 2020
1 parent 9c7788a commit 64e3dd0
Show file tree
Hide file tree
Showing 15 changed files with 326 additions and 116 deletions.
4 changes: 2 additions & 2 deletions mne/datasets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True,
path = _get_path(path, key, name)
# To update the testing or misc dataset, push commits, then make a new
# release on GitHub. Then update the "releases" variable:
releases = dict(testing='0.97', misc='0.6')
releases = dict(testing='0.98', misc='0.6')
# And also update the "md5_hashes['testing']" variable below.

# To update any other dataset, update the data archive itself (upload
Expand Down Expand Up @@ -327,7 +327,7 @@ def _data_path(path=None, force_update=False, update_path=True, download=True,
sample='12b75d1cb7df9dfb4ad73ed82f61094f',
somato='ea825966c0a1e9b2f84e3826c5500161',
spm='9f43f67150e3b694b523a21eb929ea75',
testing='603c3f087c4dbf151c729341342095c7',
testing='7c1dcfacaac7759aa40bfb800c791d85',
multimodal='26ec847ae9ab80f58f204d09e2c08367',
fnirs_motor='c4935d19ddab35422a69f3326a01fef8',
opm='370ad1dcfd5c47e029e692c85358a374',
Expand Down
25 changes: 15 additions & 10 deletions mne/source_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,12 +615,12 @@ def plot(self, subject=None, surface='inflated', hemi='lh',
colormap='auto', time_label='auto', smoothing_steps=10,
transparent=True, alpha=1.0, time_viewer='auto',
subjects_dir=None,
figure=None, views='lat', colorbar=True, clim='auto',
figure=None, views='auto', colorbar=True, clim='auto',
cortex="classic", size=800, background="black",
foreground=None, initial_time=None, time_unit='s',
backend='auto', spacing='oct6', title=None, show_traces='auto',
src=None, volume_options=1., view_layout='vertical',
verbose=None):
add_data_kwargs=None, verbose=None):
brain = plot_source_estimates(
self, subject, surface=surface, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
Expand All @@ -631,7 +631,7 @@ def plot(self, subject=None, surface='inflated', hemi='lh',
initial_time=initial_time, time_unit=time_unit, backend=backend,
spacing=spacing, title=title, show_traces=show_traces,
src=src, volume_options=volume_options, view_layout=view_layout,
verbose=verbose)
add_data_kwargs=add_data_kwargs, verbose=verbose)
return brain

@property
Expand Down Expand Up @@ -1904,11 +1904,13 @@ def project(self, directions, src=None, use_cps=True):
def plot(self, subject=None, hemi='lh', colormap='hot', time_label='auto',
smoothing_steps=10, transparent=True, brain_alpha=0.4,
overlay_alpha=None, vector_alpha=1.0, scale_factor=None,
time_viewer='auto', subjects_dir=None, figure=None, views='lat',
time_viewer='auto', subjects_dir=None, figure=None,
views='lateral',
colorbar=True, clim='auto', cortex='classic', size=800,
background='black', foreground=None, initial_time=None,
time_unit='s', show_traces='auto', src=None, volume_options=1.,
view_layout='vertical', verbose=None): # noqa: D102
view_layout='vertical', add_data_kwargs=None,
verbose=None): # noqa: D102
return plot_vector_source_estimates(
self, subject=subject, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
Expand All @@ -1920,7 +1922,8 @@ def plot(self, subject=None, hemi='lh', colormap='hot', time_label='auto',
background=background, foreground=foreground,
initial_time=initial_time, time_unit=time_unit,
show_traces=show_traces, src=src, volume_options=volume_options,
view_layout=view_layout, verbose=verbose)
view_layout=view_layout, add_data_kwargs=add_data_kwargs,
verbose=verbose)


class _BaseVolSourceEstimate(_BaseSourceEstimate):
Expand All @@ -1938,7 +1941,7 @@ def plot_3d(self, subject=None, surface='white', hemi='both',
foreground=None, initial_time=None, time_unit='s',
backend='auto', spacing='oct6', title=None, show_traces='auto',
src=None, volume_options=1., view_layout='vertical',
verbose=None):
add_data_kwargs=None, verbose=None):
return super().plot(
subject=subject, surface=surface, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
Expand All @@ -1949,7 +1952,8 @@ def plot_3d(self, subject=None, surface='white', hemi='both',
foreground=foreground, initial_time=initial_time,
time_unit=time_unit, backend=backend, spacing=spacing, title=title,
show_traces=show_traces, src=src, volume_options=volume_options,
view_layout=view_layout, verbose=verbose)
view_layout=view_layout, add_data_kwargs=add_data_kwargs,
verbose=verbose)

@copy_function_doc_to_method_doc(plot_volume_source_estimates)
def plot(self, src, subject=None, subjects_dir=None, mode='stat_map',
Expand Down Expand Up @@ -2267,7 +2271,7 @@ def plot_3d(self, subject=None, hemi='both', colormap='hot',
background='black', foreground=None, initial_time=None,
time_unit='s', show_traces='auto', src=None,
volume_options=1., view_layout='vertical',
verbose=None): # noqa: D102
add_data_kwargs=None, verbose=None): # noqa: D102
return _BaseVectorSourceEstimate.plot(
self, subject=subject, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
Expand All @@ -2279,7 +2283,8 @@ def plot_3d(self, subject=None, hemi='both', colormap='hot',
background=background, foreground=foreground,
initial_time=initial_time, time_unit=time_unit,
show_traces=show_traces, src=src, volume_options=volume_options,
view_layout=view_layout, verbose=verbose)
view_layout=view_layout, add_data_kwargs=add_data_kwargs,
verbose=verbose)


@fill_doc
Expand Down
47 changes: 47 additions & 0 deletions mne/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,53 @@ def _read_wavefront_obj(fname):
return np.array(coords), np.array(faces)


def _read_patch(fname):
"""Load a FreeSurfer binary patch file.
Parameters
----------
fname : str
The filename.
Returns
-------
rrs : ndarray, shape (n_vertices, 3)
The points.
tris : ndarray, shape (n_tris, 3)
The patches. Not all vertices will be present.
"""
# This is adapted from PySurfer PR #269, Bruce Fischl's read_patch.m,
# and PyCortex (BSD)
patch = dict()
with open(fname, 'r') as fid:
ver = np.fromfile(fid, dtype='>i4', count=1)[0]
if ver != -1:
raise RuntimeError(f'incorrect version # {ver} (not -1) found')
npts = np.fromfile(fid, dtype='>i4', count=1)[0]
dtype = np.dtype(
[('vertno', '>i4'), ('x', '>f'), ('y', '>f'), ('z', '>f')])
recs = np.fromfile(fid, dtype=dtype, count=npts)
# numpy to dict
patch = {key: recs[key] for key in dtype.fields.keys()}
patch['vertno'] -= 1

# read surrogate surface
rrs, tris = read_surface(
op.join(op.dirname(fname), op.basename(fname)[:3] + 'sphere'))
orig_tris = tris
is_vert = patch['vertno'] > 0 # negative are edges, ignored for now
verts = patch['vertno'][is_vert]

# eliminate invalid tris and zero out unused rrs
mask = np.zeros((len(rrs),), dtype=bool)
mask[verts] = True
rrs[~mask] = 0.
tris = tris[mask[tris].all(1)]
for ii, key in enumerate(['x', 'y', 'z']):
rrs[verts, ii] = patch[key][is_vert]
return rrs, tris, orig_tris


##############################################################################
# SURFACE CREATION

Expand Down
8 changes: 7 additions & 1 deletion mne/tests/test_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
dig_mri_distances)
from mne.surface import (read_morph_map, _compute_nearest, _tessellate_sphere,
fast_cross_3d, get_head_surf, read_curvature,
get_meg_helmet_surf, _normal_orth)
get_meg_helmet_surf, _normal_orth, _read_patch)
from mne.utils import (_TempDir, requires_vtk, catch_logging,
run_tests_if_main, object_diff, requires_freesurfer)
from mne.io import read_info
Expand Down Expand Up @@ -183,6 +183,12 @@ def test_io_surface():
assert_array_equal(pts, c_pts)
assert_array_equal(tri, c_tri)

# reading patches (just a smoke test, let the flatmap viz tests be more
# complete)
fname_patch = op.join(
data_path, 'subjects', 'fsaverage', 'surf', 'rh.cortex.patch.flat')
_read_patch(fname_patch)


@testing.requires_testing_data
def test_read_curv():
Expand Down
15 changes: 15 additions & 0 deletions mne/utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1076,6 +1076,21 @@
Can be "vertical" (default) or "horizontal". When using "horizontal" mode,
the PyVista backend must be used and hemi cannot be "split".
"""
docdict['add_data_kwargs'] = """
add_data_kwargs : dict | None
Additional arguments to brain.add_data (e.g.,
``dict(time_label_size=10)``).
"""
docdict['views'] = """
views : str | list
View to use. Can be any of::
['lateral', 'medial', 'rostral', 'caudal', 'dorsal', 'ventral',
'frontal', 'parietal', 'axial', 'sagittal', 'coronal']
Three letter abbreviations (e.g., ``'lat'``) are also supported.
Using multiple views (list) is not supported for mpl backend.
"""

# STC label time course
docdict['eltc_labels'] = """
Expand Down
72 changes: 58 additions & 14 deletions mne/viz/_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -1426,6 +1426,7 @@ def _plot_mpl_stc(stc, subject=None, surface='inflated', hemi='lh',
'par': {'elev': 30, 'azim': -60}}
time_viewer = False if time_viewer == 'auto' else time_viewer
kwargs = dict(lh=lh_kwargs, rh=rh_kwargs)
views = 'lat' if views == 'auto' else views
_check_option('views', views, sorted(lh_kwargs.keys()))
mapdata = _process_clim(clim, colormap, transparent, stc.data)
_separate_map(mapdata)
Expand Down Expand Up @@ -1582,13 +1583,13 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
colormap='auto', time_label='auto',
smoothing_steps=10, transparent=True, alpha=1.0,
time_viewer='auto', subjects_dir=None, figure=None,
views='lat', colorbar=True, clim='auto',
views='auto', colorbar=True, clim='auto',
cortex="classic", size=800, background="black",
foreground=None, initial_time=None,
time_unit='s', backend='auto', spacing='oct6',
title=None, show_traces='auto',
src=None, volume_options=1., view_layout='vertical',
verbose=None):
add_data_kwargs=None, verbose=None):
"""Plot SourceEstimate.
Parameters
Expand Down Expand Up @@ -1629,10 +1630,14 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
length. If int is provided it will be used to identify the Mayavi
figure by it's id or create a new figure with the given id. If an
instance of matplotlib figure, mpl backend is used for plotting.
views : str | list
View to use. See `surfer.Brain`. Supported views: ['lat', 'med', 'ros',
'cau', 'dor' 'ven', 'fro', 'par']. Using multiple views is not
supported for mpl backend.
%(views)s
When plotting a standard SourceEstimate (not volume, mixed, or vector)
and using the PyVista backend, ``views='flat'`` is also supported to
plot cortex as a flatmap.
.. versionchanged:: 0.21.0
Support for flatmaps.
colorbar : bool
If True, display colorbar on scene.
%(clim)s
Expand Down Expand Up @@ -1676,13 +1681,25 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
.. versionadded:: 0.17.0
%(show_traces)s
%(src_volume_options_layout)s
%(add_data_kwargs)s
%(verbose)s
Returns
-------
figure : instance of surfer.Brain | matplotlib.figure.Figure
An instance of :class:`surfer.Brain` from PySurfer or
matplotlib figure.
Notes
-----
Flatmaps are available by default for ``fsaverage`` but not for other
subjects reconstructed by FreeSurfer. We recommend using
:func:`mne.compute_source_morph` to morph source estimates to ``fsaverage``
for flatmap plotting. If you want to construct your own flatmap for a given
subject, these links might help:
- https://surfer.nmr.mgh.harvard.edu/fswiki/FreeSurferOccipitalFlattenedPatch
- https://openwetware.org/wiki/Beauchamp:FreeSurfer
""" # noqa: E501
from .backends.renderer import _get_3d_backend, set_3d_backend
from ..source_estimate import _BaseSourceEstimate
Expand Down Expand Up @@ -1716,15 +1733,15 @@ def plot_source_estimates(stc, subject=None, surface='inflated', hemi='lh',
stc, overlay_alpha=alpha, brain_alpha=alpha, vector_alpha=alpha,
cortex=cortex, foreground=foreground, size=size, scale_factor=None,
show_traces=show_traces, src=src, volume_options=volume_options,
view_layout=view_layout, **kwargs)
view_layout=view_layout, add_data_kwargs=add_data_kwargs, **kwargs)


def _plot_stc(stc, subject, surface, hemi, colormap, time_label,
smoothing_steps, subjects_dir, views, clim, figure, initial_time,
time_unit, background, time_viewer, colorbar, transparent,
brain_alpha, overlay_alpha, vector_alpha, cortex, foreground,
size, scale_factor, show_traces, src, volume_options,
view_layout):
view_layout, add_data_kwargs):
from .backends.renderer import _get_3d_backend
vec = stc._data_ndim == 3
subjects_dir = get_subjects_dir(subjects_dir=subjects_dir,
Expand All @@ -1739,7 +1756,7 @@ def _plot_stc(stc, subject, surface, hemi, colormap, time_label,
_require_version('surfer', 'stc.plot', '0.9')
else: # PyVista
from ._brain import _Brain as Brain

views = _check_views(surface, views, hemi, stc, backend)
_check_option('hemi', hemi, ['lh', 'rh', 'split', 'both'])
_check_option('view_layout', view_layout, ('vertical', 'horizontal'))
time_label, times = _handle_time(time_label, time_unit, stc.times)
Expand Down Expand Up @@ -1842,6 +1859,7 @@ def _plot_stc(stc, subject, surface, hemi, colormap, time_label,
kwargs["clim"] = clim
kwargs["volume_options"] = volume_options
kwargs["src"] = src_vol
kwargs.update({} if add_data_kwargs is None else add_data_kwargs)
with warnings.catch_warnings(record=True): # traits warnings
brain.add_data(**kwargs)
brain.scale_data_colormap(fmin=scale_pts[0], fmid=scale_pts[1],
Expand Down Expand Up @@ -2346,19 +2364,44 @@ def _check_pysurfer_antialias(Brain):
return kwargs


def _check_views(surf, views, hemi, stc=None, backend=None):
from ..source_estimate import SourceEstimate
_validate_type(views, (list, tuple, str), 'views')
views = [views] if isinstance(views, str) else list(views)
if surf == 'flat':
_check_option('views', views, (['auto'], ['flat']))
views = ['flat']
elif len(views) == 1 and views[0] == 'auto':
views = ['lateral']
if views == ['flat']:
if stc is not None:
_validate_type(stc, SourceEstimate, 'stc',
'SourceEstimate when a flatmap is used')
if backend is not None:
if backend != 'pyvista':
raise RuntimeError('The PyVista 3D backend must be used to '
'plot a flatmap')
if (views == ['flat']) ^ (surf == 'flat'): # exactly only one of the two
raise ValueError('surface="flat" must be used with views="flat", got '
f'surface={repr(surf)} and views={repr(views)}')
return views


@verbose
def plot_vector_source_estimates(stc, subject=None, hemi='lh', colormap='hot',
time_label='auto', smoothing_steps=10,
transparent=None, brain_alpha=0.4,
overlay_alpha=None, vector_alpha=1.0,
scale_factor=None, time_viewer='auto',
subjects_dir=None, figure=None, views='lat',
subjects_dir=None, figure=None,
views='lateral',
colorbar=True, clim='auto', cortex='classic',
size=800, background='black',
foreground=None, initial_time=None,
time_unit='s', show_traces='auto',
src=None, volume_options=1.,
view_layout='vertical', verbose=None):
view_layout='vertical',
add_data_kwargs=None, verbose=None):
"""Plot VectorSourceEstimate with PySurfer.
A "glass brain" is drawn and all dipoles defined in the source estimate
Expand Down Expand Up @@ -2406,8 +2449,7 @@ def plot_vector_source_estimates(stc, subject=None, hemi='lh', colormap='hot',
split view is requested, this must be a list of the appropriate
length. If int is provided it will be used to identify the Mayavi
figure by it's id or create a new figure with the given id.
views : str | list
View to use. See `surfer.Brain`.
%(views)s
colorbar : bool
If True, display colorbar on scene.
%(clim_onesided)s
Expand All @@ -2433,6 +2475,7 @@ def plot_vector_source_estimates(stc, subject=None, hemi='lh', colormap='hot',
milliseconds ("ms").
%(show_traces)s
%(src_volume_options_layout)s
%(add_data_kwargs)s
%(verbose)s
Returns
Expand All @@ -2459,7 +2502,8 @@ def plot_vector_source_estimates(stc, subject=None, hemi='lh', colormap='hot',
brain_alpha=brain_alpha, overlay_alpha=overlay_alpha,
vector_alpha=vector_alpha, cortex=cortex, foreground=foreground,
size=size, scale_factor=scale_factor, show_traces=show_traces,
src=src, volume_options=volume_options, view_layout=view_layout)
src=src, volume_options=volume_options, view_layout=view_layout,
add_data_kwargs=add_data_kwargs)


@verbose
Expand Down
Loading

0 comments on commit 64e3dd0

Please sign in to comment.