init
This commit is contained in:
@@ -0,0 +1,780 @@
|
||||
"""Module containing useful plotting tools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
from subprocess import PIPE
|
||||
from subprocess import Popen
|
||||
from subprocess import TimeoutExpired
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
|
||||
from . import _vtk
|
||||
from .colors import Color
|
||||
|
||||
|
||||
class FONTS(Enum):
|
||||
"""Font families available to PyVista."""
|
||||
|
||||
arial = _vtk.VTK_ARIAL
|
||||
courier = _vtk.VTK_COURIER
|
||||
times = _vtk.VTK_TIMES
|
||||
|
||||
|
||||
# Track render window support and plotting
|
||||
SUPPORTS_OPENGL = None
|
||||
SUPPORTS_PLOTTING = None
|
||||
|
||||
|
||||
def supports_open_gl():
|
||||
"""Return if the system supports OpenGL.
|
||||
|
||||
This function checks if the system supports OpenGL by creating a VTK render
|
||||
window and querying its OpenGL support.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` if the system supports OpenGL, ``False`` otherwise.
|
||||
|
||||
"""
|
||||
global SUPPORTS_OPENGL # noqa: PLW0603
|
||||
if SUPPORTS_OPENGL is None:
|
||||
ren_win = _vtk.vtkRenderWindow()
|
||||
SUPPORTS_OPENGL = bool(ren_win.SupportsOpenGL())
|
||||
return SUPPORTS_OPENGL
|
||||
|
||||
|
||||
def _system_supports_plotting(): # noqa: PLR0911
|
||||
"""Check if the environment supports plotting on Windows, Linux, or Mac OS.
|
||||
|
||||
Returns
|
||||
-------
|
||||
system_supports_plotting : bool
|
||||
``True`` when system supports plotting.
|
||||
|
||||
"""
|
||||
if os.environ.get('ALLOW_PLOTTING', '').lower() == 'true':
|
||||
return True
|
||||
|
||||
# Windows case
|
||||
if os.name == 'nt':
|
||||
# actually have to check here. Somewhat expensive.
|
||||
return supports_open_gl()
|
||||
|
||||
# mac case
|
||||
if platform.system() == 'Darwin':
|
||||
# check if finder available
|
||||
proc = Popen(['pgrep', '-qx', 'Finder'], stdout=PIPE, stderr=PIPE, encoding='utf8')
|
||||
try:
|
||||
proc.communicate(timeout=10)
|
||||
except TimeoutExpired:
|
||||
return False
|
||||
if proc.returncode == 0:
|
||||
return True
|
||||
|
||||
# display variable set, likely available
|
||||
return 'DISPLAY' in os.environ
|
||||
|
||||
# Linux case
|
||||
try:
|
||||
proc = Popen(['xset', '-q'], stdout=PIPE, stderr=PIPE, encoding='utf8')
|
||||
proc.communicate(timeout=10)
|
||||
except (OSError, TimeoutExpired):
|
||||
return False
|
||||
else: # pragma: no cover
|
||||
return proc.returncode == 0
|
||||
|
||||
|
||||
def system_supports_plotting():
|
||||
"""Check if the environment supports plotting.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` when system supports plotting.
|
||||
|
||||
"""
|
||||
global SUPPORTS_PLOTTING # noqa: PLW0603
|
||||
if SUPPORTS_PLOTTING is None:
|
||||
SUPPORTS_PLOTTING = _system_supports_plotting()
|
||||
|
||||
# always use the cached response
|
||||
return SUPPORTS_PLOTTING
|
||||
|
||||
|
||||
def _update_axes_label_color(axes_actor, color=None):
|
||||
"""Set the axes label color (internal helper)."""
|
||||
color = Color(color, default_color=pyvista.global_theme.font.color)
|
||||
if isinstance(axes_actor, _vtk.vtkAxesActor):
|
||||
prop_x = axes_actor.GetXAxisCaptionActor2D().GetCaptionTextProperty()
|
||||
prop_y = axes_actor.GetYAxisCaptionActor2D().GetCaptionTextProperty()
|
||||
prop_z = axes_actor.GetZAxisCaptionActor2D().GetCaptionTextProperty()
|
||||
for prop in [prop_x, prop_y, prop_z]:
|
||||
prop.SetColor(color.float_rgb)
|
||||
prop.SetShadow(False)
|
||||
elif isinstance(axes_actor, _vtk.vtkAnnotatedCubeActor):
|
||||
axes_actor.GetTextEdgesProperty().SetColor(color.float_rgb)
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def create_axes_marker( # noqa: PLR0917
|
||||
label_color=None,
|
||||
x_color=None,
|
||||
y_color=None,
|
||||
z_color=None,
|
||||
xlabel='X',
|
||||
ylabel='Y',
|
||||
zlabel='Z',
|
||||
labels_off: bool = False, # noqa: FBT001, FBT002
|
||||
line_width=2,
|
||||
cone_radius=0.4,
|
||||
shaft_length=0.8,
|
||||
tip_length=0.2,
|
||||
ambient=0.5,
|
||||
label_size=(0.25, 0.1),
|
||||
) -> _vtk.vtkAxesActor:
|
||||
"""Create an axis actor.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label_color : ColorLike, optional
|
||||
Color of the label text.
|
||||
|
||||
x_color : ColorLike, optional
|
||||
Color of the x-axis text.
|
||||
|
||||
y_color : ColorLike, optional
|
||||
Color of the y-axis text.
|
||||
|
||||
z_color : ColorLike, optional
|
||||
Color of the z-axis text.
|
||||
|
||||
xlabel : str, default: "X"
|
||||
Text used for the x-axis.
|
||||
|
||||
ylabel : str, default: "Y"
|
||||
Text used for the y-axis.
|
||||
|
||||
zlabel : str, default: "Z"
|
||||
Text used for the z-axis.
|
||||
|
||||
labels_off : bool, default: False
|
||||
Enable or disable the text labels for the axes.
|
||||
|
||||
line_width : float, default: 2
|
||||
The width of the marker lines.
|
||||
|
||||
cone_radius : float, default: 0.4
|
||||
The radius of the axes arrow tips.
|
||||
|
||||
shaft_length : float, default: 0.8
|
||||
The length of the axes arrow shafts.
|
||||
|
||||
tip_length : float, default: 0.2
|
||||
Length of the tip.
|
||||
|
||||
ambient : float, default: 0.5
|
||||
The ambient of the axes arrows. Value should be between 0 and 1.
|
||||
|
||||
label_size : sequence[float], default: (0.25, 0.1)
|
||||
The width and height of the axes label actors. Values should be between
|
||||
0 and 1. For example ``(0.2, 0.1)``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkAxesActor`
|
||||
Axes actor.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create the default axes marker.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> marker = pv.create_axes_marker()
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_actor(marker)
|
||||
>>> pl.show()
|
||||
|
||||
Create an axes marker at the origin with custom colors and axis labels.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> marker = pv.create_axes_marker(
|
||||
... line_width=4,
|
||||
... ambient=0.0,
|
||||
... x_color='#378df0',
|
||||
... y_color='#ab2e5d',
|
||||
... z_color='#f7fb9a',
|
||||
... xlabel='X Axis',
|
||||
... ylabel='Y Axis',
|
||||
... zlabel='Z Axis',
|
||||
... label_size=(0.1, 0.1),
|
||||
... )
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_actor(marker)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
x_color = Color(x_color, default_color=pyvista.global_theme.axes.x_color)
|
||||
y_color = Color(y_color, default_color=pyvista.global_theme.axes.y_color)
|
||||
z_color = Color(z_color, default_color=pyvista.global_theme.axes.z_color)
|
||||
axes_actor = _vtk.vtkAxesActor()
|
||||
axes_actor.GetXAxisShaftProperty().SetColor(x_color.float_rgb)
|
||||
axes_actor.GetXAxisTipProperty().SetColor(x_color.float_rgb)
|
||||
axes_actor.GetYAxisShaftProperty().SetColor(y_color.float_rgb)
|
||||
axes_actor.GetYAxisTipProperty().SetColor(y_color.float_rgb)
|
||||
axes_actor.GetZAxisShaftProperty().SetColor(z_color.float_rgb)
|
||||
axes_actor.GetZAxisTipProperty().SetColor(z_color.float_rgb)
|
||||
# Set labels
|
||||
axes_actor.SetXAxisLabelText(xlabel)
|
||||
axes_actor.SetYAxisLabelText(ylabel)
|
||||
axes_actor.SetZAxisLabelText(zlabel)
|
||||
if labels_off:
|
||||
axes_actor.AxisLabelsOff()
|
||||
# Set Line width
|
||||
axes_actor.GetXAxisShaftProperty().SetLineWidth(line_width)
|
||||
axes_actor.GetYAxisShaftProperty().SetLineWidth(line_width)
|
||||
axes_actor.GetZAxisShaftProperty().SetLineWidth(line_width)
|
||||
|
||||
axes_actor.SetConeRadius(cone_radius)
|
||||
axes_actor.SetNormalizedShaftLength([shaft_length] * 3)
|
||||
axes_actor.SetNormalizedTipLength([tip_length] * 3)
|
||||
axes_actor.GetXAxisShaftProperty().SetAmbient(ambient)
|
||||
axes_actor.GetYAxisShaftProperty().SetAmbient(ambient)
|
||||
axes_actor.GetZAxisShaftProperty().SetAmbient(ambient)
|
||||
axes_actor.GetXAxisTipProperty().SetAmbient(ambient)
|
||||
axes_actor.GetYAxisTipProperty().SetAmbient(ambient)
|
||||
axes_actor.GetZAxisTipProperty().SetAmbient(ambient)
|
||||
|
||||
for label_actor in [
|
||||
axes_actor.GetXAxisCaptionActor2D(),
|
||||
axes_actor.GetYAxisCaptionActor2D(),
|
||||
axes_actor.GetZAxisCaptionActor2D(),
|
||||
]:
|
||||
label_actor.SetWidth(label_size[0])
|
||||
label_actor.SetHeight(label_size[1])
|
||||
|
||||
_update_axes_label_color(axes_actor, label_color)
|
||||
|
||||
return axes_actor
|
||||
|
||||
|
||||
@_deprecate_positional_args
|
||||
def create_axes_orientation_box( # noqa: PLR0917
|
||||
line_width=1,
|
||||
text_scale=0.366667,
|
||||
edge_color='black',
|
||||
x_color=None,
|
||||
y_color=None,
|
||||
z_color=None,
|
||||
xlabel='X',
|
||||
ylabel='Y',
|
||||
zlabel='Z',
|
||||
x_face_color='red',
|
||||
y_face_color='green',
|
||||
z_face_color='blue',
|
||||
color_box: bool = False, # noqa: FBT001, FBT002
|
||||
label_color=None,
|
||||
labels_off: bool = False, # noqa: FBT001, FBT002
|
||||
opacity=0.5,
|
||||
show_text_edges: bool = False, # noqa: FBT001, FBT002
|
||||
):
|
||||
"""Create a Box axes orientation widget with labels.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
line_width : float, optional
|
||||
The width of the marker lines.
|
||||
|
||||
text_scale : float, optional
|
||||
Size of the text relative to the faces.
|
||||
|
||||
edge_color : ColorLike, optional
|
||||
Color of the edges.
|
||||
|
||||
x_color : ColorLike, optional
|
||||
Color of the x-axis text.
|
||||
|
||||
y_color : ColorLike, optional
|
||||
Color of the y-axis text.
|
||||
|
||||
z_color : ColorLike, optional
|
||||
Color of the z-axis text.
|
||||
|
||||
xlabel : str, optional
|
||||
Text used for the x-axis.
|
||||
|
||||
ylabel : str, optional
|
||||
Text used for the y-axis.
|
||||
|
||||
zlabel : str, optional
|
||||
Text used for the z-axis.
|
||||
|
||||
x_face_color : ColorLike, optional
|
||||
Color used for the x-axis arrow. Defaults to theme axes
|
||||
parameters.
|
||||
|
||||
y_face_color : ColorLike, optional
|
||||
Color used for the y-axis arrow. Defaults to theme axes
|
||||
parameters.
|
||||
|
||||
z_face_color : ColorLike, optional
|
||||
Color used for the z-axis arrow. Defaults to theme axes
|
||||
parameters.
|
||||
|
||||
color_box : bool, optional
|
||||
Enable or disable the face colors. Otherwise, box is white.
|
||||
|
||||
label_color : ColorLike, optional
|
||||
Color of the labels.
|
||||
|
||||
labels_off : bool, optional
|
||||
Enable or disable the text labels for the axes.
|
||||
|
||||
opacity : float, optional
|
||||
Opacity in the range of ``[0, 1]`` of the orientation box.
|
||||
|
||||
show_text_edges : bool, optional
|
||||
Enable or disable drawing the vector text edges.
|
||||
|
||||
Returns
|
||||
-------
|
||||
:vtk:`vtkAnnotatedCubeActor`
|
||||
Annotated cube actor.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create and plot an orientation box
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> actor = pv.create_axes_orientation_box(
|
||||
... line_width=1,
|
||||
... text_scale=0.53,
|
||||
... edge_color='black',
|
||||
... x_color='k',
|
||||
... y_color=None,
|
||||
... z_color=None,
|
||||
... xlabel='X',
|
||||
... ylabel='Y',
|
||||
... zlabel='Z',
|
||||
... color_box=False,
|
||||
... labels_off=False,
|
||||
... opacity=1.0,
|
||||
... )
|
||||
>>> pl = pv.Plotter()
|
||||
>>> _ = pl.add_actor(actor)
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
x_color = Color(x_color, default_color=pyvista.global_theme.axes.x_color)
|
||||
y_color = Color(y_color, default_color=pyvista.global_theme.axes.y_color)
|
||||
z_color = Color(z_color, default_color=pyvista.global_theme.axes.z_color)
|
||||
edge_color = Color(edge_color, default_color=pyvista.global_theme.edge_color)
|
||||
x_face_color = Color(x_face_color)
|
||||
y_face_color = Color(y_face_color)
|
||||
z_face_color = Color(z_face_color)
|
||||
axes_actor = _vtk.vtkAnnotatedCubeActor()
|
||||
axes_actor.SetFaceTextScale(text_scale)
|
||||
if xlabel is not None:
|
||||
axes_actor.SetXPlusFaceText(f'+{xlabel}')
|
||||
axes_actor.SetXMinusFaceText(f'-{xlabel}')
|
||||
if ylabel is not None:
|
||||
axes_actor.SetYPlusFaceText(f'+{ylabel}')
|
||||
axes_actor.SetYMinusFaceText(f'-{ylabel}')
|
||||
if zlabel is not None:
|
||||
axes_actor.SetZPlusFaceText(f'+{zlabel}')
|
||||
axes_actor.SetZMinusFaceText(f'-{zlabel}')
|
||||
axes_actor.SetFaceTextVisibility(not labels_off)
|
||||
axes_actor.SetTextEdgesVisibility(show_text_edges)
|
||||
# https://github.com/pyvista/pyvista/pull/5382
|
||||
# axes_actor.GetTextEdgesProperty().SetColor(edge_color.float_rgb)
|
||||
axes_actor.GetTextEdgesProperty().SetLineWidth(line_width)
|
||||
axes_actor.GetXPlusFaceProperty().SetColor(x_color.float_rgb)
|
||||
axes_actor.GetXMinusFaceProperty().SetColor(x_color.float_rgb)
|
||||
axes_actor.GetYPlusFaceProperty().SetColor(y_color.float_rgb)
|
||||
axes_actor.GetYMinusFaceProperty().SetColor(y_color.float_rgb)
|
||||
axes_actor.GetZPlusFaceProperty().SetColor(z_color.float_rgb)
|
||||
axes_actor.GetZMinusFaceProperty().SetColor(z_color.float_rgb)
|
||||
|
||||
axes_actor.GetCubeProperty().SetOpacity(opacity)
|
||||
axes_actor.GetCubeProperty().SetEdgeColor(edge_color.float_rgb)
|
||||
axes_actor.GetCubeProperty().SetEdgeVisibility(True)
|
||||
axes_actor.GetCubeProperty().BackfaceCullingOn()
|
||||
if opacity < 1.0:
|
||||
# Hide the text edges
|
||||
axes_actor.GetTextEdgesProperty().SetOpacity(0)
|
||||
|
||||
if color_box:
|
||||
# Hide the cube so we can color each face
|
||||
axes_actor.GetCubeProperty().SetOpacity(0)
|
||||
axes_actor.GetCubeProperty().SetEdgeVisibility(False)
|
||||
|
||||
cube = pyvista.Cube()
|
||||
cube.clear_data() # remove normals
|
||||
face_colors = np.array(
|
||||
[
|
||||
x_face_color.int_rgb,
|
||||
x_face_color.int_rgb,
|
||||
y_face_color.int_rgb,
|
||||
y_face_color.int_rgb,
|
||||
z_face_color.int_rgb,
|
||||
z_face_color.int_rgb,
|
||||
],
|
||||
np.uint8,
|
||||
)
|
||||
cube.cell_data['face_colors'] = face_colors
|
||||
|
||||
cube_mapper = _vtk.vtkPolyDataMapper()
|
||||
cube_mapper.SetInputData(cube)
|
||||
cube_mapper.SetColorModeToDirectScalars()
|
||||
cube_mapper.Update()
|
||||
|
||||
cube_actor = pyvista.Actor(mapper=cube_mapper)
|
||||
cube_actor.prop.culling = 'back'
|
||||
cube_actor.prop.opacity = opacity
|
||||
|
||||
prop_assembly = _vtk.vtkPropAssembly()
|
||||
prop_assembly.AddPart(axes_actor)
|
||||
prop_assembly.AddPart(cube_actor)
|
||||
actor = prop_assembly
|
||||
else:
|
||||
actor = axes_actor # type: ignore[assignment]
|
||||
|
||||
_update_axes_label_color(actor, label_color)
|
||||
|
||||
return actor
|
||||
|
||||
|
||||
def create_north_arrow():
|
||||
"""Create a north arrow mesh.
|
||||
|
||||
.. versionadded:: 0.44.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.PolyData
|
||||
North arrow mesh.
|
||||
|
||||
"""
|
||||
points = np.array(
|
||||
[
|
||||
[0.0, 5.0, 0.0],
|
||||
[-2.0, 0.0, 0.0],
|
||||
[0.0, 1.5, 0.0],
|
||||
[2.0, 0.0, 0.0],
|
||||
[0.0, 5.0, 1.0],
|
||||
[-2.0, 0.0, 1.0],
|
||||
[0.0, 1.5, 1.0],
|
||||
[2.0, 0.0, 1.0],
|
||||
],
|
||||
)
|
||||
faces = np.array(
|
||||
[
|
||||
4,
|
||||
3,
|
||||
7,
|
||||
4,
|
||||
0,
|
||||
4,
|
||||
2,
|
||||
6,
|
||||
7,
|
||||
3,
|
||||
4,
|
||||
1,
|
||||
5,
|
||||
6,
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
4,
|
||||
5,
|
||||
1,
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
4,
|
||||
7,
|
||||
6,
|
||||
5,
|
||||
],
|
||||
)
|
||||
return pyvista.PolyData(points, faces)
|
||||
|
||||
|
||||
def normalize(x, minimum=None, maximum=None):
|
||||
"""Normalize the given value between [minimum, maximum].
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : numpy.ndarray
|
||||
The array of values to normalize.
|
||||
minimum : float, optional
|
||||
The minimum value to which ``x`` should be normalized. If not specified,
|
||||
the minimum value in ``x`` will be used.
|
||||
maximum : float, optional
|
||||
The maximum value to which ``x`` should be normalized. If not specified,
|
||||
the maximum value in ``x`` will be used.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
The normalized array of values, where the values are scaled to the
|
||||
range ``[minimum, maximum]``.
|
||||
|
||||
"""
|
||||
if minimum is None:
|
||||
minimum = np.nanmin(x)
|
||||
if maximum is None:
|
||||
maximum = np.nanmax(x)
|
||||
return (x - minimum) / (maximum - minimum)
|
||||
|
||||
|
||||
@_deprecate_positional_args(allowed=['mapping', 'n_colors'])
|
||||
def opacity_transfer_function( # noqa: PLR0917
|
||||
mapping,
|
||||
n_colors,
|
||||
interpolate: bool = True, # noqa: FBT001, FBT002
|
||||
kind='linear',
|
||||
):
|
||||
"""Get the opacity transfer function for a mapping.
|
||||
|
||||
These values will map on to a scalar bar range and thus the number of
|
||||
colors (``n_colors``) must correspond to the number of colors in the color
|
||||
mapping that these opacities are associated to.
|
||||
|
||||
If interpolating, ``scipy.interpolate.interp1d`` is used if available,
|
||||
otherwise ``np.interp`` is used. The ``kind`` argument controls the kind of
|
||||
interpolation for ``interp1d``.
|
||||
|
||||
This returns the opacity range from 0 to 255, where 0 is totally
|
||||
transparent and 255 is totally opaque.
|
||||
|
||||
The equation to create the sigmoid mapping is: ``1 / (1 + exp(-x))`` where
|
||||
``x`` is the range from ``-a`` to ``+a`` and ``a`` is the value given in
|
||||
the ``mapping`` string. Default is ``a=10`` for 'sigmoid' mapping.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mapping : list[float] | str
|
||||
The opacity mapping to use. Can be a ``str`` name of a predefined
|
||||
mapping including ``'linear'``, ``'geom'``, ``'sigmoid'``,
|
||||
``'sigmoid_1-10,15,20'``, and ``foreground``. Append an ``'_r'`` to any
|
||||
of those names (except ``foreground``) to reverse that mapping.
|
||||
The mapping can also be a custom user-defined array/list of values
|
||||
that will be interpolated across the ``n_color`` range.
|
||||
|
||||
n_colors : int
|
||||
The number of colors that the opacities must be mapped to.
|
||||
|
||||
interpolate : bool
|
||||
Flag on whether or not to interpolate the opacity mapping for all
|
||||
colors.
|
||||
|
||||
kind : str
|
||||
The interpolation kind if ``interpolate`` is ``True`` and ``scipy``
|
||||
is available. If ``scipy`` is not available, linear interpolation
|
||||
is always used. Options are:
|
||||
|
||||
- ``'linear'``
|
||||
- ``'nearest'``
|
||||
- ``'zero'``
|
||||
- ``'slinear'``
|
||||
- ``'quadratic'``
|
||||
- ``'cubic'``
|
||||
- ``'previous'``
|
||||
- ``'next'``
|
||||
|
||||
.. versionchanged:: 0.46
|
||||
|
||||
Linear interpolation is now always used by default. Previously,
|
||||
quadratic interpolation was used if ``scipy`` was installed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
numpy.ndarray
|
||||
Array of ``numpy.uint8`` values ``n_colors`` long containing the
|
||||
[0-255] opacity mapping values.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import pyvista as pv
|
||||
>>> # Fetch the `sigmoid` mapping between 0 and 255
|
||||
>>> tf = pv.opacity_transfer_function('sigmoid', 256)
|
||||
>>> # Fetch the `geom_r` mapping between 0 and 1
|
||||
>>> tf = pv.opacity_transfer_function('geom_r', 256).astype(float) / 255.0
|
||||
>>> # Interpolate a user defined opacity mapping
|
||||
>>> opacity = [0, 0.2, 0.9, 0.6, 0.3]
|
||||
>>> tf = pv.opacity_transfer_function(opacity, 256)
|
||||
|
||||
"""
|
||||
sigmoid = lambda x: np.array(1 / (1 + np.exp(-x)) * 255, dtype=np.uint8)
|
||||
transfer_func = {
|
||||
'linear': np.linspace(0, 255, n_colors, dtype=np.uint8),
|
||||
'geom': np.geomspace(1e-6, 255, n_colors, dtype=np.uint8),
|
||||
'geom_r': np.geomspace(255, 1e-6, n_colors, dtype=np.uint8),
|
||||
'sigmoid': sigmoid(np.linspace(-10.0, 10.0, n_colors)),
|
||||
'sigmoid_1': sigmoid(np.linspace(-1.0, 1.0, n_colors)),
|
||||
'sigmoid_2': sigmoid(np.linspace(-2.0, 2.0, n_colors)),
|
||||
'sigmoid_3': sigmoid(np.linspace(-3.0, 3.0, n_colors)),
|
||||
'sigmoid_4': sigmoid(np.linspace(-4.0, 4.0, n_colors)),
|
||||
'sigmoid_5': sigmoid(np.linspace(-5.0, 5.0, n_colors)),
|
||||
'sigmoid_6': sigmoid(np.linspace(-6.0, 6.0, n_colors)),
|
||||
'sigmoid_7': sigmoid(np.linspace(-7.0, 7.0, n_colors)),
|
||||
'sigmoid_8': sigmoid(np.linspace(-8.0, 8.0, n_colors)),
|
||||
'sigmoid_9': sigmoid(np.linspace(-9.0, 9.0, n_colors)),
|
||||
'sigmoid_10': sigmoid(np.linspace(-10.0, 10.0, n_colors)),
|
||||
'sigmoid_15': sigmoid(np.linspace(-15.0, 15.0, n_colors)),
|
||||
'sigmoid_20': sigmoid(np.linspace(-20.0, 20.0, n_colors)),
|
||||
'foreground': np.hstack((0, [255] * (n_colors - 1))).astype(np.uint8),
|
||||
}
|
||||
transfer_func['linear_r'] = transfer_func['linear'][::-1]
|
||||
transfer_func['sigmoid_r'] = transfer_func['sigmoid'][::-1]
|
||||
for i in range(3, 11):
|
||||
k = f'sigmoid_{i}'
|
||||
rk = f'{k}_r'
|
||||
transfer_func[rk] = transfer_func[k][::-1]
|
||||
if isinstance(mapping, str):
|
||||
try:
|
||||
return transfer_func[mapping]
|
||||
except KeyError:
|
||||
msg = (
|
||||
f'Opacity transfer function ({mapping}) unknown. '
|
||||
f'Valid options: {list(transfer_func.keys())}'
|
||||
)
|
||||
raise ValueError(msg) from None
|
||||
elif isinstance(mapping, (np.ndarray, list, tuple)):
|
||||
mapping = np.array(mapping)
|
||||
if mapping.size == n_colors:
|
||||
# User could pass transfer function ready for lookup table
|
||||
pass
|
||||
elif mapping.size < n_colors:
|
||||
# User pass custom transfer function to be linearly interpolated
|
||||
if np.max(mapping) > 1.0 or np.min(mapping) < 0.0:
|
||||
mapping = normalize(mapping)
|
||||
# Interpolate transfer function to match lookup table
|
||||
xo = np.linspace(0, n_colors, len(mapping), dtype=np.int_)
|
||||
xx = np.linspace(0, n_colors, n_colors, dtype=np.int_)
|
||||
try:
|
||||
if not interpolate:
|
||||
msg = 'No interpolation.'
|
||||
raise ValueError(msg)
|
||||
from scipy.interpolate import interp1d # noqa: PLC0415
|
||||
|
||||
f = interp1d(xo, mapping, kind=kind)
|
||||
vals = f(xx)
|
||||
vals[vals < 0] = 0.0
|
||||
vals[vals > 1.0] = 1.0
|
||||
mapping = (vals * 255.0).astype(np.uint8)
|
||||
|
||||
except (ImportError, ValueError):
|
||||
# Otherwise use simple linear interp
|
||||
mapping = (np.interp(xx, xo, mapping) * 255).astype(np.uint8)
|
||||
else:
|
||||
msg = (
|
||||
f'Transfer function cannot have more values than `n_colors`. '
|
||||
f'This has {mapping.size} elements'
|
||||
)
|
||||
raise RuntimeError(msg)
|
||||
return mapping
|
||||
msg = f'Transfer function type ({type(mapping)}) not understood'
|
||||
raise TypeError(msg)
|
||||
|
||||
|
||||
def parse_font_family(font_family: str) -> int:
|
||||
"""Check and validate the given font family name.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
font_family : str
|
||||
Font family name to validate. Must be one of the font names defined in
|
||||
the ``FONTS`` enum class.
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Corresponding integer value of the valid font family name in the
|
||||
``FONTS`` enum class.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
If the font_family is not one of the defined font names in the ``FONTS``
|
||||
enum class.
|
||||
|
||||
"""
|
||||
font_family = font_family.lower()
|
||||
fonts = [font.name for font in FONTS]
|
||||
if font_family not in fonts:
|
||||
msg = f'Font must one of the following:\n{", ".join(fonts)}'
|
||||
raise ValueError(msg)
|
||||
return FONTS[font_family].value
|
||||
|
||||
|
||||
def check_matplotlib_vtk_compatibility():
|
||||
"""Check if VTK and Matplotlib versions are compatible for MathText rendering.
|
||||
|
||||
This function is primarily geared towards checking if MathText rendering is
|
||||
supported with the given versions of VTK and Matplotlib. It follows the
|
||||
version constraints:
|
||||
|
||||
* VTK <= 9.2.2 requires Matplotlib < 3.6
|
||||
* VTK > 9.2.2 requires Matplotlib >= 3.6
|
||||
|
||||
Other version combinations of VTK and Matplotlib will work without
|
||||
errors, but some features (like MathText/LaTeX rendering) may
|
||||
silently fail.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
True if the versions of VTK and Matplotlib are compatible for MathText
|
||||
rendering, False otherwise.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
If the versions of VTK and Matplotlib cannot be checked.
|
||||
|
||||
"""
|
||||
import matplotlib as mpl # noqa: PLC0415
|
||||
|
||||
mpl_vers = tuple(map(int, mpl.__version__.split('.')[:2]))
|
||||
if pyvista.vtk_version_info <= (9, 2, 2):
|
||||
return not mpl_vers >= (3, 6)
|
||||
elif pyvista.vtk_version_info > (9, 2, 2):
|
||||
return mpl_vers >= (3, 6)
|
||||
msg = 'Uncheckable versions.' # pragma: no cover
|
||||
raise RuntimeError(msg) # pragma: no cover
|
||||
|
||||
|
||||
def check_math_text_support():
|
||||
"""Check if MathText and LaTeX symbols are supported.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
``True`` if both MathText and LaTeX symbols are supported, ``False``
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
# Something seriously sketchy is happening with this VTK code
|
||||
# It seems to hijack stdout and stderr?
|
||||
# See https://github.com/pyvista/pyvista/issues/4732
|
||||
# This is a hack to get around that by executing the code in a subprocess
|
||||
# and capturing the output:
|
||||
# _vtk.vtkMathTextFreeTypeTextRenderer().MathTextIsSupported()
|
||||
_cmd = 'import vtk;print(vtk.vtkMathTextFreeTypeTextRenderer().MathTextIsSupported());'
|
||||
proc = subprocess.run([sys.executable, '-c', _cmd], check=False, capture_output=True)
|
||||
math_text_support = False if proc.returncode else proc.stdout.decode().strip() == 'True'
|
||||
return math_text_support and check_matplotlib_vtk_compatibility()
|
||||
Reference in New Issue
Block a user