Files
geMoldInsight/venv/lib/python3.11/site-packages/pyvista/plotting/utilities/algorithms.py
T
2026-02-12 23:27:48 +08:00

634 lines
19 KiB
Python

"""Internal :vtk:`vtkAlgorithm` support helpers."""
from __future__ import annotations
import traceback
from typing import TYPE_CHECKING
import numpy as np
import pyvista
from pyvista._deprecate_positional_args import _deprecate_positional_args
from pyvista.core.errors import PyVistaPipelineError
from pyvista.core.utilities.helpers import wrap
from pyvista.core.utilities.misc import _NoNewAttrMixin
from pyvista.plotting import _vtk
if TYPE_CHECKING:
from pyvista.core.utilities.arrays import CellLiteral
from pyvista.core.utilities.arrays import PointLiteral
def algorithm_to_mesh_handler(
mesh_or_algo, port=0
) -> tuple[pyvista.DataSet, _vtk.vtkAlgorithm | _vtk.vtkAlgorithmOutput | None]:
"""Handle :vtk:`vtkAlgorithms` where mesh objects are expected.
This is a convenience method to handle :vtk:`vtkAlgorithms` when passed to methods
that expect a :class:`~pyvista.DataSet`. This method will check if the passed
object is a :vtk:`vtkAlgorithm` or :vtk:`vtkAlgorithmOutput` and if so,
return that algorithm's output dataset (mesh) as the mesh to be used by the
calling function.
Parameters
----------
mesh_or_algo : DataSet | :vtk:`vtkAlgorithm` | :vtk:`vtkAlgorithmOutput`
The input to be used as a data set (mesh) or :vtk:`vtkAlgorithm` object.
port : int, default: 0
If the input (``mesh_or_algo``) is an algorithm, this specifies which output
port to use on that algorithm for the returned mesh.
Returns
-------
mesh : pyvista.DataSet
The resulting mesh data set from the input.
algorithm : :vtk:`vtkAlgorithm` | :vtk:`vtkAlgorithmOutput` | None
If an algorithm is passed, it will be returned. Otherwise returns ``None``.
"""
if isinstance(mesh_or_algo, (_vtk.vtkAlgorithm, _vtk.vtkAlgorithmOutput)):
if isinstance(mesh_or_algo, _vtk.vtkAlgorithmOutput):
algo = mesh_or_algo.GetProducer()
# If vtkAlgorithmOutput, override port argument
port = mesh_or_algo.GetIndex()
output = mesh_or_algo
else:
algo = mesh_or_algo
output = algo.GetOutputPort(port)
algo.Update() # NOTE: this could be expensive... but we need it to get the mesh
# for legacy implementation. This can be refactored.
mesh = wrap(algo.GetOutputDataObject(port))
if mesh is None:
# This is known to happen with vtkPointSet and VTKPythonAlgorithmBase
# see workaround in PreserveTypeAlgorithmBase.
# This check remains as a fail-safe.
msg = 'The passed algorithm is failing to produce an output.' # type: ignore[unreachable]
raise PyVistaPipelineError(msg)
# NOTE: Return the vtkAlgorithmOutput only if port is non-zero. Segfaults can sometimes
# happen with vtkAlgorithmOutput. This logic will mostly avoid those issues.
# See https://gitlab.kitware.com/vtk/vtk/-/issues/18776
return mesh, output if port != 0 else algo
return mesh_or_algo, None
def set_algorithm_input(alg, inp, port=0):
"""Set the input to a :vtk:`vtkAlgorithm`.
Parameters
----------
alg : :vtk:`vtkAlgorithm`
The algorithm whose input is being set.
inp : :vtk:`vtkAlgorithm` | :vtk:`vtkAlgorithmOutput` | :vtk:`vtkDataObject`
The input to the algorithm.
port : int, default: 0
The input port.
"""
if isinstance(inp, _vtk.vtkAlgorithm):
alg.SetInputConnection(port, inp.GetOutputPort())
elif isinstance(inp, _vtk.vtkAlgorithmOutput):
alg.SetInputConnection(port, inp)
else:
alg.SetInputDataObject(port, inp)
class PreserveTypeAlgorithmBase(
_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.VTKPythonAlgorithmBase
):
"""Base algorithm to preserve type.
Parameters
----------
nInputPorts : int, default: 1
Number of input ports for the algorithm.
nOutputPorts : int, default: 1
Number of output ports for the algorithm.
"""
def __init__(self, nInputPorts=1, nOutputPorts=1):
"""Initialize algorithm."""
_vtk.VTKPythonAlgorithmBase.__init__(
self,
nInputPorts=nInputPorts,
nOutputPorts=nOutputPorts,
)
def GetInputData(self, inInfo, port, idx):
"""Get input data object.
This will convert :vtk:`vtkPointSet` to :vtk:`vtkPolyData`.
Parameters
----------
inInfo : :vtk:`vtkInformation`
The information object associated with the input port.
port : int
The index of the input port.
idx : int
The index of the data object within the input port.
Returns
-------
:vtk:`vtkDataObject`
The input data object.
"""
inp = wrap(_vtk.VTKPythonAlgorithmBase.GetInputData(self, inInfo, port, idx))
if isinstance(inp, pyvista.PointSet):
return inp.cast_to_polydata()
return inp
# THIS IS CRUCIAL to preserve data type through filter
def RequestDataObject(self, _request, inInfo, outInfo) -> int:
"""Preserve data type.
Parameters
----------
_request : :vtk:`vtkInformation`
The request object for the filter.
inInfo : :vtk:`vtkInformationVector`
The input information vector for the filter.
outInfo : :vtk:`vtkInformationVector`
The output information vector for the filter.
Returns
-------
int
Returns 1 if successful.
"""
class_name = self.GetInputData(inInfo, 0, 0).GetClassName()
if class_name == 'vtkPointSet':
# See https://gitlab.kitware.com/vtk/vtk/-/issues/18771
self.OutputType = 'vtkPolyData'
else:
self.OutputType = class_name
self.FillOutputPortInformation(0, outInfo.GetInformationObject(0))
return 1
class ActiveScalarsAlgorithm(PreserveTypeAlgorithmBase):
"""Algorithm to control active scalars.
The output of this filter is a shallow copy of the input data
set with the active scalars set as specified.
Parameters
----------
name : str
Name of scalars used to set as active on the output mesh.
Accepts a string name of an array that is present on the mesh.
Array should be sized as a single vector.
preference : str, default: 'point'
When ``mesh.n_points == mesh.n_cells`` and setting
scalars, this parameter sets how the scalars will be
mapped to the mesh. The default, ``'point'``, causes the
scalars to be associated with the mesh points. Can be
either ``'point'`` or ``'cell'``.
"""
def __init__(self, name: str, preference: PointLiteral | CellLiteral = 'point'):
"""Initialize algorithm."""
super().__init__()
self.scalars_name = name
self.preference = preference
def RequestData(self, _request, inInfo, outInfo) -> int:
"""Perform algorithm execution.
Parameters
----------
_request : :vtk:`vtkInformation`
The request object.
inInfo : :vtk:`vtkInformationVector`
Information about the input data.
outInfo : :vtk:`vtkInformationVector`
Information about the output data.
Returns
-------
int
1 on success.
"""
try:
inp = wrap(self.GetInputData(inInfo, 0, 0))
out = self.GetOutputData(outInfo, 0)
output = inp.copy()
if output.n_arrays:
output.set_active_scalars(self.scalars_name, preference=self.preference)
out.ShallowCopy(output)
except Exception: # pragma: no cover
traceback.print_exc()
raise
return 1
class PointSetToPolyDataAlgorithm(
_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.VTKPythonAlgorithmBase
):
"""Algorithm to cast PointSet to PolyData.
This is implemented with :func:`pyvista.PointSet.cast_to_polydata`.
"""
def __init__(self):
"""Initialize algorithm."""
_vtk.VTKPythonAlgorithmBase.__init__(
self,
nInputPorts=1,
nOutputPorts=1,
inputType='vtkPointSet',
outputType='vtkPolyData',
)
def RequestData(self, _request, inInfo, outInfo) -> int:
"""Perform algorithm execution.
Parameters
----------
_request : :vtk:`vtkInformation`
Information associated with the request.
inInfo : :vtk:`vtkInformationVector`
Information about the input data.
outInfo : :vtk:`vtkInformationVector`
Information about the output data.
Returns
-------
int
1 when successful.
"""
try:
inp = wrap(self.GetInputData(inInfo, 0, 0))
out = self.GetOutputData(outInfo, 0)
output = inp.cast_to_polydata(deep=False)
out.ShallowCopy(output)
except Exception: # pragma: no cover
traceback.print_exc()
raise
return 1
class AddIDsAlgorithm(PreserveTypeAlgorithmBase):
"""Algorithm to add point or cell IDs.
Output of this filter is a shallow copy of the input with
point and/or cell ID arrays added.
Parameters
----------
point_ids : bool, default: True
Whether to add point IDs.
cell_ids : bool, default: True
Whether to add cell IDs.
Raises
------
ValueError
If neither point IDs nor cell IDs are set.
"""
@_deprecate_positional_args
def __init__(self, point_ids: bool = True, cell_ids: bool = True): # noqa: FBT001, FBT002
"""Initialize algorithm."""
super().__init__()
if not point_ids and not cell_ids: # pragma: no cover
msg = 'IDs must be set for points or cells or both.'
raise ValueError(msg)
self.point_ids = point_ids
self.cell_ids = cell_ids
def RequestData(self, _request, inInfo, outInfo) -> int:
"""Perform algorithm execution.
Parameters
----------
_request : :vtk:`vtkInformation`
Information associated with the request.
inInfo : :vtk:`vtkInformationVector`
Information about the input data.
outInfo : :vtk:`vtkInformationVector`
Information about the output data.
Returns
-------
int
Returns 1 if the algorithm was successful.
Raises
------
Exception
If the algorithm fails to execute properly.
"""
try:
inp = wrap(self.GetInputData(inInfo, 0, 0))
out = self.GetOutputData(outInfo, 0)
output = inp.copy()
if self.point_ids:
output.point_data['point_ids'] = np.arange(0, output.n_points, dtype=int)
if self.cell_ids:
output.cell_data['cell_ids'] = np.arange(0, output.n_cells, dtype=int)
if output.active_scalars_name in ['point_ids', 'cell_ids']:
output.active_scalars_name = inp.active_scalars_name
out.ShallowCopy(output)
except Exception: # pragma: no cover
traceback.print_exc()
raise
return 1
class CrinkleAlgorithm(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.VTKPythonAlgorithmBase):
"""Algorithm to crinkle cell IDs."""
def __init__(self):
"""Initialize algorithm."""
super().__init__(
nInputPorts=2,
outputType='vtkUnstructuredGrid',
)
def RequestData(self, _request, inInfo, outInfo) -> int:
"""Perform algorithm execution based on the input data and produce the output.
Parameters
----------
_request : :vtk:`vtkInformation`
The request information associated with the algorithm.
inInfo : :vtk:`vtkInformationVector`
Information vector describing the input data.
outInfo : :vtk:`vtkInformationVector`
Information vector where the output data should be placed.
Returns
-------
int
Status of the execution. Returns 1 on successful completion.
"""
try:
clipped = wrap(self.GetInputData(inInfo, 0, 0))
source = wrap(self.GetInputData(inInfo, 1, 0))
out = self.GetOutputData(outInfo, 0)
output = source.extract_cells(np.unique(clipped.cell_data['cell_ids']))
out.ShallowCopy(output)
except Exception: # pragma: no cover
traceback.print_exc()
raise
return 1
@_deprecate_positional_args(allowed=['inp'])
def outline_algorithm(inp, generate_faces: bool = False): # noqa: FBT001, FBT002
"""Add :vtk:`vtkOutlineFilter` to pipeline.
Parameters
----------
inp : pyvista.Common
Input data to be filtered.
generate_faces : bool, default: False
Whether to generate faces for the outline.
Returns
-------
:vtk:`vtkOutlineFilter`
Outline filter applied to the input data.
"""
alg = _vtk.vtkOutlineFilter()
set_algorithm_input(alg, inp)
alg.SetGenerateFaces(generate_faces)
return alg
@_deprecate_positional_args(allowed=['inp'])
def extract_surface_algorithm( # noqa: PLR0917
inp,
pass_pointid: bool = False, # noqa: FBT001, FBT002
pass_cellid: bool = False, # noqa: FBT001, FBT002
nonlinear_subdivision=1,
):
"""Add :vtk:`vtkDataSetSurfaceFilter` to pipeline.
Parameters
----------
inp : pyvista.Common
Input data to be filtered.
pass_pointid : bool, default: False
If ``True``, pass point IDs to the output.
pass_cellid : bool, default: False
If ``True``, pass cell IDs to the output.
nonlinear_subdivision : int, default: 1
Level of nonlinear subdivision.
Returns
-------
:vtk:`vtkDataSetSurfaceFilter`
Surface filter applied to the input data.
"""
surf_filter = _vtk.vtkDataSetSurfaceFilter()
surf_filter.SetPassThroughPointIds(pass_pointid)
surf_filter.SetPassThroughCellIds(pass_cellid)
if nonlinear_subdivision != 1:
surf_filter.SetNonlinearSubdivisionLevel(nonlinear_subdivision)
set_algorithm_input(surf_filter, inp)
return surf_filter
def active_scalars_algorithm(inp, name, preference='point'):
"""Add a filter that sets the active scalars.
Parameters
----------
inp : pyvista.Common
Input data to be filtered.
name : str
Name of the scalars to set as active.
preference : str, default: 'point'
Preference for the scalars to be set as active. Options are 'point', 'cell', or 'field'.
Returns
-------
:vtk:`vtkAlgorithm`
Active scalars filter applied to the input data.
"""
alg = ActiveScalarsAlgorithm(
name=name,
preference=preference,
)
set_algorithm_input(alg, inp)
return alg
def pointset_to_polydata_algorithm(inp):
"""Add a filter that casts PointSet to PolyData.
Parameters
----------
inp : pyvista.PointSet
Input point set to be cast to PolyData.
Returns
-------
:vtk:`vtkAlgorithm`
Filter that casts the input PointSet to PolyData.
"""
alg = PointSetToPolyDataAlgorithm()
set_algorithm_input(alg, inp)
return alg
@_deprecate_positional_args(allowed=['inp'])
def add_ids_algorithm(inp, point_ids: bool = True, cell_ids: bool = True): # noqa: FBT001, FBT002
"""Add a filter that adds point and/or cell IDs.
Parameters
----------
inp : pyvista.DataSet
The input data to which the IDs will be added.
point_ids : bool, default: True
If ``True``, point IDs will be added to the input data.
cell_ids : bool, default: True
If ``True``, cell IDs will be added to the input data.
Returns
-------
AddIDsAlgorithm
AddIDsAlgorithm filter.
"""
alg = AddIDsAlgorithm(point_ids=point_ids, cell_ids=cell_ids)
set_algorithm_input(alg, inp)
return alg
def crinkle_algorithm(clip, source):
"""Add a filter that crinkles a clip.
Parameters
----------
clip : pyvista.DataSet
The input data to be crinkled.
source : pyvista.DataSet
The source of the crinkle.
Returns
-------
CrinkleAlgorithm
CrinkleAlgorithm filter.
"""
alg = CrinkleAlgorithm()
set_algorithm_input(alg, clip, 0)
set_algorithm_input(alg, source, 1)
return alg
@_deprecate_positional_args(allowed=['inp'])
def cell_data_to_point_data_algorithm(inp, pass_cell_data: bool = False): # noqa: FBT001, FBT002
"""Add a filter that converts cell data to point data.
Parameters
----------
inp : pyvista.DataSet
The input data whose cell data will be converted to point data.
pass_cell_data : bool, default: False
If ``True``, the original cell data will be passed to the output.
Returns
-------
:vtk:`vtkCellDataToPointData`
The :vtk:`vtkCellDataToPointData` filter.
"""
alg = _vtk.vtkCellDataToPointData()
alg.SetPassCellData(pass_cell_data)
set_algorithm_input(alg, inp)
return alg
@_deprecate_positional_args(allowed=['inp'])
def point_data_to_cell_data_algorithm(inp, pass_point_data: bool = False): # noqa: FBT001, FBT002
"""Add a filter that converts point data to cell data.
Parameters
----------
inp : pyvista.DataSet
The input data whose point data will be converted to cell data.
pass_point_data : bool, default: False
If ``True``, the original point data will be passed to the output.
Returns
-------
:vtk:`vtkPointDataToCellData`
:vtk:`vtkPointDataToCellData` algorithm.
"""
alg = _vtk.vtkPointDataToCellData()
alg.SetPassPointData(pass_point_data)
set_algorithm_input(alg, inp)
return alg
def triangulate_algorithm(inp):
"""Triangulate the input data.
Parameters
----------
inp : :vtk:`vtkDataObject`
The input data to be triangulated.
Returns
-------
:vtk:`vtkTriangleFilter`
The triangle filter that has been applied to the input data.
"""
trifilter = _vtk.vtkTriangleFilter()
trifilter.PassVertsOff()
trifilter.PassLinesOff()
set_algorithm_input(trifilter, inp)
return trifilter
def decimation_algorithm(inp, target_reduction):
"""Decimate the input data to the target reduction.
Parameters
----------
inp : :vtk:`vtkDataObject`
The input data to be decimated.
target_reduction : float
The target reduction amount, as a fraction of the original data.
Returns
-------
:vtk:`vtkQuadricDecimation`
The decimation algorithm that has been applied to the input data.
"""
alg = _vtk.vtkQuadricDecimation()
alg.SetTargetReduction(target_reduction)
set_algorithm_input(alg, inp)
return alg