init
This commit is contained in:
@@ -0,0 +1,414 @@
|
||||
"""Wrapper for :vtk:`vtkVolumeProperty`."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
|
||||
import pyvista
|
||||
from pyvista._deprecate_positional_args import _deprecate_positional_args
|
||||
from pyvista.core.utilities.misc import _NoNewAttrMixin
|
||||
|
||||
from . import _vtk
|
||||
|
||||
|
||||
class VolumeProperty(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkVolumeProperty):
|
||||
"""Wrap the VTK class :vtk:`vtkVolumeProperty`.
|
||||
|
||||
This class is used to represent common properties associated with volume
|
||||
rendering. This includes properties for determining the type of
|
||||
interpolation to use when sampling a volume, the color of a volume, the
|
||||
scalar opacity of a volume, the gradient opacity of a volume, and the
|
||||
shading parameters of a volume.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lookup_table : pyvista.LookupTable, optional
|
||||
Lookup table to set the color and opacity transfer functions.
|
||||
|
||||
interpolation_type : str, optional
|
||||
Value must be either ``'linear'`` or ``'nearest'``.
|
||||
|
||||
ambient : float, optional
|
||||
When lighting is enabled, this is the amount of light in
|
||||
the range of 0 to 1 (default 0.0) that reaches the actor
|
||||
when not directed at the light source emitted from the
|
||||
viewer.
|
||||
|
||||
diffuse : float, optional
|
||||
The diffuse lighting coefficient.
|
||||
|
||||
specular : float, optional
|
||||
The specular lighting coefficient.
|
||||
|
||||
specular_power : float, optional
|
||||
The specular power. Between 0.0 and 128.0.
|
||||
|
||||
shade : bool, optional
|
||||
Enable or disable volume shading. If shading is turned off, then the
|
||||
mapper for the volume will not perform shading calculations. If shading
|
||||
is turned on, the mapper may perform shading calculations - in some
|
||||
cases shading does not apply (for example, in a maximum intensity
|
||||
projection) and therefore shading will not be performed even if this
|
||||
flag is on. For a compositing type of mapper, turning shading off is
|
||||
generally the same as setting ``ambient=1``, ``diffuse=0``,
|
||||
``specular=0``. Shading can be independently turned on/off per
|
||||
component.
|
||||
|
||||
opacity_unit_distance : float, optional
|
||||
This is the unit distance on which the scalar opacity transfer function
|
||||
is defined. By default this is 1.0, meaning that over a distance of 1.0
|
||||
units, a given opacity (from the transfer function) is
|
||||
accumulated. This is adjusted for the actual sampling distance during
|
||||
rendering.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a sample dataset from perlin noise and apply a lookup table to the
|
||||
:class:`VolumeProperty`.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> noise = pv.perlin_noise(1, (1, 3, 5), (0, 0, 0))
|
||||
>>> grid = pv.sample_function(
|
||||
... noise, bounds=[0, 3.0, -0, 1.0, 0, 1.0], dim=(40, 40, 40)
|
||||
... )
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_volume(grid, show_scalar_bar=False)
|
||||
>>> lut = actor.mapper.lookup_table
|
||||
>>> lut.cmap = 'bwr'
|
||||
>>> lut.apply_opacity([1.0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.0, 0.3])
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__( # noqa: PLR0917
|
||||
self,
|
||||
lookup_table=None,
|
||||
interpolation_type=None,
|
||||
ambient=None,
|
||||
diffuse=None,
|
||||
specular=None,
|
||||
specular_power=None,
|
||||
shade=None,
|
||||
opacity_unit_distance=None,
|
||||
):
|
||||
"""Initialize the :vtk:`vtkVolumeProperty` class."""
|
||||
super().__init__()
|
||||
self._lookup_table_ = None
|
||||
self._lookup_table_observer_id = None
|
||||
if lookup_table is not None:
|
||||
self.apply_lookup_table(lookup_table)
|
||||
if interpolation_type is not None:
|
||||
self.interpolation_type = interpolation_type
|
||||
if ambient is not None:
|
||||
self.ambient = ambient
|
||||
if diffuse is not None:
|
||||
self.diffuse = diffuse
|
||||
if specular is not None:
|
||||
self.specular = specular
|
||||
if specular_power is not None:
|
||||
self.specular_power = specular_power
|
||||
if shade is not None:
|
||||
self.shade = shade
|
||||
if opacity_unit_distance is not None:
|
||||
self.opacity_unit_distance = opacity_unit_distance
|
||||
|
||||
@property
|
||||
def _lookup_table(self) -> pyvista.LookupTable | None:
|
||||
"""Get the lookup table if applied via apply_lookup_table."""
|
||||
if self._lookup_table_ is not None:
|
||||
return self._lookup_table_()
|
||||
return None
|
||||
|
||||
@_lookup_table.setter
|
||||
def _lookup_table(self, lookup_table: pyvista.LookupTable):
|
||||
"""Set the lookup table if applied via apply_lookup_table."""
|
||||
if self._lookup_table is not None and self._lookup_table_observer_id is not None:
|
||||
# Clean up the old lookup table observer
|
||||
self._lookup_table.RemoveObserver(self._lookup_table_observer_id)
|
||||
self._lookup_table_observer_id = None
|
||||
self._lookup_table_ = weakref.ref(lookup_table)
|
||||
self._lookup_table_observer_id = lookup_table.AddObserver(
|
||||
_vtk.vtkCommand.ModifiedEvent,
|
||||
lambda *_: self.reapply_lookup_table(),
|
||||
)
|
||||
|
||||
def reapply_lookup_table(self):
|
||||
"""Reapply the lookup table previously applied.
|
||||
|
||||
The VolumeProperty is unable to keep a dynamic link to the colors
|
||||
and mapping laid out in the lookup table. This method allows you to
|
||||
reapply the lookup table to the VolumeProperty. This is useful if
|
||||
you modify the lookup table after it is applied to the
|
||||
VolumeProperty.
|
||||
|
||||
We have our own modified event observer to reapply this automatically
|
||||
when the lookup table is modified.
|
||||
|
||||
"""
|
||||
if self._lookup_table is not None:
|
||||
self.apply_lookup_table(self._lookup_table)
|
||||
|
||||
def apply_lookup_table(self, lookup_table: pyvista.LookupTable):
|
||||
"""Apply a lookup table to the volume property.
|
||||
|
||||
Applies both the color and opacity of the lookup table as transfer
|
||||
functions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
lookup_table : pyvista.LookupTable, optional
|
||||
Lookup table to set the color and opacity transfer functions.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Plot perlin noise volumetrically using a custom lookup table.
|
||||
|
||||
>>> import pyvista as pv
|
||||
>>> noise = pv.perlin_noise(1, (1, 3, 5), (0, 0, 0))
|
||||
>>> grid = pv.sample_function(
|
||||
... noise, bounds=[0, 3.0, -0, 1.0, 0, 1.0], dim=(40, 40, 40)
|
||||
... )
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_volume(grid, show_scalar_bar=False)
|
||||
>>> lut = actor.mapper.lookup_table
|
||||
>>> lut.cmap = 'bwr'
|
||||
>>> lut.apply_opacity([1.0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.0, 0.3])
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
if not isinstance(lookup_table, pyvista.LookupTable):
|
||||
msg = '`lookup_table` must be a `pyvista.LookupTable`'
|
||||
raise TypeError(msg)
|
||||
if self._lookup_table != lookup_table:
|
||||
self._lookup_table = lookup_table
|
||||
self.SetColor(lookup_table.to_color_tf())
|
||||
self.SetScalarOpacity(lookup_table.to_opacity_tf())
|
||||
|
||||
def __del__(self):
|
||||
"""Clean up the lookup table observer when the object is deleted."""
|
||||
if self._lookup_table_observer_id is not None and self._lookup_table is not None:
|
||||
self._lookup_table.RemoveObserver(self._lookup_table_observer_id)
|
||||
self._lookup_table_observer_id = None
|
||||
self._lookup_table_ = None
|
||||
|
||||
@property
|
||||
def interpolation_type(self) -> str: # numpydoc ignore=RT01
|
||||
"""Return or set the interpolation type.
|
||||
|
||||
Value must be either ``'linear'`` or ``'nearest'``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Create a sample :class:`pyvista.ImageData` dataset.
|
||||
|
||||
>>> import numpy as np
|
||||
>>> import pyvista as pv
|
||||
>>> n = 21
|
||||
>>> c = -(n - 1) / 2
|
||||
>>> vol = pv.ImageData(dimensions=(n, n, n), origin=(c, c, c))
|
||||
>>> scalars = np.linalg.norm(vol.points, axis=1)
|
||||
>>> scalars *= 255 / scalars.max()
|
||||
>>> vol['scalars'] = scalars
|
||||
|
||||
Demonstrate nearest (default) interpolation.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_volume(
|
||||
... vol,
|
||||
... show_scalar_bar=False,
|
||||
... opacity=[0.3, 0.0, 0.05, 0.0, 0.0, 0.0, 1.0, 0.0],
|
||||
... cmap='plasma',
|
||||
... )
|
||||
>>> actor.prop.interpolation_type = 'nearest'
|
||||
>>> pl.show()
|
||||
|
||||
Demonstrate linear interpolation.
|
||||
|
||||
>>> pl = pv.Plotter()
|
||||
>>> actor = pl.add_volume(
|
||||
... vol,
|
||||
... show_scalar_bar=False,
|
||||
... opacity=[0.3, 0.0, 0.05, 0.0, 0.0, 0.0, 1.0, 0.0],
|
||||
... cmap='plasma',
|
||||
... )
|
||||
>>> actor.prop.interpolation_type = 'linear'
|
||||
>>> pl.show()
|
||||
|
||||
"""
|
||||
return self.GetInterpolationTypeAsString().split()[0].lower()
|
||||
|
||||
@interpolation_type.setter
|
||||
def interpolation_type(self, value: str):
|
||||
if value == 'linear':
|
||||
self.SetInterpolationTypeToLinear()
|
||||
elif value == 'nearest':
|
||||
self.SetInterpolationTypeToNearest()
|
||||
else:
|
||||
msg = '`interpolation_type` must be either "linear" or "nearest"'
|
||||
raise ValueError(msg)
|
||||
|
||||
@property
|
||||
def opacity_unit_distance(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set the opacity unit distance.
|
||||
|
||||
This is the unit distance on which the scalar opacity transfer function
|
||||
is defined.
|
||||
|
||||
By default this is 1.0, meaning that over a distance of 1.0 units, a
|
||||
given opacity (from the transfer function) is accumulated. This is
|
||||
adjusted for the actual sampling distance during rendering.
|
||||
"""
|
||||
return self.GetScalarOpacityUnitDistance()
|
||||
|
||||
@opacity_unit_distance.setter
|
||||
def opacity_unit_distance(self, value: float):
|
||||
self.SetScalarOpacityUnitDistance(value)
|
||||
|
||||
@property
|
||||
def shade(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return or set shading of a volume.
|
||||
|
||||
If shading is turned off, then the mapper for the volume will not
|
||||
perform shading calculations. If shading is turned on, the mapper may
|
||||
perform shading calculations - in some cases shading does not apply
|
||||
(for example, in a maximum intensity projection) and therefore shading
|
||||
will not be performed even if this flag is on. For a compositing type
|
||||
of mapper, turning shading off is generally the same as setting
|
||||
``ambient=1``, ``diffuse=0``, ``specular=0``. Shading can be
|
||||
independently turned on/off per component.
|
||||
|
||||
"""
|
||||
return bool(self.GetShade())
|
||||
|
||||
@shade.setter
|
||||
def shade(self, value: bool):
|
||||
self.SetShade(value)
|
||||
|
||||
@property
|
||||
def independent_components(self) -> bool: # numpydoc ignore=RT01
|
||||
"""Return or set independent components.
|
||||
|
||||
If ``False``, then you must have either 2 or 4 component data.
|
||||
For 2 component data, the first is passed through the
|
||||
first color transfer function and the second component is passed
|
||||
through the first scalar opacity (and gradient opacity) transfer
|
||||
function. Normals will be generated off of the second component. When
|
||||
using gradient based opacity modulation, the gradients are computed off
|
||||
of the second component.
|
||||
|
||||
For 4 component data, the first three will directly represent RGB (no
|
||||
lookup table). The fourth component will be passed through the first
|
||||
scalar opacity transfer function for opacity and first gradient opacity
|
||||
transfer function for gradient based opacity modulation. Normals will
|
||||
be generated from the fourth component. When using gradient based
|
||||
opacity modulation, the gradients are computed off of the fourth
|
||||
component.
|
||||
"""
|
||||
return bool(self.GetIndependentComponents())
|
||||
|
||||
@independent_components.setter
|
||||
def independent_components(self, value: bool):
|
||||
self.SetIndependentComponents(value)
|
||||
|
||||
@property
|
||||
def ambient(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set ambient lighting coefficient.
|
||||
|
||||
This is the amount of light in the range of 0 to 1 (default 0.0) that
|
||||
reaches the actor when not directed at the light source emitted from
|
||||
the viewer.
|
||||
|
||||
Changing attribute has no effect unless :attr:`VolumeProperty.shade` is
|
||||
set to ``True``.
|
||||
|
||||
"""
|
||||
return self.GetAmbient()
|
||||
|
||||
@ambient.setter
|
||||
def ambient(self, value: float):
|
||||
self.SetAmbient(value)
|
||||
|
||||
@property
|
||||
def diffuse(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set the diffuse lighting coefficient.
|
||||
|
||||
This is the scattering of light by reflection or transmission. Diffuse
|
||||
reflection results when light strikes an irregular surface such as a
|
||||
frosted window or the surface of a frosted or coated light bulb.
|
||||
|
||||
Changing attribute has no effect unless :attr:`VolumeProperty.shade` is
|
||||
set to ``True``.
|
||||
|
||||
"""
|
||||
return self.GetDiffuse()
|
||||
|
||||
@diffuse.setter
|
||||
def diffuse(self, value: float):
|
||||
self.SetDiffuse(value)
|
||||
|
||||
@property
|
||||
def specular(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set specular.
|
||||
|
||||
Default 0.0
|
||||
|
||||
Specular lighting simulates the bright spot of a light that appears on
|
||||
shiny objects.
|
||||
|
||||
Changing attribute has no effect unless :attr:`VolumeProperty.shade` is
|
||||
set to ``True``.
|
||||
|
||||
"""
|
||||
return self.GetSpecular()
|
||||
|
||||
@specular.setter
|
||||
def specular(self, value: float):
|
||||
self.SetSpecular(value)
|
||||
|
||||
@property
|
||||
def specular_power(self) -> float: # numpydoc ignore=RT01
|
||||
"""Return or set specular power.
|
||||
|
||||
The specular power. Between 0.0 and 128.0. Default 10.0
|
||||
|
||||
"""
|
||||
return self.GetSpecularPower()
|
||||
|
||||
@specular_power.setter
|
||||
def specular_power(self, value: float):
|
||||
self.SetSpecularPower(value)
|
||||
|
||||
def copy(self) -> VolumeProperty:
|
||||
"""Create a deep copy of this property.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pyvista.plotting.volume_property.VolumeProperty
|
||||
Deep copy of this property.
|
||||
|
||||
"""
|
||||
new_prop = VolumeProperty()
|
||||
new_prop.DeepCopy(self)
|
||||
return new_prop
|
||||
|
||||
def __repr__(self):
|
||||
"""Representation of this property."""
|
||||
props = [
|
||||
f'{type(self).__name__} ({hex(id(self))})',
|
||||
]
|
||||
|
||||
for attr in dir(self):
|
||||
if not attr.startswith('_') and attr[0].islower():
|
||||
name = ' '.join(attr.split('_')).capitalize() + ':'
|
||||
try:
|
||||
value = getattr(self, attr)
|
||||
except AttributeError: # pragma:no cover
|
||||
continue
|
||||
if callable(value):
|
||||
continue
|
||||
if isinstance(value, str):
|
||||
value = f'"{value}"'
|
||||
props.append(f' {name:28s} {value}')
|
||||
|
||||
return '\n'.join(props)
|
||||
Reference in New Issue
Block a user