VTK#
Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).
import panel as pn
pn.extension('vtk')
The VTK
pane renders a VTK scene inside a panel, making it possible to interact with complex geometries in 3D.
It allows keeping state synchronized between the vtkRenderWindow
defined on the python side and the one displayed in the pane through vtk-js.
Here Python acts as a server, sending information about the scene to the client.
The synchronization is done in only one direction: Python => JavaScript. Modifications done on the JavaScript side are not reflected back to the Python vtkRenderWindow
.
Parameters:#
For details on other options for customizing the component see the layout and styling how-to guides.
axes
(dict): A dictionary with the parameters of the axes to construct in the 3d view. Must contain at leastxticker
,yticker
andzticker
.[x|y|z]ticker
is a dictionary which contains:ticks
(array of numbers) - required. Positions in the scene coordinates of the corresponding axe tickslabels
(array of strings) - optional. Label displayed respectively to theticks
positions.
If
labels
are not defined they are inferred from theticks
array.digits
: number of decimal digits whenticks
are converted tolabels
.fontsize
: size in pts of the ticks labels.show_grid
: boolean. If true (default) the axes grid is visible.grid_opacity
: float between 0-1. Defines the grid opacity.axes_opacity
: float between 0-1. Defines the axes lines opacity.
camera
(dict): A dictionary reflecting the current state of the VTK cameraenable_keybindings
(bool): A boolean to activate/deactivate keybindings. Bound keys are:s: set representation of all actors to surface
w: set representation of all actors to wireframe
v: set representation of all actors to vertex
r: center the actors and move the camera so that all actors are visible
The mouse must be over the pane to work
Warning: These keybindings may not work as expected in a notebook context, if they interact with already bound keysorientation_widget
(bool): A boolean to activate/deactivate the orientation widget in the 3D pane.ìnteractive_orientation_widget
(bool): If True the orientation widget is clickable and allows to rotate the scene in one of the orthographic projections.
Warning: if set to True, synchronization capabilities ofVTKRenderWindowSynchronized
panes could not work.object
(object): Must be avtkRenderWindow
instance.
Properties:#
actors
: return the list of the vtkActors in the scenevtk_camera
: return the vtkCamera of the renderer held by the panel
Methods:#
Several helpers to work with the VTK scene are exposed:
set_background(r: float, g: float, b: float)
: Set the color of the scene background as an RGB color, wherer
,g
, andb
are floats in the range (0, 1)synchronize()
: Synchronize the Python-sidevtkRenderWindow
object state with JavaScriptunlink_camera()
: Create a new vtkCamera object, allowing the panel to have its own camera.link_camera(other: VTK)
: Set both panels to share the same camera; any change in either panel’s camera will be applied to the other
Warning after using any of these methods to modify the state of vtkRenderWindow, the synchronize()
method must be called for those changes to affect the display
Rendering VTK objects#
Compared to working with VTK
directly, there are some differences when using it in Panel. As rendering of the objects and interactions with the view are handled by the VTK panel, we do not need to make a call to the Render
method of the vtkRenderWindow
(which would pop up the classical VTK window), nor do we need to specify a vtkRenderWindowInteractor
.
import vtk
from vtk.util.colors import tomato
# This creates a polygonal cylinder model with eight circumferential
# facets.
cylinder = vtk.vtkCylinderSource()
cylinder.SetResolution(8)
# The mapper is responsible for pushing the geometry into the graphics
# library. It may also do color mapping, if scalars or other
# attributes are defined.
cylinderMapper = vtk.vtkPolyDataMapper()
cylinderMapper.SetInputConnection(cylinder.GetOutputPort())
# The actor is a grouping mechanism: besides the geometry (mapper), it
# also has a property, transformation matrix, and/or texture map.
# Here we set its color and rotate it -22.5 degrees.
cylinderActor = vtk.vtkActor()
cylinderActor.SetMapper(cylinderMapper)
cylinderActor.GetProperty().SetColor(tomato)
# We must set ScalarVisibilty to 0 to use tomato Color
cylinderMapper.SetScalarVisibility(0)
cylinderActor.RotateX(30.0)
cylinderActor.RotateY(-45.0)
# Create the graphics structure. The renderer renders into the render
# window.
ren = vtk.vtkRenderer()
renWin = vtk.vtkRenderWindow()
renWin.AddRenderer(ren)
# Add the actors to the renderer, set the background and size
ren.AddActor(cylinderActor)
ren.SetBackground(0.1, 0.2, 0.4)
geom_pane = pn.pane.VTK(renWin, width=500, height=500)
geom_pane
We can also add additional actors to the plot, then call the synchronize
method to update the panel:
sphere = vtk.vtkSphereSource()
sphereMapper = vtk.vtkPolyDataMapper()
sphereMapper.SetInputConnection(sphere.GetOutputPort())
sphereActor = vtk.vtkActor()
sphereActor.SetMapper(sphereMapper)
sphereActor.GetProperty().SetColor(tomato)
sphereMapper.SetScalarVisibility(0)
sphereActor.SetPosition(0.5, 0.5, 0.5)
ren.AddActor(sphereActor)
geom_pane.reset_camera()
geom_pane.synchronize()
Working with PyVista
#
Most of these examples use the PyVista library as a convenient interface to VTK.
Though these examples can generally be rewritten to depend only on VTK itself, pyvista
supports a Pythonic and concise syntax for the main features needed to work with VTK objects.
For instance, the VTK example above can be rewritten using PyVista as follows:
Link PyVista and Panel:
import pyvista as pv
plotter = pv.Plotter() # we define a pyvista plotter
plotter.background_color = (0.1, 0.2, 0.4)
# we create a `VTK` panel around the render window
geo_pan_pv = pn.panel(plotter.ren_win, width=500, height=500)
Create and add some objects to the PyVista plotter, displaying them using the VTK panel
pvcylinder = pv.Cylinder(resolution=8, direction=(0,1,0))
cylinder_actor = plotter.add_mesh(pvcylinder, color=tomato, smooth_shading=True)
cylinder_actor.RotateX(30.0)
cylinder_actor.RotateY(-45.0)
sphere_actor = plotter.add_mesh(pv.Sphere(
theta_resolution=8, phi_resolution=8,
center=(0.5, 0.5, 0.5)),color=tomato, smooth_shading=True
)
# we can link camera of both panes
geom_pane.jslink(geo_pan_pv, camera='camera', bidirectional=True)
pn.Row(geom_pane, geo_pan_pv)
Export the scene#
The scene can be exported and the file generated can be loaded by the official vtk-js scene importer:
import os
filename = geo_pan_pv.export_scene(filename='geo_example')
print("The file is exported in :", os.path.join(os.getcwd(), filename))
pn.pane.HTML('<iframe width=100% height=400 src=https://kitware.github.io/vtk-js/examples/SynchronizableRenderWindow/index.html></iframe>', sizing_mode='stretch_width')
The file is exported in : /Users/runner/work/panel/panel/geo_example.synch
Advanced usage and interactivity#
This examples will show :
where information about
scalars
used to color 3D objects can be found in the sceneutilization of the
orientation widget
how to add
axes
in the sceneadding a
player
widget to create an animation
Scene creation#
First we create a scene with a nice spline inspired by this examples : Creating a Spline
import numpy as np
def make_points():
"""Helper to make XYZ points"""
theta = np.linspace(-4 * np.pi, 4 * np.pi, 100)
z = np.linspace(-2, 2, 100)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
return np.column_stack((x, y, z))
def lines_from_points(points):
"""Given an array of points, make a line set"""
poly = pv.PolyData()
poly.points = points
cells = np.full((len(points)-1, 3), 2, dtype=np.int32)
cells[:, 1] = np.arange(0, len(points)-1, dtype=np.int32)
cells[:, 2] = np.arange(1, len(points), dtype=np.int32)
poly.lines = cells
return poly
points = make_points()
line = lines_from_points(points)
line["scalars"] = np.arange(line.n_points) # By default pyvista use viridis colormap
tube = line.tube(radius=0.1) #=> the object we will represent in the scene
pl = pv.Plotter()
pl.camera_position = [(4.440892098500626e-16, -21.75168228149414, 4.258553981781006),
(4.440892098500626e-16, 0.8581731039809382, 0),
(0, 0.1850949078798294, 0.982720673084259)]
pl.add_mesh(tube, smooth_shading=True)
spline_pan = pn.panel(pl.ren_win, width=500, orientation_widget=True)
spline_pan
By clicking on the three dots, a colorbar can be displayed
The orientation widget on the bottom right of the panel is clickable too, allowing you to orient the camera in a specific direction.
Adding axes to the VTK scene#
Using the axes
parameter, it is possible to display axes in the 3D view.
The Axes
object (like orientation widget
and colorbar
) is a client-side object instantiated only on the JavaScript side, and is not synchronized back to the Python vtkRenderWindow
.
axes = dict(
origin = [-5, 5, -2],
xticker = {'ticks': np.linspace(-5,5,5)},
yticker = {'ticks': np.linspace(-5,5,5)},
zticker = {'ticks': np.linspace(-2,2,5), 'labels': [''] + [str(int(item)) for item in np.linspace(-2,2,5)[1:]]},
fontsize = 12,
digits = 1,
grid_opacity = 0.5,
show_grid=True
)
spline_pan.axes = axes
Working with Volumes#
All previous panes use geometric examples, but VTK
can work with both geometric and volume objects:
import pyvista.examples as examples
head = examples.download_head()
plotter = pv.Plotter()
plotter.camera_position = [
[565, -340, 219],
[47, 112, 52],
[0, 0, 1],
]
plotter.add_volume(head)
volume_pan = pn.panel(plotter.ren_win, width=500, orientation_widget=True)
volume_pan
As for other examples, we can add easily new objects in the pane.
For example, we can create 3 orthogonal slices and add them to the scene:
actor, mapper = plotter.add_mesh(head.slice_orthogonal(x=100, y=120, z=30))
volume_pan.synchronize()
Traceback (most recent call last): File "/Users/runner/work/panel/panel/panel/io/mime_render.py", line 163, in exec_with_return exec(compile(init_ast, "<ast>", "exec"), global_context) File "<ast>", line 1, in <module> TypeError: cannot unpack non-iterable Actor object
Create an animation#
We will reuse the panel used for the spline.
For this animation, we will rotate the actor and roll the scalars on the spline tube:
pan_clone = spline_pan.clone() # we clone the panel to animate only this panel
pan_clone.unlink_camera() # we don't want to share the camera with the previous panel
player = pn.widgets.Player(name='Player', start=0, end=100, value=0, loop_policy='reflect', interval=100)
scalars = tube["scalars"]
def animate(value):
tube["scalars"] = np.roll(scalars, 200*value)
pan_clone.actors[0].SetOrientation(0, 0, -20*value)
pan_clone.synchronize()
pn.Column(pan_clone, player, pn.bind(animate, player)).servable()
Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).