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: Recreate our helmet graphic #8116

Merged
merged 13 commits into from
Sep 8, 2020
32 changes: 32 additions & 0 deletions examples/visualization/plot_mne_helmet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
Plot the MNE brain and helmet
=============================

This tutorial shows how to make the MNE helmet + brain image.
"""

import os.path as op
import mne

sample_path = mne.datasets.sample.data_path()
subjects_dir = op.join(sample_path, 'subjects')
fname_evoked = op.join(sample_path, 'MEG', 'sample', 'sample_audvis-ave.fif')
fname_inv = op.join(sample_path, 'MEG', 'sample',
'sample_audvis-meg-oct-6-meg-inv.fif')
fname_trans = op.join(sample_path, 'MEG', 'sample',
'sample_audvis_raw-trans.fif')
inv = mne.minimum_norm.read_inverse_operator(fname_inv)
evoked = mne.read_evokeds(fname_evoked, baseline=(None, 0),
proj=True, verbose=False, condition='Left Auditory')
maps = mne.make_field_map(evoked, trans=fname_trans, ch_type='meg',
subject='sample', subjects_dir=subjects_dir)
time = 0.083
fig = mne.viz.create_3d_figure((256, 256))
mne.viz.plot_alignment(
evoked.info, subject='sample', subjects_dir=subjects_dir, fig=fig,
trans=fname_trans, meg='sensors', eeg=False, surfaces='pial',
coord_frame='mri')
evoked.plot_field(maps, time=time, fig=fig, time_label=None, vmax=5e-13)
mne.viz.set_3d_view(
fig, azimuth=40, elevation=87, focalpoint=(0., -0.01, 0.04), roll=-100,
distance=0.48)
5 changes: 3 additions & 2 deletions mne/evoked.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,10 +336,11 @@ def plot_topomap(self, times="auto", ch_type=None, vmin=None,

@copy_function_doc_to_method_doc(plot_evoked_field)
def plot_field(self, surf_maps, time=None, time_label='t = %0.0f ms',
n_jobs=1, fig=None, verbose=None):
n_jobs=1, fig=None, vmax=None, n_contours=21, verbose=None):
return plot_evoked_field(self, surf_maps, time=time,
time_label=time_label, n_jobs=n_jobs,
fig=fig, verbose=verbose)
fig=fig, vmax=vmax, n_contours=n_contours,
verbose=verbose)

@copy_function_doc_to_method_doc(plot_evoked_white)
def plot_white(self, noise_cov, show=True, rank=None, time_unit='s',
Expand Down
Binary file added mne/icons/mne_icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 25 additions & 10 deletions mne/viz/_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ def _set_aspect_equal(ax):

@verbose
def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
n_jobs=1, fig=None, verbose=None):
n_jobs=1, fig=None, vmax=None, n_contours=21,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

21 was the mayavi default?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, IIRC

Copy link
Contributor

@GuillaumeFavelier GuillaumeFavelier Sep 8, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, it's actually 10 but 21 was the default number of contours used in the function. I will change that

verbose=None):
"""Plot MEG/EEG fields on head surface and helmet in 3D.

Parameters
Expand All @@ -316,14 +317,22 @@ def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
time : float | None
The time point at which the field map shall be displayed. If None,
the average peak latency (across sensor types) is used.
time_label : str
time_label : str | None
How to print info about the time instant visualized.
%(n_jobs)s
fig : instance of mayavi.core.api.Scene | None
If None (default), a new figure will be created, otherwise it will
plot into the given figure.

.. versionadded:: 0.20
vmax : float | None
Maximum intensity. Can be None to use the max(abs(data)).

.. versionadded:: 0.21
n_contours : int
The number of contours.

.. versionadded:: 0.21
%(verbose)s

Returns
Expand All @@ -334,6 +343,8 @@ def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
# Update the backend
from .backends.renderer import _get_renderer
types = [t for t in ['eeg', 'grad', 'mag'] if t in evoked]
_validate_type(vmax, (None, 'numeric'), 'vmax')
n_contours = _ensure_int(n_contours, 'n_contours')

time_idx = None
if time is None:
Expand Down Expand Up @@ -382,23 +393,27 @@ def plot_evoked_field(evoked, surf_maps, time=None, time_label='t = %0.0f ms',
data = np.dot(map_data, evoked.data[pick, time_idx])

# Make a solid surface
vlim = np.max(np.abs(data))
if vmax is None:
vmax = np.max(np.abs(data))
vmax = float(vmax)
alpha = alphas[ii]
renderer.surface(surface=surf, color=colors[ii],
opacity=alpha)

# Now show our field pattern
renderer.surface(surface=surf, vmin=-vlim, vmax=vlim,
scalars=data, colormap=colormap)
renderer.surface(surface=surf, vmin=-vmax, vmax=vmax,
scalars=data, colormap=colormap,
polygon_offset=-1)

# And the field lines on top
renderer.contour(surface=surf, scalars=data, contours=21,
vmin=-vlim, vmax=vlim, opacity=alpha,
renderer.contour(surface=surf, scalars=data, contours=n_contours,
vmin=-vmax, vmax=vmax, opacity=alpha,
colormap=colormap_lines)

if '%' in time_label:
time_label %= (1e3 * evoked.times[time_idx])
renderer.text2d(x_window=0.01, y_window=0.01, text=time_label)
if time_label is not None:
if '%' in time_label:
time_label %= (1e3 * evoked.times[time_idx])
renderer.text2d(x_window=0.01, y_window=0.01, text=time_label)
renderer.set_camera(azimuth=10, elevation=60)
renderer.show()
return renderer.scene()
Expand Down
15 changes: 3 additions & 12 deletions mne/viz/_brain/_brain.py
Original file line number Diff line number Diff line change
Expand Up @@ -632,13 +632,13 @@ def _add_surface_data(self, hemi):
z=self.geo[hemi].coords[:, 2],
triangles=self.geo[hemi].faces,
normals=self.geo[hemi].nn,
polygon_offset=-2,
**kwargs,
)
if isinstance(mesh_data, tuple):
actor, mesh = mesh_data
# add metadata to the mesh for picking
mesh._hemi = hemi
self.resolve_coincident_topology(actor)
else:
actor, mesh = mesh_data, None
return actor, mesh
Expand Down Expand Up @@ -865,10 +865,8 @@ def add_label(self, label, color=None, alpha=1, scalar_thresh=None,
color=None,
colormap=ctable,
backface_culling=False,
polygon_offset=-2,
)
if isinstance(mesh_data, tuple):
actor, _ = mesh_data
self.resolve_coincident_topology(actor)
self._label_data.append(mesh_data)
self._renderer.set_camera(**views_dicts[hemi][v])

Expand Down Expand Up @@ -1077,6 +1075,7 @@ def add_annotation(self, annot, borders=True, alpha=1, hemi=None,
vmax=np.max(ids),
scalars=ids,
interpolate_before_map=False,
polygon_offset=-2,
)
if isinstance(mesh_data, tuple):
from ..backends._pyvista import _set_colormap_range
Expand All @@ -1085,17 +1084,9 @@ def add_annotation(self, annot, borders=True, alpha=1, hemi=None,
mesh._hemi = hemi
_set_colormap_range(actor, cmap.astype(np.uint8),
None)
self.resolve_coincident_topology(actor)

self._update()

def resolve_coincident_topology(self, actor):
"""Resolve z-fighting of overlapping surfaces."""
mapper = actor.GetMapper()
mapper.SetResolveCoincidentTopologyToPolygonOffset()
mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(
-1., -1.)

def close(self):
"""Close all figures and cleanup data structure."""
self._closed = True
Expand Down
7 changes: 5 additions & 2 deletions mne/viz/backends/_pysurfer_mayavi.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,11 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
backface_culling=False, scalars=None, colormap=None,
vmin=None, vmax=None, interpolate_before_map=True,
representation='surface', line_width=1., normals=None,
pickable=None, **kwargs):
polygon_offset=None, **kwargs):
# normals and pickable are unused
kwargs.pop('pickable', None)
del normals

if color is not None:
color = _check_color(color)
if color is not None and isinstance(color, np.ndarray) \
Expand Down Expand Up @@ -162,7 +165,7 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,
def surface(self, surface, color=None, opacity=1.0,
vmin=None, vmax=None, colormap=None,
normalized_colormap=False, scalars=None,
backface_culling=False):
backface_culling=False, polygon_offset=None):
if color is not None:
color = _check_color(color)
if normalized_colormap:
Expand Down
21 changes: 15 additions & 6 deletions mne/viz/backends/_pyvista.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ def set_interaction(self, interaction):
def polydata(self, mesh, color=None, opacity=1.0, normals=None,
backface_culling=False, scalars=None, colormap=None,
vmin=None, vmax=None, interpolate_before_map=True,
representation='surface', line_width=1., **kwargs):
representation='surface', line_width=1.,
polygon_offset=None, **kwargs):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=FutureWarning)
rgba = False
Expand Down Expand Up @@ -325,12 +326,19 @@ def polydata(self, mesh, color=None, opacity=1.0, normals=None,
style=representation, line_width=line_width, **kwargs,
)

if polygon_offset is not None:
mapper = actor.GetMapper()
mapper.SetResolveCoincidentTopologyToPolygonOffset()
mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(
polygon_offset, polygon_offset)

return actor, mesh

def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
backface_culling=False, scalars=None, colormap=None,
vmin=None, vmax=None, interpolate_before_map=True,
representation='surface', line_width=1., normals=None, **kwargs):
representation='surface', line_width=1., normals=None,
polygon_offset=None, **kwargs):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=FutureWarning)
vertices = np.c_[x, y, z]
Expand All @@ -349,6 +357,7 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
interpolate_before_map=interpolate_before_map,
representation=representation,
line_width=line_width,
polygon_offset=polygon_offset,
**kwargs,
)

Expand All @@ -366,7 +375,7 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,
triangles = np.c_[np.full(n_triangles, 3), triangles]
mesh = PolyData(vertices, triangles)
mesh.point_arrays['scalars'] = scalars
contour = mesh.contour(isosurfaces=contours, rng=(vmin, vmax))
contour = mesh.contour(isosurfaces=contours)
line_width = width
if kind == 'tube':
contour = contour.tube(radius=width, n_sides=self.tube_n_sides)
Expand All @@ -377,6 +386,7 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,
show_scalar_bar=False,
line_width=line_width,
color=color,
rng=[vmin, vmax],
cmap=colormap,
opacity=opacity,
smooth_shading=self.figure.smooth_shading
Expand All @@ -386,7 +396,7 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,
def surface(self, surface, color=None, opacity=1.0,
vmin=None, vmax=None, colormap=None,
normalized_colormap=False, scalars=None,
backface_culling=False):
backface_culling=False, polygon_offset=None):
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=FutureWarning)
normals = surface.get('nn', None)
Expand All @@ -407,6 +417,7 @@ def surface(self, surface, color=None, opacity=1.0,
colormap=colormap,
vmin=vmin,
vmax=vmax,
polygon_offset=polygon_offset,
)

def sphere(self, center, color, scale, opacity=1.0,
Expand Down Expand Up @@ -743,8 +754,6 @@ def _set_3d_view(figure, azimuth, elevation, focalpoint, distance, roll=None):
phi = _deg2rad(azimuth)
if elevation is not None:
theta = _deg2rad(elevation)
if roll is not None:
roll = _deg2rad(roll)

renderer = figure.plotter.renderer
bounds = np.array(renderer.ComputeVisiblePropBounds())
Expand Down
12 changes: 9 additions & 3 deletions mne/viz/backends/base_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def set_interaction(self, interaction):
def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
backface_culling=False, scalars=None, colormap=None,
vmin=None, vmax=None, interpolate_before_map=True,
representation='surface', line_width=1., normals=None, **kwargs):
representation='surface', line_width=1., normals=None,
polygon_offset=None, **kwargs):
"""Add a mesh in the scene.

Parameters
Expand Down Expand Up @@ -79,6 +80,8 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False,
The width of the lines when representation='wireframe'.
normals: array, shape (n_vertices, 3)
The array containing the normal of each vertex.
polygon_offset: float
If not None, the factor used to resolve coincident topology.
kwargs: args
The arguments to pass to triangular_mesh

Expand Down Expand Up @@ -128,8 +131,9 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0,

@abstractclassmethod
def surface(self, surface, color=None, opacity=1.0,
vmin=None, vmax=None, colormap=None, scalars=None,
backface_culling=False):
vmin=None, vmax=None, colormap=None,
normalized_colormap=False, scalars=None,
backface_culling=False, polygon_offset=None):
"""Add a surface in the scene.

Parameters
Expand All @@ -154,6 +158,8 @@ def surface(self, surface, color=None, opacity=1.0,
The scalar valued associated to the vertices.
backface_culling: bool
If True, enable backface culling on the surface.
polygon_offset: float
If not None, the factor used to resolve coincident topology.
"""
pass

Expand Down
6 changes: 4 additions & 2 deletions mne/viz/backends/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def _use_test_3d_backend(backend_name, interactive=False):


def set_3d_view(figure, azimuth=None, elevation=None,
focalpoint=None, distance=None):
focalpoint=None, distance=None, roll=None):
"""Configure the view of the given scene.

Parameters
Expand All @@ -215,10 +215,12 @@ def set_3d_view(figure, azimuth=None, elevation=None,
The focal point of the view: (x, y, z).
distance : float
The distance to the focal point.
roll : float
The view roll.
"""
backend._set_3d_view(figure=figure, azimuth=azimuth,
elevation=elevation, focalpoint=focalpoint,
distance=distance)
distance=distance, roll=roll)


def set_3d_title(figure, title, size=40):
Expand Down