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: Volume rendering #8064

Merged
merged 26 commits into from
Aug 5, 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
2 changes: 2 additions & 0 deletions doc/changes/latest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ Changelog

- Add :class:`mne.MixedVectorSourceEstimate` for vector source estimates for mixed source spaces, by `Eric Larson`_

- Add mixed and volumetric source estimate plotting using volumetric ray-casting to :meth:`mne.MixedSourceEstimate.plot` and :meth:`mne.VolSourceEstimate.plot_3d` by `Eric Larson`_

- Add :meth:`mne.MixedSourceEstimate.surface` and :meth:`mne.MixedSourceEstimate.volume` methods to allow surface and volume extraction by `Eric Larson`_

- Add :meth:`mne.VectorSourceEstimate.project` to project vector source estimates onto the direction of maximum source power by `Eric Larson`_
Expand Down
1 change: 1 addition & 0 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@ def reset_warnings(gallery_conf, fname):
'VolSourceEstimate': 'mne.VolSourceEstimate',
'VolVectorSourceEstimate': 'mne.VolVectorSourceEstimate',
'MixedSourceEstimate': 'mne.MixedSourceEstimate',
'MixedVectorSourceEstimate': 'mne.MixedVectorSourceEstimate',
'SourceEstimate': 'mne.SourceEstimate', 'Projection': 'mne.Projection',
'ConductorModel': 'mne.bem.ConductorModel',
'Dipole': 'mne.Dipole', 'DipoleFixed': 'mne.DipoleFixed',
Expand Down
14 changes: 12 additions & 2 deletions examples/inverse/plot_mixed_source_space_inverse.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,27 @@
pick_ori=None)
src = inverse_operator['src']

###############################################################################
# Plot the mixed source estimate
# ------------------------------

# sphinx_gallery_thumbnail_number = 3
initial_time = 0.1
stc_vec = apply_inverse(evoked, inverse_operator, lambda2, inv_method,
pick_ori='vector')
brain = stc_vec.plot(
hemi='both', src=inverse_operator['src'], views='coronal',
initial_time=initial_time, subjects_dir=subjects_dir)

###############################################################################
# Plot the surface
# ----------------
initial_time = 0.1
brain = stc.surface().plot(initial_time=initial_time,
subjects_dir=subjects_dir)
###############################################################################
# Plot the volume
# ----------------

# sphinx_gallery_thumbnail_number = 4
fig = stc.volume().plot(initial_time=initial_time, src=src,
subjects_dir=subjects_dir)

Expand Down
16 changes: 16 additions & 0 deletions mne/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def pytest_configure(config):
ignore:.*pandas\.util\.testing is deprecated.*:
ignore:.*tostring.*is deprecated.*:DeprecationWarning
ignore:.*QDesktopWidget\.availableGeometry.*:DeprecationWarning
ignore:Unable to enable faulthandler.*:UserWarning
always:.*get_data.* is deprecated in favor of.*:DeprecationWarning
always::ResourceWarning
""" # noqa: E501
Expand Down Expand Up @@ -320,6 +321,21 @@ def renderer_notebook():
yield


@pytest.fixture(scope='session')
def pixel_ratio():
"""Get the pixel ratio."""
from mne.viz.backends.tests._utils import (has_mayavi, has_pyvista,
has_pyqt5)
if not (has_mayavi() or has_pyvista()) or not has_pyqt5():
return 1.
from PyQt5.QtWidgets import QApplication, QMainWindow
_ = QApplication.instance() or QApplication([])
window = QMainWindow()
ratio = float(window.devicePixelRatio())
window.close()
return ratio


@pytest.fixture(scope='function', params=[testing._pytest_param()])
def subjects_dir_tmp(tmpdir):
"""Copy MNE-testing-data subjects_dir to a temp dir for manipulation."""
Expand Down
2 changes: 2 additions & 0 deletions mne/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
depth_sparse=dict(exp=0.8, limit=None, limit_depth_chs='whiten',
combine_xyz='fro', allow_fixed_depth=True),
interpolation_method=dict(eeg='spline', meg='MNE', fnirs='nearest'),
volume_options=dict(
alpha=None, resolution=1., surface_alpha=None, blending='mip'),
)


Expand Down
142 changes: 97 additions & 45 deletions mne/source_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,30 @@ def save(self, fname, ftype='h5', verbose=None):
src_type=self._src_type),
title='mnepython', overwrite=True)

@copy_function_doc_to_method_doc(plot_source_estimates)
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',
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):
brain = plot_source_estimates(
self, subject, surface=surface, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
transparent=transparent, alpha=alpha, time_viewer=time_viewer,
subjects_dir=subjects_dir, figure=figure, views=views,
colorbar=colorbar, clim=clim, cortex=cortex, size=size,
background=background, 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)
return brain

@property
def sfreq(self):
"""Sample rate of the data."""
Expand Down Expand Up @@ -1576,28 +1600,6 @@ def save(self, fname, ftype='stc', verbose=None):
super().save(fname)
logger.info('[done]')

@copy_function_doc_to_method_doc(plot_source_estimates)
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',
cortex="classic", size=800, background="black",
foreground=None, initial_time=None, time_unit='s',
backend='auto', spacing='oct6', title=None,
show_traces='auto', verbose=None):
brain = plot_source_estimates(
self, subject, surface=surface, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
transparent=transparent, alpha=alpha, time_viewer=time_viewer,
subjects_dir=subjects_dir, figure=figure, views=views,
colorbar=colorbar, clim=clim, cortex=cortex, size=size,
background=background, foreground=foreground,
initial_time=initial_time, time_unit=time_unit, backend=backend,
spacing=spacing, title=title, show_traces=show_traces,
verbose=verbose)
return brain

@verbose
def estimate_snr(self, info, fwd, cov, verbose=None):
r"""Compute time-varying SNR in the source space.
Expand Down Expand Up @@ -1899,12 +1901,57 @@ def project(self, directions, src=None, use_cps=True):
self.verbose)
return stc, directions

@copy_function_doc_to_method_doc(plot_vector_source_estimates)
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',
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
return plot_vector_source_estimates(
self, subject=subject, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
transparent=transparent, brain_alpha=brain_alpha,
overlay_alpha=overlay_alpha, vector_alpha=vector_alpha,
scale_factor=scale_factor, time_viewer=time_viewer,
subjects_dir=subjects_dir, figure=figure, views=views,
colorbar=colorbar, clim=clim, cortex=cortex, size=size,
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)


class _BaseVolSourceEstimate(_BaseSourceEstimate):

_src_type = 'volume'
_src_count = None

@copy_function_doc_to_method_doc(plot_source_estimates)
def plot_3d(self, subject=None, surface='white', hemi='both',
colormap='auto', time_label='auto', smoothing_steps=10,
transparent=True, alpha=0.2, time_viewer='auto',
subjects_dir=None,
figure=None, views='axial', 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):
return super().plot(
subject=subject, surface=surface, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
transparent=transparent, alpha=alpha, time_viewer=time_viewer,
subjects_dir=subjects_dir,
figure=figure, views=views, colorbar=colorbar, clim=clim,
cortex=cortex, size=size, background=background,
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)

@copy_function_doc_to_method_doc(plot_volume_source_estimates)
def plot(self, src, subject=None, subjects_dir=None, mode='stat_map',
bg_img='T1.mgz', colorbar=True, colormap='auto', clim='auto',
Expand Down Expand Up @@ -2162,8 +2209,8 @@ def save(self, fname, ftype='stc', verbose=None):


@fill_doc
class VolVectorSourceEstimate(_BaseVectorSourceEstimate,
_BaseVolSourceEstimate):
class VolVectorSourceEstimate(_BaseVolSourceEstimate,
_BaseVectorSourceEstimate):
"""Container for volume source estimates.

Parameters
Expand Down Expand Up @@ -2209,6 +2256,32 @@ class VolVectorSourceEstimate(_BaseVectorSourceEstimate,

_scalar_class = VolSourceEstimate

# defaults differ: hemi='both', views='axial'
@copy_function_doc_to_method_doc(plot_vector_source_estimates)
def plot_3d(self, subject=None, hemi='both', 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='axial',
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
return _BaseVectorSourceEstimate.plot(
self, subject=subject, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
transparent=transparent, brain_alpha=brain_alpha,
overlay_alpha=overlay_alpha, vector_alpha=vector_alpha,
scale_factor=scale_factor, time_viewer=time_viewer,
subjects_dir=subjects_dir, figure=figure, views=views,
colorbar=colorbar, clim=clim, cortex=cortex, size=size,
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)


@fill_doc
class VectorSourceEstimate(_BaseVectorSourceEstimate,
Expand Down Expand Up @@ -2259,27 +2332,6 @@ class VectorSourceEstimate(_BaseVectorSourceEstimate,

_scalar_class = SourceEstimate

@copy_function_doc_to_method_doc(plot_vector_source_estimates)
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',
colorbar=True, clim='auto', cortex='classic', size=800,
background='black', foreground=None, initial_time=None,
time_unit='s', show_traces='auto', verbose=None): # noqa: D102
return plot_vector_source_estimates(
self, subject=subject, hemi=hemi, colormap=colormap,
time_label=time_label, smoothing_steps=smoothing_steps,
transparent=transparent, brain_alpha=brain_alpha,
overlay_alpha=overlay_alpha, vector_alpha=vector_alpha,
scale_factor=scale_factor, time_viewer=time_viewer,
subjects_dir=subjects_dir, figure=figure, views=views,
colorbar=colorbar, clim=clim, cortex=cortex, size=size,
background=background, foreground=foreground,
initial_time=initial_time, time_unit=time_unit,
show_traces=show_traces, verbose=verbose,
)


###############################################################################
# Mixed source estimate (two cortical surfs plus other stuff)
Expand Down
37 changes: 33 additions & 4 deletions mne/utils/docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -990,13 +990,15 @@
or 'cubic'.
"""
docdict["show_traces"] = """
show_traces : bool | str
show_traces : bool | str | float
If True, enable interactive picking of a point on the surface of the
brain and plot it's time course using the bottom 1/3 of the figure.
brain and plot its time course.
This feature is only available with the PyVista 3d backend, and requires
``time_viewer=True``. Defaults to 'auto', which will use True if and
only if ``time_viewer=True``, the backend is PyVista, and there is more
than one time point.
than one time point. If float (between zero and one), it specifies what
proportion of the total window should be devoted to traces (True is
equivalent to 0.25, i.e., it will occupy the bottom 1/4 of the figure).

.. versionadded:: 0.20.0
"""
Expand All @@ -1007,7 +1009,34 @@
default is ``'auto'``, which will use ``time=%0.2f ms`` if there
is more than one time point.
"""

docdict["src_volume_options_layout"] = """
src : instance of SourceSpaces | None
The source space corresponding to the source estimate. Only necessary
if the STC is a volume or mixed source estimate.
volume_options : float | dict | None
Options for volumetric source estimate plotting, with key/value pairs:

- ``'resolution'`` : float | None
Resolution (in mm) of volume rendering. Smaller (e.g., 1.) looks
better at the cost of speed. None (default) uses the volume source
space resolution, which is often something like 7 or 5 mm,
without resampling.
- ``'blending'`` : str
Can be "mip" (default) for maximum intensity projection or
"composite" for composite blending.
- ``'alpha'`` : float | None
Alpha for the volumetric rendering. Uses 0.4 for vector source
estimates and 1.0 for scalar source estimates.
- ``'surface_alpha'`` : float | None
Alpha for the surface enclosing the volume(s). None will use
half the volume alpha. Set to zero to avoid plotting the surface.

A float input (default 1.) or None will be used for the ``'resolution'``
entry.
view_layout : str
Can be "vertical" (default) or "horizontal". When using "horizontal" mode,
the PyVista backend must be used and hemi cannot be "split".
"""

# STC label time course
docdict['eltc_labels'] = """
Expand Down
Loading