from pumapy.utilities.workspace import Workspace
from pumapy.utilities.isosurface import generate_isosurface
import pyvista as pv
import numpy as np
[docs]def render_volume(workspace, cutoff=None, solid_color=(1., 1., 1.), style='surface', origin=(0., 0., 0.),
window_size=(1920, 1200), opacity=1., background=(0.3, 0.3, 0.3), show_grid=True, plot_directly=True,
show_axes=True, show_outline=True, cmap='gray', add_to_plot=None, notebook=False):
""" Volume render using Pyvista Threshold filter
:param workspace: domain
:type workspace: Workspace or ndarray
:param cutoff: specifying the values to render
:type cutoff: tuple(int, int), optional
:param solid_color: if set to None, the material is colored by the matrix's values. Otherwise, a solid color can be specified (e.g. for white (1., 1., 1.))
:type solid_color: tuple(float, float, float), optional
:param style: specifying the representation style ('surface', 'edges', 'wireframe', 'points')
:type style: string, optional
:param origin: origin of the data as
:type origin: tuple(float, float, float), optional
:param window_size: with the popup window size
:type window_size: tuple(int, int), optional
:param opacity: opacity of volume
:type opacity: float, optional
:param background: color of the background from (0., 0., 0.) (black) to (1., 1., 1.) (white)
:type: background: tuple(float, float, float)
:param show_grid: show the grid with the size of the sides
:type show_grid: bool, optional
:param plot_directly: whether to return a Plotter object (to make further changes to it) or show the plot directly
:type plot_directly: bool, optional
:param show_axes: show orientation axis in the bottom left corner
:type show_axes: bool, optional
:param show_outline: show the bounding box outline of the domain
:type show_outline: bool, optional
:param cmap: matplotlib colormap to use (overwritten by solid_color if specified)
:type cmap: str, optional
:param add_to_plot: pass an already existing plotter object to add on top of this plot
:type add_to_plot: pyvista.Plotter, optional
:param notebook: plotting interactively in a jupyter notebook (overwrites show_grid to False)
:type notebook: bool, optional
:return: None is plot_directly is True, otherwise a plotter object
:rtype: pyvista.Plotter object or None
"""
if cutoff is None:
solid_color = None
r = Renderer(add_to_plot, "threshold", workspace, cutoff, solid_color, style, origin, window_size, opacity,
background, show_grid, plot_directly, show_axes, show_outline, cmap, None, notebook)
return r.render()
[docs]def render_contour(workspace, cutoff, solid_color=(1., 1., 1.), style='surface', origin=(0., 0., 0.),
window_size=(1920, 1200), opacity=1., background=(0.3, 0.3, 0.3), show_grid=True, plot_directly=True,
show_axes=True, show_outline=True, add_to_plot=None, notebook=False):
""" Contour render using Pyvista Contour filter
:param workspace: domain
:type workspace: Workspace or ndarray
:param cutoff: specifying the values at which the isosurface is created
:type cutoff: tuple(int, int)
:param solid_color: a solid color to color the surface (e.g. for white (1., 1., 1.))
:type solid_color: tuple(float, float, float), optional
:param style: specifying the representation style ('surface', 'edges', 'wireframe', 'points')
:type style: string, optional
:param origin: origin of the data as
:type origin: tuple(float, float, float), optional
:param window_size: with the popup window size
:type window_size: tuple(int, int), optional
:param opacity: opacity of contour
:type opacity: float, optional
:param background: color of the background from (0., 0., 0.) (black) to (1., 1., 1.) (white)
:type: background: tuple(float, float, float)
:param show_grid: show the grid with the size of the sides
:type show_grid: bool, optional
:param plot_directly: whether to return a Plotter object (to make further changes to it) or show the plot directly
:type plot_directly: bool, optional
:param show_axes: show orientation axis in the bottom left corner
:type show_axes: bool, optional
:param show_outline: show the bounding box outline of the domain
:type show_outline: bool, optional
:param add_to_plot: pass an already existing plotter object to add on top of this plot
:type add_to_plot: pyvista.Plotter, optional
:param notebook: plotting interactively in a jupyter notebook (overwrites show_grid to False)
:type notebook: bool, optional
:return: None is plot_directly is True, otherwise a plotter object
:rtype: pyvista.Plotter object or None
"""
r = Renderer(add_to_plot, "contour", workspace, cutoff, solid_color, style, origin, window_size, opacity,
background, show_grid, plot_directly, show_axes, show_outline, None, None, notebook)
return r.render()
[docs]def render_orientation(workspace, scale_factor=1., solid_color=(1., 1., 1.), style='surface', origin=(0., 0., 0.),
window_size=(1920, 1200), opacity=1., background=(0.3, 0.3, 0.3), show_grid=True,
plot_directly=True, show_axes=True, show_outline=True, add_to_plot=None, notebook=False):
""" Orientation render using Pyvista Glyph filter
:param workspace: domain
:type workspace: Workspace or ndarray
:param scale_factor: scale the arrows by a factor
:type scale_factor: float
:param solid_color: a solid color to color the surface (e.g. for white (1., 1., 1.))
:type solid_color: tuple(float, float, float), optional
:param style: specifying the representation style ('surface', 'edges', 'wireframe', 'points')
:type style: string, optional
:param origin: origin of the data as
:type origin: tuple(float, float, float), optional
:param window_size: with the popup window size
:type window_size: tuple(int, int), optional
:param opacity: opacity of arrows
:type opacity: float, optional
:param background: color of the background from (0., 0., 0.) (black) to (1., 1., 1.) (white)
:type: background: tuple(float, float, float)
:param show_grid: show the grid with the size of the sides
:type show_grid: bool, optional
:param plot_directly: whether to return a Plotter object (to make further changes to it) or show the plot directly
:type plot_directly: bool, optional
:param show_axes: show orientation axis in the bottom left corner
:type show_axes: bool, optional
:param show_outline: show the bounding box outline of the domain
:type show_outline: bool, optional
:param add_to_plot: pass an already existing plotter object to add on top of this plot
:type add_to_plot: pyvista.Plotter, optional
:param notebook: plotting interactively in a jupyter notebook (overwrites show_grid to False)
:type notebook: bool, optional
:return: None is plot_directly is True, otherwise a plotter object
:rtype: pyvista.Plotter object or None
"""
r = Renderer(add_to_plot, "glyph", workspace, None, solid_color, style, origin, window_size, opacity,
background, show_grid, plot_directly, show_axes, show_outline, None, scale_factor, notebook)
return r.render()
[docs]def render_contour_multiphase(workspace, cutoffs, solid_colors=None, style='surface', origin=(0., 0., 0.),
window_size=(1920, 1200), opacity=1., background=(0.3, 0.3, 0.3), show_grid=True,
plot_directly=True, show_axes=True, show_outline=True, add_to_plot=None, notebook=False):
""" Contour render for multi-phase materials using Pyvista
:param workspace: domain
:type workspace: Workspace or ndarray
:param cutoffs: n cutoffs is the number of materials. specifies the low and high cutoff ranges
:type cutoffs: tuple(tuple(int, int), tuple(int, int) ...)
:param solid_colors: solid colors to color the different phases' surface e.g. for white ((1., 1., 1.), (0., 0., 0.), ...)
:type solid_colors: tuple(tuple(float, float, float), tuple(float, float, float) ...), optional
:param style: specifying the representation style ('surface', 'edges', 'wireframe', 'points')
:type style: string, optional
:param origin: origin of the data as
:type origin: tuple(float, float, float), optional
:param window_size: with the popup window size
:type window_size: tuple(int, int), optional
:param opacity: opacity of contour
:type opacity: float, optional
:param background: color of the background from (0., 0., 0.) (black) to (1., 1., 1.) (white)
:type: background: tuple(float, float, float)
:param show_grid: show the grid with the size of the sides
:type show_grid: bool, optional
:param plot_directly: whether to return a Plotter object (to make further changes to it) or show the plot directly
:type plot_directly: bool, optional
:param show_axes: show orientation axis in the bottom left corner
:type show_axes: bool, optional
:param show_outline: show the bounding box outline of the domain
:type show_outline: bool, optional
:param add_to_plot: pass an already existing plotter object to add on top of this plot
:type add_to_plot: pyvista.Plotter, optional
:param notebook: plotting interactively in a jupyter notebook (overwrites show_grid to False)
:type notebook: bool, optional
:return: None is plot_directly is True, otherwise a plotter object
:rtype: pyvista.Plotter object or None
"""
if add_to_plot is None:
p = pv.Plotter(notebook=notebook)
else:
p = add_to_plot
if notebook: # observed problems in rendering with grid in jupyter
show_grid = False
for i, cutoff in enumerate(cutoffs):
if solid_colors is None or len(solid_colors) != len(cutoffs):
solid_color = tuple(np.random.rand(3))
else:
solid_color = solid_colors[i]
r = Renderer(p, "contour", workspace, cutoff, solid_color, style, origin, window_size, opacity,
background, show_grid, False, show_axes, show_outline, None, None, None)
p = r.render()
if plot_directly:
p.show()
else:
return p
[docs]class Renderer:
def __init__(self, existing_plot, filter_type, workspace, cutoff, solid_color, style, origin, window_size, opacity,
background, show_grid, plot_directly, show_axes, show_outline, cmap, scale_factor, notebook):
self.filter_type = filter_type
self.cutoff = cutoff
self.solid_color = solid_color
self.style = style
self.origin = origin
self.window_size = window_size
self.opacity = opacity
self.background = background
self.show_grid = show_grid
self.plot_directly = plot_directly
self.show_axes = show_axes
self.show_outline = show_outline
self.cmap = cmap
self.scale_factor = scale_factor
if isinstance(workspace, Workspace):
if self.filter_type == "glyph":
self.array = workspace.orientation
else:
self.array = workspace.matrix
elif isinstance(workspace, np.ndarray):
if self.filter_type == "glyph":
if not (workspace.ndim == 4 and workspace.shape[3] == 3):
raise Exception("For rendering orientation, need a numpy.ndarray of shape (X, Y, Z, 3)")
self.array = workspace
else:
raise Exception("Need to input either pumapy.Workspace or a numpy.ndarray")
if self.filter_type != "contour":
self.grid = pv.UniformGrid()
self.grid.origin = self.origin
self.filter = None
if existing_plot is None:
self.p = pv.Plotter(notebook=notebook)
else:
self.p = existing_plot
if notebook: # observed problems in rendering with grid in jupyter
self.show_grid = False
[docs] def render(self):
self.build_plotter()
if self.plot_directly:
self.p.show()
else:
return self.p
[docs] def build_plotter(self):
if self.style == 'edges':
show_edges = True
self.style = 'surface'
else:
show_edges = False
if self.filter_type == "threshold":
self.grid.dimensions = np.array(self.array.shape) + 1
self.grid.cell_arrays["values"] = self.array.flatten(order="F")
self.filter = self.grid.threshold(self.cutoff)
elif self.filter_type == "contour":
tri_mesh = generate_isosurface(self.array, self.cutoff)
tri_mesh.verts += self.origin
tri_mesh.create_mesh()
self.filter = tri_mesh.mesh
elif self.filter_type == "glyph":
self.grid.dimensions = np.array(self.array.shape[:3]) + 1
tmp = np.zeros((self.array[:, :, :, 0].size, 3), dtype=float)
for i in range(3):
tmp[:, i] = self.array[:, :, :, i].ravel(order='F')
self.grid.cell_arrays["scalars"] = np.linalg.norm(self.array, axis=3).ravel(order='F')
self.grid.cell_arrays["vectors"] = tmp
self.filter = self.grid.glyph(orient="vectors", scale="scalars", factor=self.scale_factor, geom=pv.Arrow())
self.p.add_mesh(self.filter, color=self.solid_color, show_edges=show_edges, style=self.style,
cmap=self.cmap, show_scalar_bar=False, opacity=self.opacity)
if self.cmap is not None and self.solid_color is None:
self.p.add_scalar_bar(title='', height=0.5, vertical=True, position_x=0.05, position_y=0.3)
self.p.background_color = self.background
self.p.window_size = self.window_size
if self.show_outline:
self.p.add_bounding_box(color="k")
if self.show_axes:
self.p.show_axes()
if self.show_grid:
self.p.show_grid()