PyVista Visualization (plot3d)¶
The PyVista backend is the 3-D visualization API for placing 2-D slices in 3-D space, rendering 3-D scalar fields as volume / slice / contour views, and drawing 3-D vector fields as streamlines or quiver arrows. Use the regular plot() / cmap() / contour() APIs from Plotting for ordinary 1-D/2-D analysis, and switch to PyVista when you need interactive 3-D camera control or overlays.
Choosing an Entry Point¶
Target |
Recommended API |
Return value |
|---|---|---|
2D scalar slice |
|
|
3D scalar volume |
|
|
3D vector field |
|
|
Boundary meshes |
|
|
Backtrace / trace paths |
|
|
Mesh construction only |
|
PyVista mesh object |
Data2d.plot3d() and Data3d.plot3d() render with PyVista. VectorData.plot3d() also uses the PyVista backend by default; pass backend="mpl" when you want the Matplotlib 3-D backend.
Installation¶
PyVista is installed as a regular emout dependency, so no extra selector is needed for 3-D views.
pip install emout
If an old environment or editable install raises ModuleNotFoundError, reinstall emout so the dependency set is refreshed.
Scalar Fields¶
Place a 2D Slice in 3D Space¶
A 2-D slice is rendered as a pyvista.StructuredGrid plane. plot3d() returns the plotter, so you can adjust the camera or take screenshots afterwards.
import emout
data = emout.Emout("output_dir")
plotter = data.phisp[-1, 100, :, :].plot3d(
cmap="viridis",
clim=(-20, 20),
show_edges=False,
show=False,
)
plotter.show()
Render a 3D Volume¶
Use mode to choose how a 3-D scalar field is rendered.
|
Rendering |
Main options |
|---|---|---|
|
outer surface |
|
|
volume rendering |
|
|
orthogonal slices |
|
|
iso-surfaces |
|
data.phisp[-1].plot3d(mode="box", opacity=0.4, show=True)
data.phisp[-1].plot3d(mode="volume", opacity="sigmoid", show=True)
data.phisp[-1].plot3d(mode="slice", cmap="coolwarm", clim=(-50, 50), show=True)
data.phisp[-1].plot3d(mode="contour", contour_levels=12, show=True)
To save an image, create the plotter with show=False and use PyVista’s screenshot().
plotter = data.phisp[-1].plot3d(mode="contour", contour_levels=10, show=False)
plotter.screenshot("phisp_contour.png")
plotter.close()
Pass filename= when you want plot3d() to save the result directly. Image extensions such as .png are saved as screenshots. .html is saved as interactive HTML when the PyVista Jupyter/trame extras are installed. savefilename= is also accepted as a compatibility alias matching the 2-D plot API.
data.phisp[-1].plot3d(mode="contour", levels=[0.0], filename="phisp_iso.png")
data.j1xyz[-1].plot3d(mode="stream", filename="j1_stream.png")
Vector Fields¶
A three-component VectorData can draw streamlines with mode="stream" / "streamline" or glyph arrows with mode="quiver" / "vec".
data.j1xyz[-1].plot3d(
mode="stream",
backend="pyvista",
n_points=300,
tube_radius=0.02,
show=True,
)
data.j1xyz[-1].plot3d(
mode="quiver",
backend="pyvista",
skip=(3, 3, 2),
factor=0.4,
show=True,
)
Choose streamline seeds with seed_mode. The default sphere mode starts near the centre. plane often gives a view similar to 2-D streamlines, volume seeds through the whole domain, and surface starts from boundary meshes. Pass seed_points when you want fixed custom start points. Quiver arrows are down-sampled with skip and scaled with factor.
data.j1xyz[-1].plot3d(seed_mode="plane", seed_plane="xz", seed_position="center")
data.j1xyz[-1].plot3d(seed_mode="volume", n_points=1000)
data.j1xyz[-1].plot3d(seed_mode="surface", seed_surface=data.boundaries)
data.j1xyz[-1].plot3d(seed_points=[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]])
Overlays¶
Add Layers to the Same Plotter¶
Every PyVista API accepts plotter=. Add multiple layers to the same plotter to combine scalar slices, volume outlines, and vector streamlines in one 3-D scene.
plotter = data.phisp[-1].plot3d(
mode="slice",
cmap="coolwarm",
clim=(-50, 50),
show=False,
)
data.j1xyz[-1].plot3d(
mode="stream",
backend="pyvista",
plotter=plotter,
tube_radius=0.02,
color="white",
show=True,
)
Boundary meshes can be added to the same plotter. data.boundaries.plot3d() draws the whole collection, data.boundaries[0].plot3d() draws one boundary, and data.boundaries.mesh().plot3d() draws an already built MeshSurface3D.
plotter = data.phisp[-1].plot3d(mode="slice", show=False)
data.boundaries.plot3d(
plotter=plotter,
color="0.7",
opacity=0.35,
show_edges=True,
show=True,
)
Pass boundary mesh-construction arguments via mesh_kwargs, and per-boundary overrides via per. See Boundary Meshes for the Matplotlib / plot_surfaces() path.
Overlay Backtrace / Trace Paths¶
Results from data.trace.*(..., get_trace=True) have plot3d() and can add trajectories to an existing plotter. When probabilities are available, alpha="auto" uses the per-trajectory probability as opacity.
trace = data.trace.forward(
x=20.0, y=32.0, z=40.0,
vx=(-5.0, 5.0, 16),
vy=0.0,
vz=(-5.0, 5.0, 16),
get_trace=True,
)
plotter = data.phisp[-1].plot3d(mode="slice", show=False)
trace.plot3d(
plotter=plotter,
direction="forward",
tube_radius=0.05,
color="black",
show=True,
)
If you build traces with get_probabilities=False, probability-derived alpha is unavailable. Pass an explicit value such as alpha=0.3 when needed.
Ticks, Cameras, and Trajectory Display¶
Scalar, vector, boundary, and trace plot3d() calls accept show_bounds,
bounds_kwargs, camera, and camera_position. bounds_kwargs forwards
font sizes, tick counts, and other settings to PyVista’s show_bounds();
emout supplies unit-aware labels. Select camera="isometric", "low", or
"side-low", or set camera_position=(position, focal_point, view_up).
Do not supply both camera and camera_position.
For scalar and vector colour bars, use settings such as
scalar_bar_args={"vertical": True, "position_x": 0.82} to adjust placement and fonts.
For trajectories, min_alpha sets an opacity floor. alpha_scale="normalize"
divides by the maximum, while alpha_scale="log" maps the logarithms of
positive values to 0–1. These affect display only; probability arrays are unchanged.
The defaults are min_alpha=0 and alpha_scale="linear".
direction="both" explicitly selects both directions, with optional colours
for each direction. clip_bounds=(xmin, xmax, ymin, ymax, zmin, zmax) uses
display coordinates after SI conversion and offsets. Crossing segments are
trimmed at box faces, and paths separated by excluded regions remain disconnected.
The following example assumes boundary index 0 is flat-surface and trace
has already been computed. It samples potential on the boundaries and overlays
everything in grid coordinates. Adjust the per index and height limit for
other boundary configurations.
import pyvista as pv
from emout.plot.pyvista_plot import create_volume_mesh
volume, name, _, label = create_volume_mesh(data.phisp[-1], use_si=False)
surface = data.boundaries.mesh(use_si=False, per={0: {"resolution": "grid"}})
coloured = surface.to_pyvista().sample(volume)
plotter = pv.Plotter(off_screen=True)
plotter.add_mesh(coloured, scalars=name, cmap="coolwarm", scalar_bar_args={"title": label})
trace.plot3d(
plotter=plotter, direction="both", use_si=False,
backward_color="blue", forward_color="red",
alpha_scale="normalize", min_alpha=0.08,
clip_bounds=(0, data.inp.nx, 0, data.inp.ny, 0, min(data.inp.nz, data.inp.zssurf + 20)),
show_bounds=True, bounds_kwargs={"font_size": 12, "n_xlabels": 4}, camera="low",
)
plotter.screenshot("boundary_trace.png")
plotter.close()
Units and Axis Order¶
Grid-data axis order is (t, z, y, x), and a 3-D volume is (z, y, x). The PyVista helpers read that order and rearrange coordinates into PyVista’s (x, y, z) convention.
use_si=True is the default. When unit metadata is available, coordinates and values are converted to SI units. If unit metadata is absent, passing use_si=True still renders grid / raw values internally.
offsets=(x_offset, y_offset, z_offset) accepts numbers or "left" / "center" / "right". Use the same use_si and offsets values across all layers in an overlay.
Remote Execution and HPC¶
PyVista rendering creates a plotter in the local Python process. data.remote() / remote_figure() target Matplotlib image rendering, so they do not keep a Data3d.plot3d() PyVista scene on the worker for repeated redraws.
On KUDPC and other login-node environments, do not run heavy PyVista rendering or large 3-D data reads directly on the login node. Route them to a compute node or a supported visualization environment. In scripts, show=False plus screenshot() is usually easier to run in batch.
plotter = data.phisp[-1].plot3d(mode="slice", show=False)
plotter.screenshot("phisp_slice.png")
plotter.close()
Low-Level Helpers¶
Usually you should use plot3d(). Use helpers from emout.plot.pyvista_plot only when you want to modify the PyVista mesh yourself.
from emout.plot.pyvista_plot import (
create_plane_mesh,
create_surface_mesh,
create_volume_mesh,
create_vector_mesh3d,
)
mesh, scalar_name, axis_labels, scalar_label = create_volume_mesh(data.phisp[-1])
Low-level helpers return PyVista objects directly; they are not high-level emout remote-rendering or article-recording APIs.
Common Errors¶
Symptom |
Cause |
Fix |
|---|---|---|
|
old environment or dependencies not refreshed |
|
|
the 2-D slice still includes |
fix time to a single index and pass a spatial 2-D slice |
|
the 3-D spatial axes are not all present |
select only time, e.g. |
no streamlines appear |
too few seeds or too small a seed radius |
increase |
layers are offset |
different layers use different |
use the same values for every layer |