From 0894be06a518b974ee40b7d66cbec7409015a8e2 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 22 Jul 2020 11:48:16 +0200 Subject: [PATCH 01/30] Fix representation --- mne/viz/backends/_pyvista.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 176c52603df..379e661fe8c 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -247,7 +247,7 @@ def _mesh(self, mesh, color, opacity=1.0, rng=[vmin, vmax], show_scalar_bar=False, smooth_shading=self.figure.smooth_shading, interpolate_before_map=interpolate_before_map, - representation=representation, line_width=line_width, **kwargs, + style=representation, line_width=line_width, **kwargs, ) try: @@ -569,7 +569,6 @@ def remove_mesh(self, mesh_data): def _add_mesh(plotter, *args, **kwargs): _process_events(plotter) - kwargs['style'] = kwargs.pop('representation', 'wireframe') return plotter.add_mesh(*args, **kwargs) From 3d3bb9a6b05f9521973c43f62d0a36e266a02385 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 22 Jul 2020 17:32:01 +0200 Subject: [PATCH 02/30] Use normals when available --- mne/viz/_3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 3b1aea763cc..074cd3ded46 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2564,7 +2564,7 @@ def plot_sparse_source_estimates(src, stcs, colors=None, linewidth=2, z=points[:, 2], triangles=use_faces, color=brain_color, opacity=opacity, backface_culling=True, shading=True, - **kwargs) + normals=normals, **kwargs) # Show time courses fig = plt.figure(fig_number) From 61ec43f06ffcf889f24eda6fba817220d3b238e6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 22 Jul 2020 17:52:44 +0200 Subject: [PATCH 03/30] Retrieve normals infos from surface when possible --- mne/viz/backends/_pyvista.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 379e661fe8c..28de19f97e5 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -326,12 +326,19 @@ def surface(self, surface, color=None, opacity=1.0, cmap = _get_colormap_from_array(colormap, normalized_colormap) vertices = np.array(surface['rr']) triangles = np.array(surface['tris']) + normals = surface.get('nn', None) n_triangles = len(triangles) triangles = np.c_[np.full(n_triangles, 3), triangles] mesh = PolyData(vertices, triangles) if scalars is not None: mesh.point_arrays['scalars'] = scalars - _add_mesh( + if normals is not None: + normals = np.array(normals) + mesh.point_arrays["Normals"] = normals + mesh.GetPointData().SetActiveNormals("Normals") + else: + return + actor = _add_mesh( plotter=self.plotter, mesh=mesh, color=color, rng=[vmin, vmax], @@ -341,6 +348,13 @@ def surface(self, surface, color=None, opacity=1.0, backface_culling=backface_culling, smooth_shading=self.figure.smooth_shading ) + try: + mesh.point_arrays["Normals"] + except KeyError: + pass + else: + prop = actor.GetProperty() + prop.SetInterpolationToPhong() def sphere(self, center, color, scale, opacity=1.0, resolution=8, backface_culling=False, From 8565a4006f47e398c2dfc81febca2a809325d83e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 22 Jul 2020 18:04:55 +0200 Subject: [PATCH 04/30] Remove debugging code --- mne/viz/backends/_pyvista.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 28de19f97e5..a1d7c433c33 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -336,8 +336,6 @@ def surface(self, surface, color=None, opacity=1.0, normals = np.array(normals) mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") - else: - return actor = _add_mesh( plotter=self.plotter, mesh=mesh, color=color, From 7c6b9bfcaad468926c06dd0f029dec2cac9d3a28 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Wed, 22 Jul 2020 18:19:12 +0200 Subject: [PATCH 05/30] Use smooth_shading=True when normals are not given --- mne/viz/backends/_pyvista.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index a1d7c433c33..f9b25de36e8 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=False, + smooth_shading=True, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=False): + notebook=None, smooth_shading=True): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, @@ -238,6 +238,10 @@ def _mesh(self, mesh, color, opacity=1.0, colormap = colormap.astype(np.float64) / 255. from matplotlib.colors import ListedColormap colormap = ListedColormap(colormap) + if 'Normals' in mesh.point_arrays: + smooth_shading = False + else: + smooth_shading = self.figure.smooth_shading actor = _add_mesh( plotter=self.plotter, @@ -245,16 +249,12 @@ def _mesh(self, mesh, color, opacity=1.0, rgba=rgba, opacity=opacity, cmap=colormap, backface_culling=backface_culling, rng=[vmin, vmax], show_scalar_bar=False, - smooth_shading=self.figure.smooth_shading, + smooth_shading=smooth_shading, interpolate_before_map=interpolate_before_map, style=representation, line_width=line_width, **kwargs, ) - try: - mesh.point_arrays["Normals"] - except KeyError: - pass - else: + if 'Normals' in mesh.point_arrays: prop = actor.GetProperty() prop.SetInterpolationToPhong() return actor, mesh @@ -333,9 +333,12 @@ def surface(self, surface, color=None, opacity=1.0, if scalars is not None: mesh.point_arrays['scalars'] = scalars if normals is not None: + smooth_shading = False normals = np.array(normals) mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") + else: + smooth_shading = self.figure.smooth_shading actor = _add_mesh( plotter=self.plotter, mesh=mesh, color=color, @@ -344,13 +347,10 @@ def surface(self, surface, color=None, opacity=1.0, opacity=opacity, cmap=cmap, backface_culling=backface_culling, - smooth_shading=self.figure.smooth_shading + smooth_shading=smooth_shading, ) - try: - mesh.point_arrays["Normals"] - except KeyError: - pass - else: + + if 'Normals' in mesh.point_arrays: prop = actor.GetProperty() prop.SetInterpolationToPhong() From 6097c6b666120dd99b8beea8692392449c4c66b3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 23 Jul 2020 14:31:44 +0200 Subject: [PATCH 06/30] Disable pyvista smooth shading by default --- mne/viz/backends/_pyvista.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index f9b25de36e8..e4ba6a1aa15 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=True, + smooth_shading=False, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=True): + notebook=None, smooth_shading=False): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, From 7cf9f3ab32016ecbb1e0e109efa29766f5da3171 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 23 Jul 2020 14:41:34 +0200 Subject: [PATCH 07/30] Fix surface --- mne/viz/backends/_pyvista.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index e4ba6a1aa15..e37f5ba4208 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -350,7 +350,7 @@ def surface(self, surface, color=None, opacity=1.0, smooth_shading=smooth_shading, ) - if 'Normals' in mesh.point_arrays: + if normals is not None: prop = actor.GetProperty() prop.SetInterpolationToPhong() From 30af75173a436c93aeb0b48f53d2f9c88497ce69 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 23 Jul 2020 15:45:00 +0200 Subject: [PATCH 08/30] Use private _compute_normals --- mne/viz/backends/_pyvista.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index e37f5ba4208..66005f62560 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -238,10 +238,6 @@ def _mesh(self, mesh, color, opacity=1.0, colormap = colormap.astype(np.float64) / 255. from matplotlib.colors import ListedColormap colormap = ListedColormap(colormap) - if 'Normals' in mesh.point_arrays: - smooth_shading = False - else: - smooth_shading = self.figure.smooth_shading actor = _add_mesh( plotter=self.plotter, @@ -249,7 +245,7 @@ def _mesh(self, mesh, color, opacity=1.0, rgba=rgba, opacity=opacity, cmap=colormap, backface_culling=backface_culling, rng=[vmin, vmax], show_scalar_bar=False, - smooth_shading=smooth_shading, + smooth_shading=self.figure.smooth_shading, interpolate_before_map=interpolate_before_map, style=representation, line_width=line_width, **kwargs, ) @@ -271,6 +267,8 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False, if normals is not None: mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") + else: + _compute_normals(mesh) return self._mesh( mesh, color, @@ -333,12 +331,11 @@ def surface(self, surface, color=None, opacity=1.0, if scalars is not None: mesh.point_arrays['scalars'] = scalars if normals is not None: - smooth_shading = False normals = np.array(normals) mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") else: - smooth_shading = self.figure.smooth_shading + _compute_normals(mesh) actor = _add_mesh( plotter=self.plotter, mesh=mesh, color=color, @@ -347,10 +344,10 @@ def surface(self, surface, color=None, opacity=1.0, opacity=opacity, cmap=cmap, backface_culling=backface_culling, - smooth_shading=smooth_shading, + smooth_shading=self.figure.smooth_shading, ) - if normals is not None: + if 'Normals' in mesh.point_arrays: prop = actor.GetProperty() prop.SetInterpolationToPhong() @@ -807,6 +804,18 @@ def _update_picking_callback(plotter, plotter.picker = picker +def _compute_normals(polydata): + normal = vtk.vtkPolyDataNormals() + normal.SetComputeCellNormals(False) + normal.SetConsistency(False) + normal.SetNonManifoldTraversal(False) + normal.SetSplitting(False) + normal.SetInputData(polydata) + normal.Update() + mesh = pyvista.wrap(normal.GetOutput()) + polydata.overwrite(mesh) + + def _add_polydata_actor(plotter, polydata, name=None, hide=False): mapper = vtk.vtkPolyDataMapper() From 1f2ee8cf743f9b6602b5e9a85ca0b75b90e7f5f0 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 23 Jul 2020 16:36:36 +0200 Subject: [PATCH 09/30] TST: Compute cell normals --- mne/viz/backends/_pyvista.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 66005f62560..d2c84ae3d09 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -806,7 +806,7 @@ def _update_picking_callback(plotter, def _compute_normals(polydata): normal = vtk.vtkPolyDataNormals() - normal.SetComputeCellNormals(False) + normal.SetComputeCellNormals(True) normal.SetConsistency(False) normal.SetNonManifoldTraversal(False) normal.SetSplitting(False) From 2e6f434c5296fa1cc90f0158021c80c7b4352920 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Thu, 23 Jul 2020 17:14:34 +0200 Subject: [PATCH 10/30] Test consistent_normals=True --- mne/viz/backends/_pyvista.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index d2c84ae3d09..32de047839c 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -804,16 +804,17 @@ def _update_picking_callback(plotter, plotter.picker = picker -def _compute_normals(polydata): - normal = vtk.vtkPolyDataNormals() - normal.SetComputeCellNormals(True) - normal.SetConsistency(False) - normal.SetNonManifoldTraversal(False) - normal.SetSplitting(False) - normal.SetInputData(polydata) - normal.Update() - mesh = pyvista.wrap(normal.GetOutput()) - polydata.overwrite(mesh) +def _compute_normals(mesh): + mesh.compute_normals( + cell_normals=False, + point_normals=True, + split_vertices=False, + flip_normals=False, + consistent_normals=True, + auto_orient_normals=False, + non_manifold_traversal=False, + inplace=True, + ) def _add_polydata_actor(plotter, polydata, name=None, From 90c42fba45b476509b9f5f5f6a5b2892a8a19a7b Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 09:39:36 +0200 Subject: [PATCH 11/30] Test non_manifold_traversal=True --- mne/viz/backends/_pyvista.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 0c0f012956d..0255523d37b 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -814,9 +814,9 @@ def _compute_normals(mesh): point_normals=True, split_vertices=False, flip_normals=False, - consistent_normals=True, + consistent_normals=False, auto_orient_normals=False, - non_manifold_traversal=False, + non_manifold_traversal=True, inplace=True, ) From 7868c18e0ee007d4a110bb90574d2836546f1023 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 11:11:38 +0200 Subject: [PATCH 12/30] Reproduce baseline --- mne/viz/backends/_pyvista.py | 94 +++++++++++++++++------------------- 1 file changed, 45 insertions(+), 49 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 0255523d37b..99de4d0a359 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -250,9 +250,6 @@ def _mesh(self, mesh, color, opacity=1.0, style=representation, line_width=line_width, **kwargs, ) - if 'Normals' in mesh.point_arrays: - prop = actor.GetProperty() - prop.SetInterpolationToPhong() return actor, mesh def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False, @@ -336,7 +333,7 @@ def surface(self, surface, color=None, opacity=1.0, mesh.GetPointData().SetActiveNormals("Normals") else: _compute_normals(mesh) - actor = _add_mesh( + _add_mesh( plotter=self.plotter, mesh=mesh, color=color, rng=[vmin, vmax], @@ -347,10 +344,6 @@ def surface(self, surface, color=None, opacity=1.0, smooth_shading=self.figure.smooth_shading, ) - if 'Normals' in mesh.point_arrays: - prop = actor.GetProperty() - prop.SetInterpolationToPhong() - def sphere(self, center, color, scale, opacity=1.0, resolution=8, backface_culling=False, radius=None): @@ -369,7 +362,7 @@ def sphere(self, center, color, scale, opacity=1.0, factor=factor, geom=geom) actor = _add_mesh( self.plotter, - glyph, color=color, opacity=opacity, + mesh=glyph, color=color, opacity=opacity, backface_culling=backface_culling, smooth_shading=self.figure.smooth_shading ) @@ -430,9 +423,9 @@ def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8, elif mode == 'arrow' or mode == '3darrow': _add_mesh( self.plotter, - grid.glyph(orient='vec', - scale=scale, - factor=factor), + mesh=grid.glyph(orient='vec', + scale=scale, + factor=factor), color=color, opacity=opacity, backface_culling=backface_culling @@ -450,10 +443,10 @@ def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8, geom = cone.GetOutput() _add_mesh( self.plotter, - grid.glyph(orient='vec', - scale=scale, - factor=factor, - geom=geom), + mesh=grid.glyph(orient='vec', + scale=scale, + factor=factor, + geom=geom), color=color, opacity=opacity, backface_culling=backface_culling @@ -477,10 +470,10 @@ def quiver3d(self, x, y, z, u, v, w, color, scale, mode, resolution=8, geom = trp.GetOutput() _add_mesh( self.plotter, - grid.glyph(orient='vec', - scale=scale, - factor=factor, - geom=geom), + mesh=grid.glyph(orient='vec', + scale=scale, + factor=factor, + geom=geom), color=color, opacity=opacity, backface_culling=backface_culling @@ -576,9 +569,35 @@ def remove_mesh(self, mesh_data): self.plotter.renderer.remove_actor(actor) +def _compute_normals(mesh): + mesh.compute_normals( + cell_normals=False, + inplace=True, + ) + + def _add_mesh(plotter, *args, **kwargs): _process_events(plotter) - return plotter.add_mesh(*args, **kwargs) + mesh = kwargs.get('mesh') + actor = plotter.add_mesh(*args, **kwargs) + if 'Normals' in mesh.point_arrays: + prop = actor.GetProperty() + prop.SetInterpolationToPhong() + return actor + + +def _add_polydata_actor(plotter, polydata, name=None, + hide=False): + mapper = vtk.vtkPolyDataMapper() + mapper.SetInputData(polydata) + + actor = vtk.vtkActor() + actor.SetMapper(mapper) + if hide: + actor.VisibilityOff() + + plotter.add_actor(actor, name=name) + return actor def _deg2rad(deg): @@ -808,33 +827,6 @@ def _update_picking_callback(plotter, plotter.picker = picker -def _compute_normals(mesh): - mesh.compute_normals( - cell_normals=False, - point_normals=True, - split_vertices=False, - flip_normals=False, - consistent_normals=False, - auto_orient_normals=False, - non_manifold_traversal=True, - inplace=True, - ) - - -def _add_polydata_actor(plotter, polydata, name=None, - hide=False): - mapper = vtk.vtkPolyDataMapper() - mapper.SetInputData(polydata) - - actor = vtk.vtkActor() - actor.SetMapper(mapper) - if hide: - actor.VisibilityOff() - - plotter.add_actor(actor, name=name) - return actor - - def _arrow_glyph(grid, factor): glyph = vtk.vtkGlyphSource2D() glyph.SetGlyphTypeToArrow() @@ -899,7 +891,11 @@ def _sphere(plotter, center, color, radius): sphere.SetCenter(center) sphere.Update() mesh = pyvista.wrap(sphere.GetOutput()) - actor = _add_mesh(plotter, mesh, color=color) + actor = _add_mesh( + plotter, + mesh=mesh, + color=color + ) return actor, mesh From f5c89103435785ae81031306b31539c0edd888cb Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 12:28:57 +0200 Subject: [PATCH 13/30] Test sphere --- mne/viz/backends/_pyvista.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 99de4d0a359..27d15b95488 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -360,6 +360,7 @@ def sphere(self, center, color, scale, opacity=1.0, mesh = PolyData(np.array(center)) glyph = mesh.glyph(orient=False, scale=False, factor=factor, geom=geom) + _compute_normals(glyph) actor = _add_mesh( self.plotter, mesh=glyph, color=color, opacity=opacity, From 52655ec0d59bd6808f98d1286064da67fa7ddca3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 14:20:05 +0200 Subject: [PATCH 14/30] Reproduce baseline --- mne/viz/backends/_pyvista.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 27d15b95488..643bc052ac1 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=False, + smooth_shading=True, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=False): + notebook=None, smooth_shading=True): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, @@ -238,6 +238,10 @@ def _mesh(self, mesh, color, opacity=1.0, colormap = colormap.astype(np.float64) / 255. from matplotlib.colors import ListedColormap colormap = ListedColormap(colormap) + if 'Normals' in mesh.point_arrays: + smooth_shading = False + else: + smooth_shading = self.figure.smooth_shading actor = _add_mesh( plotter=self.plotter, @@ -245,10 +249,13 @@ def _mesh(self, mesh, color, opacity=1.0, rgba=rgba, opacity=opacity, cmap=colormap, backface_culling=backface_culling, rng=[vmin, vmax], show_scalar_bar=False, - smooth_shading=self.figure.smooth_shading, + smooth_shading=smooth_shading, interpolate_before_map=interpolate_before_map, style=representation, line_width=line_width, **kwargs, ) + if 'Normals' in mesh.point_arrays: + prop = actor.GetProperty() + prop.SetInterpolationToPhong() return actor, mesh @@ -264,8 +271,6 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False, if normals is not None: mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") - else: - _compute_normals(mesh) return self._mesh( mesh, color, @@ -328,12 +333,13 @@ def surface(self, surface, color=None, opacity=1.0, if scalars is not None: mesh.point_arrays['scalars'] = scalars if normals is not None: + smooth_shading = False normals = np.array(normals) mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") else: - _compute_normals(mesh) - _add_mesh( + smooth_shading = self.figure.smooth_shading + actor = _add_mesh( plotter=self.plotter, mesh=mesh, color=color, rng=[vmin, vmax], @@ -341,8 +347,11 @@ def surface(self, surface, color=None, opacity=1.0, opacity=opacity, cmap=cmap, backface_culling=backface_culling, - smooth_shading=self.figure.smooth_shading, + smooth_shading=smooth_shading, ) + if 'Normals' in mesh.point_arrays: + prop = actor.GetProperty() + prop.SetInterpolationToPhong() def sphere(self, center, color, scale, opacity=1.0, resolution=8, backface_culling=False, @@ -360,7 +369,6 @@ def sphere(self, center, color, scale, opacity=1.0, mesh = PolyData(np.array(center)) glyph = mesh.glyph(orient=False, scale=False, factor=factor, geom=geom) - _compute_normals(glyph) actor = _add_mesh( self.plotter, mesh=glyph, color=color, opacity=opacity, @@ -579,12 +587,7 @@ def _compute_normals(mesh): def _add_mesh(plotter, *args, **kwargs): _process_events(plotter) - mesh = kwargs.get('mesh') - actor = plotter.add_mesh(*args, **kwargs) - if 'Normals' in mesh.point_arrays: - prop = actor.GetProperty() - prop.SetInterpolationToPhong() - return actor + return plotter.add_mesh(*args, **kwargs) def _add_polydata_actor(plotter, polydata, name=None, From 0536ba32c2401eec63a8b84affdd37cac3d2a1a7 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 15:59:18 +0200 Subject: [PATCH 15/30] Specialize Travis in viz --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 260ae2b1216..20fc36d08bf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -142,8 +142,8 @@ script: pip install -e .; python mne/tests/test_evoked.py; fi; - - echo 'pytest -m "${CONDITION}" --cov=mne ${USE_DIRS}' - - pytest -m "${CONDITION}" --tb=short --cov=mne ${USE_DIRS} + - echo 'pytest -m "${CONDITION}" --cov=mne mne/viz' + - pytest -m "${CONDITION}" --tb=short --cov=mne mne/viz # run the minimal one with the testing data - if [ "${DEPS}" == "minimal" ]; then export MNE_SKIP_TESTING_DATASET_TESTS=false; From 2447322d2e557396c603bbd782ea28a18a0dfcda Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 16:35:27 +0200 Subject: [PATCH 16/30] Add test_smooth_shading v1 --- mne/viz/backends/_pyvista.py | 4 ++-- mne/viz/backends/tests/test_renderer.py | 28 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 643bc052ac1..3401a058f71 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=True, + smooth_shading=False, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=True): + notebook=None, smooth_shading=False): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index ae168c1eb13..7d599d795ec 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -165,3 +165,31 @@ def test_get_3d_backend(renderer): orig_backend = renderer.MNE_3D_BACKEND assert renderer.get_3d_backend() == orig_backend assert renderer.get_3d_backend() == orig_backend + + +def test_smooth_shading(renderer): + if renderer._get_3d_backend() == "mayavi": + pytest.skip('This parameter is only supported on PyVista') + from pyvista import PolyData + + vertices = np.array([ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + ]) + triangles = np.array([ + [3, 0, 1, 2] + ]) + normals = np.array([ + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + ]) + + mesh = PolyData(vertices, triangles) + mesh.point_arrays['Normals'] = normals + mesh.GetPointData().SetActiveNormals("Normals") + + rend = renderer._get_renderer() + rend.plotter.add_mesh(mesh, smooth_shading=True) + rend.show() From 40b1b359fe3b0a48b3acc0c7adb3bfc3ee602a4e Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Fri, 24 Jul 2020 16:53:24 +0200 Subject: [PATCH 17/30] Try with sphere --- mne/viz/backends/tests/test_renderer.py | 33 +++++++++---------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index 7d599d795ec..dab344bd70a 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -170,26 +170,15 @@ def test_get_3d_backend(renderer): def test_smooth_shading(renderer): if renderer._get_3d_backend() == "mayavi": pytest.skip('This parameter is only supported on PyVista') - from pyvista import PolyData - - vertices = np.array([ - [0, 0, 0], - [1, 0, 0], - [0, 1, 0], - ]) - triangles = np.array([ - [3, 0, 1, 2] - ]) - normals = np.array([ - [0, 0, 1], - [0, 0, 1], - [0, 0, 1], - ]) - - mesh = PolyData(vertices, triangles) - mesh.point_arrays['Normals'] = normals - mesh.GetPointData().SetActiveNormals("Normals") - - rend = renderer._get_renderer() - rend.plotter.add_mesh(mesh, smooth_shading=True) + tet_size = 1.0 + tet_x = np.array([0, tet_size, 0, 0]) + tet_y = np.array([0, 0, tet_size, 0]) + tet_z = np.array([0, 0, 0, tet_size]) + sph_center = np.column_stack((tet_x, tet_y, tet_z)) + sph_color = 'red' + sph_scale = tet_size / 3.0 + + rend = renderer._get_renderer(smooth_shading=True) + rend.sphere(center=sph_center, color=sph_color, + scale=sph_scale, radius=1.0) rend.show() From adfff379b09f4e7382c0e9751f82d7ace2b6682d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 09:36:50 +0200 Subject: [PATCH 18/30] Simplify testing --- .travis.yml | 2 +- mne/viz/backends/_pyvista.py | 4 ++-- mne/viz/backends/tests/test_renderer.py | 23 +++++------------------ 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index 20fc36d08bf..9d8ff17a862 100644 --- a/.travis.yml +++ b/.travis.yml @@ -143,7 +143,7 @@ script: python mne/tests/test_evoked.py; fi; - echo 'pytest -m "${CONDITION}" --cov=mne mne/viz' - - pytest -m "${CONDITION}" --tb=short --cov=mne mne/viz + - pytest -v mne/viz # run the minimal one with the testing data - if [ "${DEPS}" == "minimal" ]; then export MNE_SKIP_TESTING_DATASET_TESTS=false; diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 3401a058f71..643bc052ac1 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=False, + smooth_shading=True, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=False): + notebook=None, smooth_shading=True): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index dab344bd70a..87e4075002c 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -102,7 +102,11 @@ def test_3d_backend(renderer): cam_distance = 5 * tet_size # init scene - rend = renderer.backend._Renderer(size=win_size, bgcolor=win_color) + rend = renderer.backend._Renderer( + size=win_size, + bgcolor=win_color, + smooth_shading=True, + ) rend.set_interactive() # use mesh @@ -165,20 +169,3 @@ def test_get_3d_backend(renderer): orig_backend = renderer.MNE_3D_BACKEND assert renderer.get_3d_backend() == orig_backend assert renderer.get_3d_backend() == orig_backend - - -def test_smooth_shading(renderer): - if renderer._get_3d_backend() == "mayavi": - pytest.skip('This parameter is only supported on PyVista') - tet_size = 1.0 - tet_x = np.array([0, tet_size, 0, 0]) - tet_y = np.array([0, 0, tet_size, 0]) - tet_z = np.array([0, 0, 0, tet_size]) - sph_center = np.column_stack((tet_x, tet_y, tet_z)) - sph_color = 'red' - sph_scale = tet_size / 3.0 - - rend = renderer._get_renderer(smooth_shading=True) - rend.sphere(center=sph_center, color=sph_color, - scale=sph_scale, radius=1.0) - rend.show() From fb280183e260f56bda4d21b86fcd6033379ac83a Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 10:32:19 +0200 Subject: [PATCH 19/30] Confirm sphere mode failure on Travis (plot_dipole_locations) --- mne/viz/_3d.py | 6 +++++- mne/viz/backends/_pyvista.py | 4 ++-- mne/viz/tests/test_3d.py | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 2e074b12abd..1ecc6e057a8 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2734,7 +2734,11 @@ def plot_dipole_locations(dipoles, trans=None, subject=None, subjects_dir=None, elif mode in ['arrow', 'sphere']: from .backends.renderer import _get_renderer color = (1., 0., 0.) if color is None else color - renderer = _get_renderer(fig=fig, size=(600, 600)) + renderer = _get_renderer( + fig=fig, + size=(600, 600), + smooth_shading=True, + ) pos = dipoles.pos ori = dipoles.ori if coord_frame != 'head': diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 643bc052ac1..3401a058f71 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=True, + smooth_shading=False, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=True): + notebook=None, smooth_shading=False): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, diff --git a/mne/viz/tests/test_3d.py b/mne/viz/tests/test_3d.py index 26ed45ed6d8..60bc39a69f9 100644 --- a/mne/viz/tests/test_3d.py +++ b/mne/viz/tests/test_3d.py @@ -570,7 +570,7 @@ def test_plot_dipole_orientations(renderer): dipoles = read_dipole(dip_fname) trans = read_trans(trans_fname) for coord_frame, mode in zip(['head', 'mri'], - ['arrow', 'sphere']): + ['sphere']): dipoles.plot_locations(trans=trans, subject='sample', subjects_dir=subjects_dir, mode=mode, coord_frame=coord_frame) From f4ca3d8f6314922ac730c2b52fcae67e0bf7bc9f Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 11:08:04 +0200 Subject: [PATCH 20/30] Reproduce failure on Travis --- mne/viz/tests/test_3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/tests/test_3d.py b/mne/viz/tests/test_3d.py index 60bc39a69f9..99a96725395 100644 --- a/mne/viz/tests/test_3d.py +++ b/mne/viz/tests/test_3d.py @@ -570,7 +570,7 @@ def test_plot_dipole_orientations(renderer): dipoles = read_dipole(dip_fname) trans = read_trans(trans_fname) for coord_frame, mode in zip(['head', 'mri'], - ['sphere']): + ['arrow']): dipoles.plot_locations(trans=trans, subject='sample', subjects_dir=subjects_dir, mode=mode, coord_frame=coord_frame) From 22ca5a7a9f48c876417f58867b434755a5ec96f7 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 13:33:57 +0200 Subject: [PATCH 21/30] Restore test_plot_dipole_orientations --- mne/viz/tests/test_3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mne/viz/tests/test_3d.py b/mne/viz/tests/test_3d.py index 99a96725395..26ed45ed6d8 100644 --- a/mne/viz/tests/test_3d.py +++ b/mne/viz/tests/test_3d.py @@ -570,7 +570,7 @@ def test_plot_dipole_orientations(renderer): dipoles = read_dipole(dip_fname) trans = read_trans(trans_fname) for coord_frame, mode in zip(['head', 'mri'], - ['arrow']): + ['arrow', 'sphere']): dipoles.plot_locations(trans=trans, subject='sample', subjects_dir=subjects_dir, mode=mode, coord_frame=coord_frame) From 30966c0b9505b6dbe8e9b981cdf4a77e900f8bf6 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 13:41:16 +0200 Subject: [PATCH 22/30] Disable pyvista smooth shading --- mne/viz/backends/_pyvista.py | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 3401a058f71..139accec4ee 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -238,10 +238,6 @@ def _mesh(self, mesh, color, opacity=1.0, colormap = colormap.astype(np.float64) / 255. from matplotlib.colors import ListedColormap colormap = ListedColormap(colormap) - if 'Normals' in mesh.point_arrays: - smooth_shading = False - else: - smooth_shading = self.figure.smooth_shading actor = _add_mesh( plotter=self.plotter, @@ -249,13 +245,10 @@ def _mesh(self, mesh, color, opacity=1.0, rgba=rgba, opacity=opacity, cmap=colormap, backface_culling=backface_culling, rng=[vmin, vmax], show_scalar_bar=False, - smooth_shading=smooth_shading, + smooth_shading=self.figure.smooth_shading, interpolate_before_map=interpolate_before_map, style=representation, line_width=line_width, **kwargs, ) - if 'Normals' in mesh.point_arrays: - prop = actor.GetProperty() - prop.SetInterpolationToPhong() return actor, mesh @@ -333,13 +326,10 @@ def surface(self, surface, color=None, opacity=1.0, if scalars is not None: mesh.point_arrays['scalars'] = scalars if normals is not None: - smooth_shading = False normals = np.array(normals) mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") - else: - smooth_shading = self.figure.smooth_shading - actor = _add_mesh( + _add_mesh( plotter=self.plotter, mesh=mesh, color=color, rng=[vmin, vmax], @@ -347,11 +337,8 @@ def surface(self, surface, color=None, opacity=1.0, opacity=opacity, cmap=cmap, backface_culling=backface_culling, - smooth_shading=smooth_shading, + smooth_shading=self.figure.smooth_shading, ) - if 'Normals' in mesh.point_arrays: - prop = actor.GetProperty() - prop.SetInterpolationToPhong() def sphere(self, center, color, scale, opacity=1.0, resolution=8, backface_culling=False, @@ -587,7 +574,12 @@ def _compute_normals(mesh): def _add_mesh(plotter, *args, **kwargs): _process_events(plotter) - return plotter.add_mesh(*args, **kwargs) + mesh = kwargs.get('mesh') + actor = plotter.add_mesh(*args, **kwargs) + if 'Normals' in mesh.point_arrays: + prop = actor.GetProperty() + prop.SetInterpolationToPhong() + return actor def _add_polydata_actor(plotter, polydata, name=None, From bfc96eba739ffe89889e30d7de17b8b80eb57d15 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 13:46:07 +0200 Subject: [PATCH 23/30] Compute normals when not provided --- mne/viz/backends/_pyvista.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 139accec4ee..862d60c690f 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -264,6 +264,8 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False, if normals is not None: mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") + else: + _compute_normals(mesh) return self._mesh( mesh, color, @@ -329,6 +331,8 @@ def surface(self, surface, color=None, opacity=1.0, normals = np.array(normals) mesh.point_arrays["Normals"] = normals mesh.GetPointData().SetActiveNormals("Normals") + else: + _compute_normals(mesh) _add_mesh( plotter=self.plotter, mesh=mesh, color=color, @@ -568,6 +572,8 @@ def remove_mesh(self, mesh_data): def _compute_normals(mesh): mesh.compute_normals( cell_normals=False, + consistent_normals=False, + non_manifold_traversal=False, inplace=True, ) From b12433a29744693c4b2f938f80f5c4e3ace5bd07 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 14:16:31 +0200 Subject: [PATCH 24/30] Remove smooth_shading parameter --- mne/viz/_3d.py | 6 +----- mne/viz/backends/_pysurfer_mayavi.py | 2 +- mne/viz/backends/_pyvista.py | 12 ++---------- mne/viz/backends/tests/test_renderer.py | 6 +----- .../plot_background_freesurfer_mne.py | 2 +- 5 files changed, 6 insertions(+), 22 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 1ecc6e057a8..2e074b12abd 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2734,11 +2734,7 @@ def plot_dipole_locations(dipoles, trans=None, subject=None, subjects_dir=None, elif mode in ['arrow', 'sphere']: from .backends.renderer import _get_renderer color = (1., 0., 0.) if color is None else color - renderer = _get_renderer( - fig=fig, - size=(600, 600), - smooth_shading=True, - ) + renderer = _get_renderer(fig=fig, size=(600, 600)) pos = dipoles.pos ori = dipoles.ori if coord_frame != 'head': diff --git a/mne/viz/backends/_pysurfer_mayavi.py b/mne/viz/backends/_pysurfer_mayavi.py index 0277f4c3117..8e2cf6ad84a 100644 --- a/mne/viz/backends/_pysurfer_mayavi.py +++ b/mne/viz/backends/_pysurfer_mayavi.py @@ -65,7 +65,7 @@ class _Renderer(_BaseRenderer): """ def __init__(self, fig=None, size=(600, 600), bgcolor='black', - name=None, show=False, shape=(1, 1), smooth_shading=True): + name=None, show=False, shape=(1, 1)): if bgcolor is not None: bgcolor = _check_color(bgcolor) self.mlab = _import_mlab() diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 862d60c690f..82c37ee065f 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,14 +49,12 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=False, off_screen=False, notebook=False): self.plotter = plotter self.plotter_class = plotter_class self.display = display self.background_color = background_color - self.smooth_shading = smooth_shading self.notebook = notebook self.store = dict() @@ -144,12 +142,11 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=False): + notebook=None): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, - background_color=bgcolor, notebook=notebook, - smooth_shading=smooth_shading) + background_color=bgcolor, notebook=notebook) self.font_family = "arial" self.tube_n_sides = 20 self.shape = shape @@ -245,7 +242,6 @@ def _mesh(self, mesh, color, opacity=1.0, rgba=rgba, opacity=opacity, cmap=colormap, backface_culling=backface_culling, rng=[vmin, vmax], show_scalar_bar=False, - smooth_shading=self.figure.smooth_shading, interpolate_before_map=interpolate_before_map, style=representation, line_width=line_width, **kwargs, ) @@ -308,7 +304,6 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0, color=color, cmap=colormap, opacity=opacity, - smooth_shading=self.figure.smooth_shading ) return actor, contour @@ -341,7 +336,6 @@ def surface(self, surface, color=None, opacity=1.0, opacity=opacity, cmap=cmap, backface_culling=backface_culling, - smooth_shading=self.figure.smooth_shading, ) def sphere(self, center, color, scale, opacity=1.0, @@ -364,7 +358,6 @@ def sphere(self, center, color, scale, opacity=1.0, self.plotter, mesh=glyph, color=color, opacity=opacity, backface_culling=backface_culling, - smooth_shading=self.figure.smooth_shading ) return actor, glyph @@ -392,7 +385,6 @@ def tube(self, origin, destination, radius=0.001, color='white', color=color, show_scalar_bar=False, cmap=cmap, - smooth_shading=self.figure.smooth_shading, ) return tube diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index 87e4075002c..ae168c1eb13 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -102,11 +102,7 @@ def test_3d_backend(renderer): cam_distance = 5 * tet_size # init scene - rend = renderer.backend._Renderer( - size=win_size, - bgcolor=win_color, - smooth_shading=True, - ) + rend = renderer.backend._Renderer(size=win_size, bgcolor=win_color) rend.set_interactive() # use mesh diff --git a/tutorials/source-modeling/plot_background_freesurfer_mne.py b/tutorials/source-modeling/plot_background_freesurfer_mne.py index 0ac0000300c..3e20af02dbe 100644 --- a/tutorials/source-modeling/plot_background_freesurfer_mne.py +++ b/tutorials/source-modeling/plot_background_freesurfer_mne.py @@ -331,7 +331,7 @@ def imshow_mri(data, img, vox, xyz, suptitle): # sphere, a given vertex in the source (sample) mesh can be mapped easily # to the same location in the destination (fsaverage) mesh, and vice-versa. -renderer_kwargs = dict(bgcolor='w', smooth_shading=False) +renderer_kwargs = dict(bgcolor='w') renderer = mne.viz.backends.renderer._get_renderer( size=(800, 400), **renderer_kwargs) curvs = [ From 3d3cf1e47dac32933483caeef69f12a0edecf8e3 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 14:19:03 +0200 Subject: [PATCH 25/30] Restore travis conf --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b43a4bf4e69..df9f261d4e5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -142,8 +142,8 @@ script: pip install -e .; python mne/tests/test_evoked.py; fi; - - echo 'pytest -v mne/viz' - - pytest -v mne/viz + - echo 'pytest -m "${CONDITION}" --tb=short --cov=mne -vv ${USE_DIRS}' + - pytest -m "${CONDITION}" --tb=short --cov=mne -vv ${USE_DIRS} # run the minimal one with the testing data - if [ "${DEPS}" == "minimal" ]; then export MNE_SKIP_TESTING_DATASET_TESTS=false; From 0ddc82cbdf8d2812d6c7897aaae1379d7a5bd23c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 15:32:11 +0200 Subject: [PATCH 26/30] Revert "Remove smooth_shading parameter" This reverts commit b12433a29744693c4b2f938f80f5c4e3ace5bd07. --- mne/viz/_3d.py | 6 +++++- mne/viz/backends/_pysurfer_mayavi.py | 2 +- mne/viz/backends/_pyvista.py | 12 ++++++++++-- mne/viz/backends/tests/test_renderer.py | 6 +++++- .../plot_background_freesurfer_mne.py | 2 +- 5 files changed, 22 insertions(+), 6 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 2e074b12abd..1ecc6e057a8 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2734,7 +2734,11 @@ def plot_dipole_locations(dipoles, trans=None, subject=None, subjects_dir=None, elif mode in ['arrow', 'sphere']: from .backends.renderer import _get_renderer color = (1., 0., 0.) if color is None else color - renderer = _get_renderer(fig=fig, size=(600, 600)) + renderer = _get_renderer( + fig=fig, + size=(600, 600), + smooth_shading=True, + ) pos = dipoles.pos ori = dipoles.ori if coord_frame != 'head': diff --git a/mne/viz/backends/_pysurfer_mayavi.py b/mne/viz/backends/_pysurfer_mayavi.py index 8e2cf6ad84a..0277f4c3117 100644 --- a/mne/viz/backends/_pysurfer_mayavi.py +++ b/mne/viz/backends/_pysurfer_mayavi.py @@ -65,7 +65,7 @@ class _Renderer(_BaseRenderer): """ def __init__(self, fig=None, size=(600, 600), bgcolor='black', - name=None, show=False, shape=(1, 1)): + name=None, show=False, shape=(1, 1), smooth_shading=True): if bgcolor is not None: bgcolor = _check_color(bgcolor) self.mlab = _import_mlab() diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 82c37ee065f..862d60c690f 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,12 +49,14 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', + smooth_shading=False, off_screen=False, notebook=False): self.plotter = plotter self.plotter_class = plotter_class self.display = display self.background_color = background_color + self.smooth_shading = smooth_shading self.notebook = notebook self.store = dict() @@ -142,11 +144,12 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None): + notebook=None, smooth_shading=False): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, - background_color=bgcolor, notebook=notebook) + background_color=bgcolor, notebook=notebook, + smooth_shading=smooth_shading) self.font_family = "arial" self.tube_n_sides = 20 self.shape = shape @@ -242,6 +245,7 @@ def _mesh(self, mesh, color, opacity=1.0, rgba=rgba, opacity=opacity, cmap=colormap, backface_culling=backface_culling, rng=[vmin, vmax], show_scalar_bar=False, + smooth_shading=self.figure.smooth_shading, interpolate_before_map=interpolate_before_map, style=representation, line_width=line_width, **kwargs, ) @@ -304,6 +308,7 @@ def contour(self, surface, scalars, contours, width=1.0, opacity=1.0, color=color, cmap=colormap, opacity=opacity, + smooth_shading=self.figure.smooth_shading ) return actor, contour @@ -336,6 +341,7 @@ def surface(self, surface, color=None, opacity=1.0, opacity=opacity, cmap=cmap, backface_culling=backface_culling, + smooth_shading=self.figure.smooth_shading, ) def sphere(self, center, color, scale, opacity=1.0, @@ -358,6 +364,7 @@ def sphere(self, center, color, scale, opacity=1.0, self.plotter, mesh=glyph, color=color, opacity=opacity, backface_culling=backface_culling, + smooth_shading=self.figure.smooth_shading ) return actor, glyph @@ -385,6 +392,7 @@ def tube(self, origin, destination, radius=0.001, color='white', color=color, show_scalar_bar=False, cmap=cmap, + smooth_shading=self.figure.smooth_shading, ) return tube diff --git a/mne/viz/backends/tests/test_renderer.py b/mne/viz/backends/tests/test_renderer.py index ae168c1eb13..87e4075002c 100644 --- a/mne/viz/backends/tests/test_renderer.py +++ b/mne/viz/backends/tests/test_renderer.py @@ -102,7 +102,11 @@ def test_3d_backend(renderer): cam_distance = 5 * tet_size # init scene - rend = renderer.backend._Renderer(size=win_size, bgcolor=win_color) + rend = renderer.backend._Renderer( + size=win_size, + bgcolor=win_color, + smooth_shading=True, + ) rend.set_interactive() # use mesh diff --git a/tutorials/source-modeling/plot_background_freesurfer_mne.py b/tutorials/source-modeling/plot_background_freesurfer_mne.py index 3e20af02dbe..0ac0000300c 100644 --- a/tutorials/source-modeling/plot_background_freesurfer_mne.py +++ b/tutorials/source-modeling/plot_background_freesurfer_mne.py @@ -331,7 +331,7 @@ def imshow_mri(data, img, vox, xyz, suptitle): # sphere, a given vertex in the source (sample) mesh can be mapped easily # to the same location in the destination (fsaverage) mesh, and vice-versa. -renderer_kwargs = dict(bgcolor='w') +renderer_kwargs = dict(bgcolor='w', smooth_shading=False) renderer = mne.viz.backends.renderer._get_renderer( size=(800, 400), **renderer_kwargs) curvs = [ From 5766e64b38e7c693cd96d473a0ca8dcab17f806d Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Mon, 27 Jul 2020 16:27:25 +0200 Subject: [PATCH 27/30] Restore smooth_shading --- mne/viz/_3d.py | 6 +----- mne/viz/backends/_pyvista.py | 10 +++++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/mne/viz/_3d.py b/mne/viz/_3d.py index 1ecc6e057a8..2e074b12abd 100644 --- a/mne/viz/_3d.py +++ b/mne/viz/_3d.py @@ -2734,11 +2734,7 @@ def plot_dipole_locations(dipoles, trans=None, subject=None, subjects_dir=None, elif mode in ['arrow', 'sphere']: from .backends.renderer import _get_renderer color = (1., 0., 0.) if color is None else color - renderer = _get_renderer( - fig=fig, - size=(600, 600), - smooth_shading=True, - ) + renderer = _get_renderer(fig=fig, size=(600, 600)) pos = dipoles.pos ori = dipoles.ori if coord_frame != 'head': diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 862d60c690f..6b5eba104c7 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -49,7 +49,7 @@ def __init__(self, plotter=None, size=(600, 600), shape=(1, 1), background_color='black', - smooth_shading=False, + smooth_shading=True, off_screen=False, notebook=False): self.plotter = plotter @@ -144,7 +144,7 @@ class _Renderer(_BaseRenderer): def __init__(self, fig=None, size=(600, 600), bgcolor='black', name="PyVista Scene", show=False, shape=(1, 1), - notebook=None, smooth_shading=False): + notebook=None, smooth_shading=True): from .renderer import MNE_3D_BACKEND_TESTING from .._3d import _get_3d_option figure = _Figure(show=show, title=name, size=size, shape=shape, @@ -581,8 +581,12 @@ def _compute_normals(mesh): def _add_mesh(plotter, *args, **kwargs): _process_events(plotter) mesh = kwargs.get('mesh') + if 'smooth_shading' in kwargs: + smooth_shading = kwargs.pop('smooth_shading') + else: + smooth_shading = True actor = plotter.add_mesh(*args, **kwargs) - if 'Normals' in mesh.point_arrays: + if smooth_shading and 'Normals' in mesh.point_arrays: prop = actor.GetProperty() prop.SetInterpolationToPhong() return actor From d1e0a58beba19fd1af90e3a7017e78f21e376a65 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Jul 2020 12:08:13 +0200 Subject: [PATCH 28/30] Add renderer.polydata --- mne/viz/_brain/_brain.py | 4 +- mne/viz/backends/_pysurfer_mayavi.py | 6 -- mne/viz/backends/_pyvista.py | 100 +++++++++++++-------------- 3 files changed, 49 insertions(+), 61 deletions(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index a6e3ccc77a2..5f6663e57c4 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -270,7 +270,7 @@ def __init__(self, subject_id, hemi, surf, title=None, self._hemi_meshes[h] = mesh self._hemi_actors[h] = actor else: - self._renderer._mesh( + self._renderer.polydata( self._hemi_meshes[h], **kwargs, ) @@ -528,7 +528,7 @@ def add_data(self, array, fmin=None, fmid=None, fmax=None, self._data[hemi]['actor'] = actor self._data[hemi]['mesh'] = mesh else: - self._renderer._mesh( + self._renderer.polydata( self._data[hemi]['mesh'], **kwargs, ) diff --git a/mne/viz/backends/_pysurfer_mayavi.py b/mne/viz/backends/_pysurfer_mayavi.py index 0277f4c3117..5ad63ee45dc 100644 --- a/mne/viz/backends/_pysurfer_mayavi.py +++ b/mne/viz/backends/_pysurfer_mayavi.py @@ -95,12 +95,6 @@ def set_interactive(self): self.fig.scene.interactor.interactor_style = \ tvtk.InteractorStyleTerrain() - def _mesh(self, mesh, color, opacity=1.0, - backface_culling=False, scalars=None, colormap=None, - vmin=None, vmax=None, interpolate_before_map=True, - representation='surface', line_width=1., **kwargs): - raise NotImplementedError("This feature is not available with mayavi.") - 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, diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 6b5eba104c7..b1d1314ac2f 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -217,17 +217,16 @@ def scene(self): def set_interactive(self): self.plotter.enable_terrain_style() - def _mesh(self, mesh, color, opacity=1.0, - backface_culling=False, scalars=None, colormap=None, - vmin=None, vmax=None, interpolate_before_map=True, - representation='surface', line_width=1., **kwargs): + def polydata(self, mesh, color, 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): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=FutureWarning) - n_vertices = mesh.n_points rgba = False - if color is not None and len(color) == n_vertices: + if color is not None and len(color) == mesh.n_points: if color.shape[1] == 3: - scalars = np.c_[color, np.ones(n_vertices)] + scalars = np.c_[color, np.ones(mesh.n_points)] else: scalars = color scalars = (scalars * 255).astype('ubyte') @@ -238,7 +237,11 @@ def _mesh(self, mesh, color, opacity=1.0, colormap = colormap.astype(np.float64) / 255. from matplotlib.colors import ListedColormap colormap = ListedColormap(colormap) - + if normals is not None: + mesh.point_arrays["Normals"] = normals + mesh.GetPointData().SetActiveNormals("Normals") + else: + _compute_normals(mesh) actor = _add_mesh( plotter=self.plotter, mesh=mesh, color=color, scalars=scalars, @@ -261,23 +264,19 @@ def mesh(self, x, y, z, triangles, color, opacity=1.0, shading=False, vertices = np.c_[x, y, z] triangles = np.c_[np.full(len(triangles), 3), triangles] mesh = PolyData(vertices, triangles) - if normals is not None: - mesh.point_arrays["Normals"] = normals - mesh.GetPointData().SetActiveNormals("Normals") - else: - _compute_normals(mesh) - return self._mesh( - mesh, - color, - opacity, - backface_culling, - scalars, - colormap, - vmin, - vmax, - interpolate_before_map, - representation, - line_width, + return self.polydata( + mesh=mesh, + color=color, + opacity=opacity, + normals=normals, + backface_culling=backface_culling, + scalars=scalars, + colormap=colormap, + vmin=vmin, + vmax=vmax, + interpolate_before_map=interpolate_before_map, + representation=representation, + line_width=line_width, **kwargs, ) @@ -318,31 +317,25 @@ def surface(self, surface, color=None, opacity=1.0, backface_culling=False): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=FutureWarning) - cmap = _get_colormap_from_array(colormap, normalized_colormap) + normals = surface.get('nn', None) vertices = np.array(surface['rr']) triangles = np.array(surface['tris']) - normals = surface.get('nn', None) - n_triangles = len(triangles) - triangles = np.c_[np.full(n_triangles, 3), triangles] + triangles = np.c_[np.full(len(triangles), 3), triangles] mesh = PolyData(vertices, triangles) - if scalars is not None: - mesh.point_arrays['scalars'] = scalars - if normals is not None: - normals = np.array(normals) - mesh.point_arrays["Normals"] = normals - mesh.GetPointData().SetActiveNormals("Normals") - else: - _compute_normals(mesh) - _add_mesh( - plotter=self.plotter, - mesh=mesh, color=color, - rng=[vmin, vmax], - show_scalar_bar=False, - opacity=opacity, - cmap=cmap, - backface_culling=backface_culling, - smooth_shading=self.figure.smooth_shading, - ) + colormap = _get_colormap_from_array(colormap, normalized_colormap) + if scalars is not None: + mesh.point_arrays['scalars'] = scalars + return self.polydata( + mesh=mesh, + color=color, + opacity=opacity, + normals=normals, + backface_culling=backface_culling, + scalars=scalars, + colormap=colormap, + vmin=vmin, + vmax=vmax, + ) def sphere(self, center, color, scale, opacity=1.0, resolution=8, backface_culling=False, @@ -570,12 +563,13 @@ def remove_mesh(self, mesh_data): def _compute_normals(mesh): - mesh.compute_normals( - cell_normals=False, - consistent_normals=False, - non_manifold_traversal=False, - inplace=True, - ) + if 'Normals' not in mesh.point_arrays: + mesh.compute_normals( + cell_normals=False, + consistent_normals=False, + non_manifold_traversal=False, + inplace=True, + ) def _add_mesh(plotter, *args, **kwargs): From 35fabcda553e4148f6bc768fd38541d10789380c Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Jul 2020 12:19:08 +0200 Subject: [PATCH 29/30] Remove _add_polydata_actor --- mne/viz/_brain/_brain.py | 14 +++++--------- mne/viz/backends/_pyvista.py | 16 +--------------- 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/mne/viz/_brain/_brain.py b/mne/viz/_brain/_brain.py index 5f6663e57c4..e7ac2e840b8 100644 --- a/mne/viz/_brain/_brain.py +++ b/mne/viz/_brain/_brain.py @@ -1153,8 +1153,7 @@ def set_time_point(self, time_idx): self._update() def update_glyphs(self, hemi, vectors): - from ..backends._pyvista import (_set_colormap_range, - _add_polydata_actor) + from ..backends._pyvista import _set_colormap_range hemi_data = self._data.get(hemi) if hemi_data is not None: vertices = hemi_data['vertices'] @@ -1177,14 +1176,11 @@ def update_glyphs(self, hemi, vectors): ) if polydata is not None: if hemi_data['glyph_mesh'] is None: - hemi_data['glyph_mesh'] = polydata - glyph_actor = _add_polydata_actor( - plotter=self._renderer.plotter, - polydata=polydata, - hide=True - ) - hemi_data['glyph_actor'] = glyph_actor + glyph_actor, _ = self._renderer.polydata(polydata) + glyph_actor.VisibilityOff() glyph_actor.GetProperty().SetLineWidth(2.) + hemi_data['glyph_actor'] = glyph_actor + hemi_data['glyph_mesh'] = polydata else: glyph_actor = hemi_data['glyph_actor'] glyph_mesh = hemi_data['glyph_mesh'] diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index b1d1314ac2f..44e71dbef10 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -217,7 +217,7 @@ def scene(self): def set_interactive(self): self.plotter.enable_terrain_style() - def polydata(self, mesh, color, opacity=1.0, normals=None, + 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): @@ -586,20 +586,6 @@ def _add_mesh(plotter, *args, **kwargs): return actor -def _add_polydata_actor(plotter, polydata, name=None, - hide=False): - mapper = vtk.vtkPolyDataMapper() - mapper.SetInputData(polydata) - - actor = vtk.vtkActor() - actor.SetMapper(mapper) - if hide: - actor.VisibilityOff() - - plotter.add_actor(actor, name=name) - return actor - - def _deg2rad(deg): return deg * np.pi / 180. From 568a3f5291137e6ffe429e4bd1b858f15adca932 Mon Sep 17 00:00:00 2001 From: Guillaume Favelier Date: Tue, 28 Jul 2020 12:23:22 +0200 Subject: [PATCH 30/30] Add some docstrings --- mne/viz/backends/_pyvista.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mne/viz/backends/_pyvista.py b/mne/viz/backends/_pyvista.py index 44e71dbef10..4405d1b5d08 100644 --- a/mne/viz/backends/_pyvista.py +++ b/mne/viz/backends/_pyvista.py @@ -563,6 +563,7 @@ def remove_mesh(self, mesh_data): def _compute_normals(mesh): + """Patch PyVista compute_normals.""" if 'Normals' not in mesh.point_arrays: mesh.compute_normals( cell_normals=False, @@ -573,6 +574,7 @@ def _compute_normals(mesh): def _add_mesh(plotter, *args, **kwargs): + """Patch PyVista add_mesh.""" _process_events(plotter) mesh = kwargs.get('mesh') if 'smooth_shading' in kwargs: