Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MRG, ENH: Add flatmap #8082

Merged
merged 9 commits into from
Aug 10, 2020
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
4 changes: 2 additions & 2 deletions mne/datasets/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,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 @@ -326,7 +326,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 @@ -1058,6 +1058,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