Skip to content

Commit

Permalink
Add PlotterITK examples and documentation (pyvista#580)
Browse files Browse the repository at this point in the history
* no longer duplicating the mesh when scalars are added

* cleaned up docs

* added docs in earnest

* added tests and itk plotting

* added itkwidgets to requirements

* various code style and spelling fixes

* Update pyvista/plotting/itkplotter.py

Co-Authored-By: Bane Sullivan <[email protected]>

* checking version without using packaging

* fixed doc on meets_version

* removed unnecessary print

* using scooby.meets_version

* updated scooby min version

Co-authored-by: Bane Sullivan <[email protected]>
  • Loading branch information
akaszynski and banesullivan authored Feb 6, 2020
1 parent f0c1dbb commit d07b43f
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 41 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/plotting/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ Plotting

plotting
qt_plotting
itk_plotting
widgets
83 changes: 83 additions & 0 deletions docs/plotting/itk_plotting.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. _jupyter_ref:

PyVista Jupyter Notebook Integration
------------------------------------

PyVista has an interface for visualizing plots in Jupyter. The
``pyvista.PlotterITK`` class allows you interactively visualize a mesh
within a jupyter notebook. For those who prefer plotting within
jupyter, this is an great way of visualizing using ``VTK`` and
``pyvista``.

Special thanks to thewtex
.. _itkwidgets: https://github.com/InsightSoftwareConsortium/itkwidgets


Installation
~~~~~~~~~~~~
To use `PlotterITK` you'll need to install ``itkwidgets>=0.25.2``.
Follow the installation steps here:
.. _itkwidgets: https://github.com/InsightSoftwareConsortium/itkwidgets#installation

You can install everything with `pip` if you prefer not using conda,
but be sure your juptyerlab is up-to-date. If you encounter problems,
uninstall and reinstall jupyterlab using pip.


Example Plotting with ITKwidgets
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The following example shows how to create a simple plot that shows a
simple sphere.

.. code:: python
import pyvista as pv
# create a mesh and identify some scalars you wish to plot
mesh = pv.Sphere()
z = mesh.points[:, 2]
# Plot using the ITKplotter
pl = pv.PlotterITK()
pl.add_mesh(mesh, scalars=z, smooth_shading=True)
pl.show(True)
.. figure:: ../images/user-generated/itk_plotting_sphere.png
:width: 600pt

ITKwidgets with pyvista


For convenience, figures can also be plotted using the ``plot_itk`` function:

.. code:: python
import pyvista as pv
# create a mesh and identify some scalars you wish to plot
mesh = pv.Sphere()
z = mesh.points[:, 2]
# Plot using the ITKplotter
pv.plot_itk(mesh, scalars=z)
Additional binder examples can be found at:

.. _itkwidgets_binder: https://hub.gke.mybinder.org/user/insightsoftware-tium-itkwidgets-p2yw6xvh/lab

.. rubric:: Attributes

.. autoautosummary:: pyvista.PlotterITK
:attributes:

.. rubric:: Methods

.. autoautosummary:: pyvista.PlotterITK
:methods:

.. autoclass:: pyvista.PlotterITK
:members:
:undoc-members:
:show-inheritance:
2 changes: 1 addition & 1 deletion examples/02-plot/depth-peeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
This is enabled by default in :any:`pyvista.rcParams`.
For this example, we will showcase the difference that depth peeling provides
as the justification for why we have enabled this by defualt.
as the justification for why we have enabled this by default.
"""
# sphinx_gallery_thumbnail_number = 2
Expand Down
2 changes: 1 addition & 1 deletion pyvista/plotting/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
from .renderer import CameraPosition, Renderer, scale_point
from .plotting import BasePlotter, Plotter, close_all
from .qt_plotting import BackgroundPlotter, QtInteractor, MainWindow, Counter
from .helpers import plot, plot_arrows, plot_compare_four
from .helpers import plot, plot_arrows, plot_compare_four, plot_itk
from .itkplotter import PlotterITK
51 changes: 51 additions & 0 deletions pyvista/plotting/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,54 @@ def plot_compare_four(data_a, data_b, data_c, data_d, disply_kwargs=None,
p.link_views()

return p.show(screenshot=screenshot, **show_kwargs)


def plot_itk(mesh, color=None, scalars=None, opacity=1.0,
smooth_shading=False):
"""Plot a PyVista/VTK mesh or dataset.
Adds any PyVista/VTK mesh that itkwidgets can wrap to the
scene.
Parameters
----------
mesh : pyvista.Common or pyvista.MultiBlock
Any PyVista or VTK mesh is supported. Also, any dataset that
:func:`pyvista.wrap` can handle including NumPy arrays of XYZ
points.
color : string or 3 item list, optional, defaults to white
Use to make the entire mesh have a single solid color. Either
a string, RGB list, or hex color string. For example:
``color='white'``, ``color='w'``, ``color=[1, 1, 1]``, or
``color='#FFFFFF'``. Color will be overridden if scalars are
specified.
scalars : str or numpy.ndarray, optional
Scalars used to "color" the mesh. Accepts a string name of an
array that is present on the mesh or an array equal to the
number of cells or the number of points in the mesh. Array
should be sized as a single vector. If both ``color`` and
``scalars`` are ``None``, then the active scalars are used.
opacity : float, optional
Opacity of the mesh. If a single float value is given, it will
be the global opacity of the mesh and uniformly applied
everywhere - should be between 0 and 1. Default 1.0
smooth_shading : bool, optional
Smooth mesh surface mesh by taking into account surface
normals. Surface will appear smoother while sharp edges will
still look sharp. Default False.
Returns
--------
plotter : itkwidgets.Viewer
ITKwidgets viewer.
"""
pl = pyvista.PlotterITK()
if isinstance(mesh, np.ndarray):
pl.add_points(mesh, color)
else:
pl.add_mesh(mesh, color, scalars, opacity, smooth_shading)
return pl.show()
160 changes: 128 additions & 32 deletions pyvista/plotting/itkplotter.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
"""PyVista-like ITKwidgets plotter.
This is super experimental: use with caution.
"""

"""PyVista-like ITKwidgets plotter."""
import numpy as np
from scooby import meets_version
import pyvista as pv

HAS_ITK = False
Expand All @@ -13,32 +11,67 @@
except ImportError:
pass


class PlotterITK():
"""An EXPERIMENTAL interface for plotting in Jupyter notebooks.
"""ITKwidgets plotter.
Used for plotting interactively within a jupyter notebook.
Requires ``itkwidgets>=0.25.2``. For installation see:
Use with caution, this is an experimental/demo feature. This creates an
interface for 3D rendering with ``itkwidgets`` just like the
:class:`pyvista.Plotter` class.
https://github.com/InsightSoftwareConsortium/itkwidgets#installation
Examples
--------
>>> import pyvista
>>> mesh = pyvista.Sphere()
>>> pl = pyvista.PlotterITK() # doctest:+SKIP
>>> pl.add_mesh(mesh, color='w') # doctest:+SKIP
>>> pl.show() # doctest:+SKIP
"""

def __init__(self, **kwargs):
"""Initialize the itkwidgets plotter."""
itk_import_err = ImportError("Please install `itkwidgets>=0.25.2`")
if not HAS_ITK:
raise ImportError("Please install `itkwidgets`.")
raise itk_import_err

from itkwidgets import __version__
if not meets_version(__version__, "0.25.2"):
raise itk_import_err

self._actors = []
self._point_sets = []
self._geometries = []
self._geometry_colors = []
self._geometry_opacities = []
self._cmap = 'Viridis (matplotlib)'
self._point_set_colors = []

def add_actor(self, actor):
"""Append internal list of actors."""
"""Add an actor to the plotter.
Parameters
----------
uinput : vtk.vtkActor
vtk actor to be added.
"""
self._actors.append(actor)

def add_points(self, points, color=None):
"""Add XYZ points to the scene."""
"""Add points to plotting object.
Parameters
----------
points : np.ndarray or pyvista.Common
n x 3 numpy array of points or pyvista dataset with points.
color : string or 3 item list, optional. Color of points (if visible).
Either a string, rgb list, or hex color string. For example:
color='white'
color='w'
color=[1, 1, 1]
color='#FFFFFF'
"""
if pv.is_pyvista_dataset(points):
point_array = points.points
else:
Expand All @@ -47,42 +80,105 @@ def add_points(self, points, color=None):
self._point_set_colors.append(pv.parse_color(color))
self._point_sets.append(point_array)

def add_mesh(self, mesh, color=None, scalars=None, clim=None,
opacity=1.0, n_colors=256, cmap='Viridis (matplotlib)',
**kwargs):
"""Add mesh to the scene."""
def add_mesh(self, mesh, color=None, scalars=None,
opacity=1.0, smooth_shading=False):
"""Add a PyVista/VTK mesh or dataset.
Adds any PyVista/VTK mesh that itkwidgets can wrap to the
scene.
Parameters
----------
mesh : pyvista.Common or pyvista.MultiBlock
Any PyVista or VTK mesh is supported. Also, any dataset
that :func:`pyvista.wrap` can handle including NumPy arrays of XYZ
points.
color : string or 3 item list, optional, defaults to white
Use to make the entire mesh have a single solid color.
Either a string, RGB list, or hex color string. For example:
``color='white'``, ``color='w'``, ``color=[1, 1, 1]``, or
``color='#FFFFFF'``. Color will be overridden if scalars are
specified.
scalars : str or numpy.ndarray, optional
Scalars used to "color" the mesh. Accepts a string name of an
array that is present on the mesh or an array equal
to the number of cells or the number of points in the
mesh. Array should be sized as a single vector. If both
``color`` and ``scalars`` are ``None``, then the active scalars are
used.
opacity : float, optional
Opacity of the mesh. If a single float value is given, it will be
the global opacity of the mesh and uniformly applied everywhere -
should be between 0 and 1. Default 1.0
smooth_shading : bool, optional
Smooth mesh surface mesh by taking into account surface
normals. Surface will appear smoother while sharp edges
will still look sharp. Default False.
"""
if not pv.is_pyvista_dataset(mesh):
mesh = pv.wrap(mesh)
mesh = mesh.copy()
if scalars is None and color is None:
scalars = mesh.active_scalars_name

if scalars is not None:
array = mesh[scalars].copy()
mesh.clear_arrays()
# smooth shading requires point normals to be freshly computed
if smooth_shading:
# extract surface if mesh is exterior
if not isinstance(mesh, pv.PolyData):
grid = mesh
mesh = grid.extract_surface()
ind = mesh.point_arrays['vtkOriginalPointIds']
# remap scalars
if isinstance(scalars, np.ndarray):
scalars = scalars[ind]

mesh.compute_normals(cell_normals=False, inplace=True)
elif 'Normals' in mesh.point_arrays:
# if 'normals' in mesh.point_arrays:
mesh.point_arrays.pop('Normals')

# make the scalars active
if isinstance(scalars, str):
if scalars in mesh.point_arrays or scalars in mesh.cell_arrays:
array = mesh[scalars].copy()
else:
raise ValueError('Scalars ({}) not in mesh'.format(scalars))
mesh[scalars] = array
mesh.active_scalars_name = scalars
elif isinstance(scalars, np.ndarray):
array = scalars
scalar_name = '_scalars'
mesh[scalar_name] = array
mesh.active_scalars_name = scalar_name
elif color is not None:
mesh.clear_arrays()

mesh.active_scalars_name = None

mesh = to_geometry(mesh)
self._geometries.append(mesh)
self._geometry_colors.append(pv.parse_color(color))
self._geometry_opacities.append(opacity)
self._cmap = cmap

return


def show(self, ui_collapsed=False):
"""Show in cell output."""
"""Show itkwidgets plotter in cell output.
Parameters
----------
ui_collapsed : bool, optional
Plot with the user interface collapsed. UI can be enabled
when plotting. Default False.
Returns
--------
plotter : itkwidgets.Viewer
ITKwidgets viewer.
"""
plotter = Viewer(geometries=self._geometries,
geometry_colors=self._geometry_colors,
geometry_opacities=self._geometry_opacities,
point_set_colors=self._point_set_colors,
point_sets=self._point_sets,
ui_collapsed=ui_collapsed,
actors=self._actors,
cmap=self._cmap)
actors=self._actors)
return plotter
2 changes: 1 addition & 1 deletion pyvista/utilities/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def __init__(self, additional=None, ncol=3, text_width=80, sort=False,
optional = ['matplotlib', 'PyQt5', 'IPython', 'colorcet',
'cmocean', 'panel']

# Information about the GPU - bare except incase there is a rendering
# Information about the GPU - bare except in case there is a rendering
# bug that the user is trying to report.
if gpu:
try:
Expand Down
2 changes: 1 addition & 1 deletion pyvista/utilities/fileio.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ def read(filename, attrs=None, file_format=None):
filename : str
The string path to the file to read. If a list of files is given,
a :class:`pyvista.MultiBlock` dataset is returned with each file being
a seperate block in the dataset.
a separate block in the dataset.
attrs : dict, optional
A dictionary of attributes to call on the reader. Keys of dictionary are
the attribute/method names and values are the arguments passed to those
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ imageio>=2.5.0
imageio-ffmpeg
colorcet
cmocean
scooby>=0.5.0
scooby>=0.5.1
meshio>=3.3.0
itkwidgets>=0.25.2
Loading

0 comments on commit d07b43f

Please sign in to comment.