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

1172 lines
37 KiB
Python

"""Wrap :vtk:`vtkLookupTable`."""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import cast
import matplotlib as mpl
import numpy as np
import pyvista
from pyvista._deprecate_positional_args import _deprecate_positional_args
from pyvista.core.utilities.arrays import convert_array
from pyvista.core.utilities.misc import _NoNewAttrMixin
from . import _vtk
from .colors import Color
from .colors import get_cmap_safe
from .tools import opacity_transfer_function
if TYPE_CHECKING:
from ._typing import ColorLike
from ._typing import ColormapOptions
RAMP_MAP = {0: 'linear', 1: 's-curve', 2: 'sqrt'}
RAMP_MAP_INV = {k: v for v, k in RAMP_MAP.items()}
class lookup_table_ndarray(_NoNewAttrMixin, np.ndarray): # noqa: N801
"""An ndarray which references the owning table and the underlying :vtk:`vtkArray`.
This class is used to ensure that the internal :vtk:`vtkLookupTable` updates when
the values array is updated.
"""
def __new__(
cls,
array,
table=None,
):
"""Allocate the array."""
obj = convert_array(array).view(cls)
obj.VTKObject = array
obj.table = _vtk.vtkWeakReference()
obj.table.Set(table)
return obj
def __array_finalize__(self, obj):
"""Finalize array (associate with parent metadata)."""
_vtk.VTKArray.__array_finalize__(self, obj) # type: ignore[arg-type]
if np.shares_memory(self, obj):
self.table = getattr(obj, 'table', None)
self.VTKObject = getattr(obj, 'VTKObject', None)
else:
self.table = None
self.VTKObject = None
def __setitem__(self, key, value):
"""Implement [] set operator.
When the array is changed it triggers "Modified()" which updates
all upstream objects, including any render windows holding the
object.
"""
super().__setitem__(key, value)
if self.VTKObject is not None:
self.VTKObject.Modified()
# the associated dataset should also be marked as modified
if self.table is not None and self.table.Get():
# this creates a new shallow copy and is necessary to update the
# internal VTK array
self.table.Get().values = self
def __array_wrap__(self, out_arr, context=None, return_scalar: bool = False): # noqa: FBT001, FBT002
"""Return a numpy scalar if array is 0d.
See https://github.com/numpy/numpy/issues/5819
"""
if out_arr.ndim:
return np.ndarray.__array_wrap__(self, out_arr, context, return_scalar)
# Match numpy's behavior and return a numpy dtype scalar
return out_arr[()]
__getattr__ = _vtk.VTKArray.__getattr__
class LookupTable(_NoNewAttrMixin, _vtk.DisableVtkSnakeCase, _vtk.vtkLookupTable):
"""Scalar to RGBA mapping table.
A lookup table is an array that maps input values to output values. When
plotting data over a dataset, it is necessary to map those scalars to
colors (in the RGBA format), and this class provides the functionality to
do so.
See :vtk:`vtkLookupTable` for more
details regarding the underlying VTK API.
Parameters
----------
cmap : str | matplotlib.colors.Colormap, optional
Color map from ``matplotlib``, ``colorcet``, or ``cmocean``. Either
``cmap`` or ``values`` can be set, but not both.
See :ref:`named_colormaps` for supported colormaps.
n_values : int, default: 256
Number of colors in the color map.
flip : bool, default: False
Flip the direction of cmap. Most colormaps allow ``*_r`` suffix to do this
as well.
values : array_like[float], optional
Lookup table values. Either ``values`` or ``cmap`` can be set, but not
both.
value_range : tuple, optional
The range of the brightness of the mapped lookup table. This range is
only used when creating custom color maps and will be ignored if
``cmap`` is set.
hue_range : tuple, optional
Lookup table hue range. This range is only used when creating custom
color maps and will be ignored if ``cmap`` is set.
alpha_range : tuple, optional
Lookup table alpha (transparency) range. This range is only used when
creating custom color maps and will be ignored if ``cmap`` is set.
scalar_range : tuple, optional
The range of scalars which will be mapped to colors. Values outside of
this range will be colored according to
:attr:`LookupTable.below_range_color` and
:attr:`LookupTable.above_range_color`.
log_scale : bool, optional
Use a log scale when mapping scalar values.
nan_color : ColorLike, optional
Color to render any values that are NANs.
above_range_color : ColorLike, optional
Color to render any values above :attr:`LookupTable.scalar_range`.
below_range_color : ColorLike, optional
Color to render any values below :attr:`LookupTable.scalar_range`.
ramp : str, optional
The shape of the table ramp. This range is only used when creating
custom color maps and will be ignored if ``cmap`` is set.
annotations : dict, optional
A dictionary of annotations. Keys are the float values in the scalars
range to annotate on the scalar bar and the values are the string
annotations.
See Also
--------
:ref:`lookup_table_example`
Examples
--------
Plot the lookup table with the default VTK color map.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut
LookupTable (...)
Table Range: (0.0, 1.0)
N Values: 256
Above Range Color: None
Below Range Color: None
NAN Color: Color(name='maroon', hex='#800000ff', opacity=255)
Log Scale: False
Color Map: "PyVista Lookup Table"
Alpha Range: (1.0, 1.0)
Hue Range: (0.0, 0.66667)
Saturation Range (1.0, 1.0)
Value Range (1.0, 1.0)
Ramp s-curve
>>> lut.plot()
Plot the lookup table with the ``'inferno'`` color map.
>>> import pyvista as pv
>>> lut = pv.LookupTable('inferno', n_values=32)
>>> lut
LookupTable (...)
Table Range: (0.0, 1.0)
N Values: 32
Above Range Color: None
Below Range Color: None
NAN Color: Color(name='maroon', hex='#800000ff', opacity=255)
Log Scale: False
Color Map: "inferno"
>>> lut.plot()
"""
_nan_color_set = False
_cmap: mpl.colors.Colormap | None = None
_values_manual = False
_opacity_parm: tuple[Any, bool, str] = (None, False, 'quadratic')
@_deprecate_positional_args(allowed=['cmap', 'n_values'])
def __init__( # noqa: PLR0917
self,
cmap=None,
n_values=256,
flip: bool = False, # noqa: FBT001, FBT002
values=None,
value_range=None,
hue_range=None,
alpha_range=None,
scalar_range=None,
log_scale=None,
nan_color=None,
above_range_color=None,
below_range_color=None,
ramp=None,
annotations=None,
):
"""Initialize the lookup table."""
if cmap is not None and values is not None:
msg = 'Cannot set both `cmap` and `values`.'
raise ValueError(msg)
if cmap is not None:
self.apply_cmap(cmap, n_values=n_values, flip=flip)
elif values is not None:
self.values = values
else:
self.n_values = n_values
if value_range is not None:
self.value_range = value_range
if hue_range is not None:
self.hue_range = hue_range
if alpha_range is not None:
self.alpha_range = alpha_range
if ramp is not None:
self.ramp = ramp
if nan_color is not None:
self.nan_color = nan_color
if above_range_color is not None:
self.above_range_color = above_range_color
if below_range_color is not None:
self.below_range_color = below_range_color
if scalar_range is not None:
self.scalar_range = scalar_range
if log_scale is not None:
self.log_scale = log_scale
if annotations is not None:
self.annotations = annotations
@property
def value_range(self) -> tuple[float, float] | None: # numpydoc ignore=RT01
"""Return or set the brightness of the mapped lookup table.
This range is only used when creating custom color maps and will return
``None`` when a color map has been set with :attr:`LookupTable.cmap`.
This will clear any existing color map and create new values for the
lookup table when set.
Examples
--------
Show the effect of setting the value range on the default color
map.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.value_range = (0, 1.0)
>>> lut.plot()
Demonstrate a different value range.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.value_range = (0.5, 0.8)
>>> lut.plot()
"""
if self._cmap:
return None
return self.GetValueRange()
@value_range.setter
def value_range(self, value: tuple[float, float]):
self.SetValueRange(value)
self.rebuild()
@property
def hue_range(self) -> tuple[float, float] | None: # numpydoc ignore=RT01
"""Return or set the hue range.
This range is only used when creating custom color maps and will return
``None`` when a color map has been set with :attr:`LookupTable.cmap`.
This will clear any existing color map and create new values for the
lookup table when set.
Examples
--------
Set the hue range. This allows you to create a lookup table
without setting a color map.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0, 0.1)
>>> lut.plot()
Create a different color map.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0.5, 0.8)
>>> lut.plot()
"""
if self._cmap:
return None
return self.GetHueRange()
@hue_range.setter
def hue_range(self, value: tuple[float, float]):
self.SetHueRange(value)
self.rebuild()
@property
def cmap(self) -> mpl.colors.Colormap | None: # numpydoc ignore=RT01
"""Return or set the color map used by this lookup table.
See :ref:`named_colormaps` for supported colormaps.
Examples
--------
Apply the single Matplotlib color map ``"Oranges"``.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.cmap = 'Oranges'
>>> lut.plot()
Apply a list of colors as a colormap.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.cmap = ['black', 'red', 'orange']
>>> lut.plot()
"""
return self._cmap
@cmap.setter
def cmap(self, value: ColormapOptions):
self.apply_cmap(value, self.n_values)
@property
def log_scale(self) -> bool: # numpydoc ignore=RT01
"""Use log scale.
When ``True`` the lookup table is a log scale to
:attr:`LookupTable.scalar_range`. Otherwise, it is linear scale.
Examples
--------
Use log scale for the lookup table.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.log_scale = True
>>> lut.scalar_range = (1, 100)
>>> lut.plot()
"""
return bool(self.GetScale())
@log_scale.setter
def log_scale(self, value: bool):
self.SetScale(value)
def __repr__(self):
"""Return the representation."""
lines = [f'{type(self).__name__} ({hex(id(self))})']
lines.append(f' Table Range: {self.scalar_range}')
lines.append(f' N Values: {self.n_values}')
lines.append(f' Above Range Color: {self.above_range_color}')
lines.append(f' Below Range Color: {self.below_range_color}')
lines.append(f' NAN Color: {self.nan_color}')
lines.append(f' Log Scale: {self.log_scale}')
lines.append(f' Color Map: "{self._lookup_type}"')
if not (self.cmap or self._values_manual):
lines.append(f' Alpha Range: {self.alpha_range}')
lines.append(f' Hue Range: {self.hue_range}')
lines.append(f' Saturation Range {self.saturation_range}')
lines.append(f' Value Range {self.value_range}')
lines.append(f' Ramp {self.ramp}')
return '\n'.join(lines)
@property
def scalar_range(self) -> tuple[float, float]: # numpydoc ignore=RT01
"""Return or set the table range.
This is the range of scalars which will be mapped to colors. Values
outside of this range will be colored according to
:attr:`LookupTable.below_range_color` and
:attr:`LookupTable.above_range_color`.
Examples
--------
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.scalar_range = (0, 10)
>>> lut.scalar_range
(0.0, 10.0)
"""
return self.GetTableRange()
@scalar_range.setter
def scalar_range(self, value: tuple[float, float]):
self.SetTableRange(value)
@property
def alpha_range(self) -> tuple[float, float] | None: # numpydoc ignore=RT01
"""Return or set the alpha range.
This range is only used when creating custom color maps and will return
``None`` when a color map has been set with :attr:`LookupTable.cmap`.
This will clear any existing color map and create new values for the
lookup table when set.
Examples
--------
Create a custom "blues" lookup table that decreases in opacity.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0.7, 0.7)
>>> lut.alpha_range = (1.0, 0.0)
>>> lut.plot(background='grey')
"""
if self._cmap:
return None
return self.GetAlphaRange()
@alpha_range.setter
def alpha_range(self, value: tuple[float, float]):
self.SetAlphaRange(value)
self.rebuild()
@property
def saturation_range(self) -> tuple[float, float] | None: # numpydoc ignore=RT01
"""Return or set the saturation range.
This range is only used when creating custom color maps and will return
``None`` when a color map has been set with :attr:`LookupTable.cmap`.
This will clear any existing color map and create new values for the
lookup table when set.
Examples
--------
Create a custom "blues" lookup table that increases in saturation.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0.7, 0.7)
>>> lut.saturation_range = (0.0, 1.0)
>>> lut.plot(background='grey')
"""
if self._cmap:
return None
return self.GetSaturationRange()
@saturation_range.setter
def saturation_range(self, value: tuple[float, float]):
self.SetSaturationRange(value)
self.rebuild()
def rebuild(self):
"""Clear the color map and recompute the values table.
This is called automatically when setting values like
:attr:`LookupTable.value_range`.
Notes
-----
This will reset any colormap set with :func:`LookupTable.apply_cmap` or
:attr:`LookupTable.values`.
"""
self._cmap = None
self._values_manual = False
self.ForceBuild()
@property
def nan_color(self) -> Color | None: # numpydoc ignore=RT01
"""Return or set the not a number (NAN) color.
Any values that are NANs will be rendered with this color.
Examples
--------
Set the NAN color to ``'grey'``.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.nan_color = 'grey'
>>> lut.plot()
"""
return Color(self.GetNanColor())
@nan_color.setter
def nan_color(self, value):
# NAN value is always set, but make it explicit for example plotting
self._nan_color_set = True
self.SetNanColor(*Color(value).float_rgba)
@property
def nan_opacity(self): # numpydoc ignore=RT01
"""Return or set the not a number (NAN) opacity.
Any values that are NANs will be rendered with this opacity.
Examples
--------
Set the NAN opacity to ``0.5``.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.nan_color = 'grey'
>>> lut.nan_opacity = 0.5
>>> lut.plot()
"""
color = self.nan_color
return color.opacity # type: ignore[union-attr]
@nan_opacity.setter
def nan_opacity(self, value):
# Hacky check to prevent auto activation
if not self._nan_color_set and (value in (1.0, 255)):
return
color = self.nan_color
if color is None:
color = Color(pyvista.global_theme.nan_color)
self.nan_color = Color(self.nan_color, opacity=value)
@property
def ramp(self) -> str: # numpydoc ignore=RT01
"""Set the shape of the table ramp.
This attribute is only used when creating custom color maps and will
return ``None`` when a color map has been set with
:attr:`LookupTable.cmap`. This will clear any existing color map and
create new values for the lookup table when set.
This value may be either ``"s-curve"``, ``"linear"``, or ``"sqrt"``.
* The default is S-curve, which tails off gradually at either end.
* The equation used for ``"s-curve"`` is ``y = (sin((x - 1/2)*pi) +
1)/2``, For an S-curve greyscale ramp, you should set
:attr:`pyvista.LookupTable.n_values` to 402 (which is ``256*pi/2``) to provide
room for the tails of the ramp.
* The equation for the ``"linear"`` is simply ``y = x``.
* The equation for the ``"sqrt"`` is ``y = sqrt(x)``.
Examples
--------
Show the default s-curve ramp.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0.0, 0.33)
>>> lut.ramp = 's-curve'
>>> lut.plot()
Plot the linear ramp.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0.0, 0.33)
>>> lut.ramp = 'linear'
>>> lut.plot()
Plot the ``"sqrt"`` ramp.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.hue_range = (0.0, 0.33)
>>> lut.ramp = 'sqrt'
>>> lut.plot()
"""
return RAMP_MAP[self.GetRamp()]
@ramp.setter
def ramp(self, value: str):
try:
self.SetRamp(RAMP_MAP_INV[value])
except KeyError:
msg = f'`ramp` must be one of the following:\n{list(RAMP_MAP_INV.keys())}'
raise ValueError(msg)
self.rebuild()
@property
def above_range_color(self) -> Color | None: # numpydoc ignore=RT01
"""Return or set the above range color.
Any values above :attr:`LookupTable.scalar_range` will be rendered with this
color.
Examples
--------
Enable the usage of the above range color.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.above_range_color = 'blue'
>>> lut.plot()
Disable the usage of the above range color.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.above_range_color = None
>>> lut.plot()
"""
if self.GetUseAboveRangeColor():
return Color(self.GetAboveRangeColor())
return None
@above_range_color.setter
def above_range_color(self, value: bool | ColorLike | None):
if value is None or value is False:
self.SetUseAboveRangeColor(False)
elif value is True:
self.SetAboveRangeColor(*Color(pyvista.global_theme.above_range_color).float_rgba)
self.SetUseAboveRangeColor(True)
else:
self.SetAboveRangeColor(*Color(value).float_rgba)
self.SetUseAboveRangeColor(True)
@property
def above_range_opacity(self): # numpydoc ignore=RT01
"""Return or set the above range opacity.
Examples
--------
Set the above range opacity to ``0.5``.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.above_range_color = 'grey'
>>> lut.above_range_opacity = 0.5
>>> lut.plot()
"""
color = self.above_range_color
return color.opacity # type: ignore[union-attr]
@above_range_opacity.setter
def above_range_opacity(self, value):
color = self.above_range_color
if color is None:
color = Color(pyvista.global_theme.above_range_color)
self.above_range_color = Color(color, opacity=value)
@property
def below_range_color(self) -> Color | None: # numpydoc ignore=RT01
"""Return or set the below range color.
Any values below :attr:`LookupTable.scalar_range` will be rendered with this
color.
Examples
--------
Enable the usage of the below range color.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.below_range_color = 'blue'
>>> lut.plot()
Disable the usage of the below range color.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.below_range_color = None
>>> lut.plot()
"""
if self.GetUseBelowRangeColor():
return Color(self.GetBelowRangeColor())
return None
@below_range_color.setter
def below_range_color(self, value: bool | ColorLike | None):
if value is None or value is False:
self.SetUseBelowRangeColor(False)
elif value is True:
self.SetBelowRangeColor(*Color(pyvista.global_theme.below_range_color).float_rgba)
self.SetUseBelowRangeColor(True)
else:
self.SetBelowRangeColor(*Color(value).float_rgba)
self.SetUseBelowRangeColor(True)
@property
def below_range_opacity(self): # numpydoc ignore=RT01
"""Return or set the below range opacity.
Examples
--------
Set the below range opacity to ``0.5``.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.below_range_color = 'grey'
>>> lut.below_range_opacity = 0.5
>>> lut.plot()
"""
color = self.below_range_color
return color.opacity # type: ignore[union-attr]
@below_range_opacity.setter
def below_range_opacity(self, value):
color = self.below_range_color
if color is None:
color = Color(pyvista.global_theme.below_range_color)
self.below_range_color = Color(color, opacity=value)
@_deprecate_positional_args(allowed=['cmap', 'n_values'])
def apply_cmap(
self,
cmap: ColormapOptions,
n_values: int = 256,
flip: bool = False, # noqa: FBT001, FBT002
):
"""Assign a colormap to this lookup table.
This can be used instead of :attr:`LookupTable.cmap` when you need to
set the number of values at the same time as the color map.
Parameters
----------
cmap : str, list, matplotlib.colors.Colormap
Colormap from Matplotlib, colorcet, or cmocean.
n_values : int, default: 256
Number of colors in the color map.
flip : bool, default: False
Flip direction of cmap. Most colormaps allow ``*_r`` suffix to do
this as well.
Examples
--------
Apply ``matplotlib``'s ``'cividis'`` color map.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.apply_cmap('cividis', n_values=32)
>>> lut.plot()
"""
if isinstance(cmap, list):
n_values = len(cmap)
cmap_obj = cmap if isinstance(cmap, mpl.colors.Colormap) else get_cmap_safe(cmap)
values = cmap_obj(np.linspace(0, 1, n_values)) * 255
if flip:
values = values[::-1]
self.values = values
self._values_manual = False
# reapply the opacity
opacity, interpolate, kind = self._opacity_parm
if opacity is not None:
self.apply_opacity(opacity=opacity, interpolate=interpolate, kind=kind)
self._cmap = cmap_obj
@_deprecate_positional_args(allowed=['opacity'])
def apply_opacity(self, opacity, interpolate: bool = True, kind: str = 'quadratic'): # noqa: FBT001, FBT002
"""Assign custom opacity to this lookup table.
Parameters
----------
opacity : float | array_like[float] | str
The opacity mapping to use. Can be a ``str`` name of a predefined
mapping including ``'linear'``, ``'geom'``, ``'sigmoid'``,
``'sigmoid_3-10'``. Append an ``'_r'`` to any of those names to
reverse that mapping. This can also be a custom array or list of
values that will be interpolated across the ``n_color`` range for
user defined mappings. Values must be between 0 and 1.
If a ``float``, simply applies the same opacity across the entire
colormap and must be between 0 and 1. Note that ``int`` values are
interpreted as if they were floats.
interpolate : bool, default: True
Flag on whether or not to interpolate the opacity mapping for all
colors.
kind : str, default: 'quadratic'
The interpolation kind if ``interpolate`` is ``True`` and ``scipy``
is available. See :class:`scipy.interpolate.interp1d` for the
available interpolation kinds.
If ``scipy`` is not available, ``'linear'`` interpolation is used.
Examples
--------
Apply a user defined custom opacity to a lookup table and plot the
random hills example.
>>> import pyvista as pv
>>> from pyvista import examples
>>> mesh = examples.load_random_hills()
>>> lut = pv.LookupTable(cmap='viridis')
>>> lut.apply_opacity([1.0, 0.4, 0.0, 0.4, 0.9])
>>> lut.scalar_range = (
... mesh.active_scalars.min(),
... mesh.active_scalars.max(),
... )
>>> pl = pv.Plotter()
>>> _ = pl.add_mesh(mesh, cmap=lut)
>>> pl.show()
"""
if isinstance(opacity, (float, int)):
if not 0 <= opacity <= 1:
msg = f'Opacity must be between 0 and 1, got {opacity}'
raise ValueError(msg)
self.values[:, -1] = opacity * 255
elif len(opacity) == self.n_values:
# no interpolation is necessary
self.values[:, -1] = np.array(opacity)
else:
self.values[:, -1] = opacity_transfer_function(
opacity,
self.n_values,
interpolate=interpolate,
kind=kind,
)
self._opacity_parm = (opacity, interpolate, kind)
@property
def values(self) -> lookup_table_ndarray: # numpydoc ignore=RT01
"""Return or set the lookup table values.
This attribute is used when creating a custom lookup table. The table
must be a RGBA array shaped ``(n, 4)``.
Examples
--------
Create a simple four value lookup table ranging from black to red.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.values = [
... [0, 0, 0, 255],
... [85, 0, 0, 255],
... [170, 0, 0, 255],
... [255, 0, 0, 255],
... ]
>>> lut.values
lookup_table_ndarray([[ 0, 0, 0, 255],
[ 85, 0, 0, 255],
[170, 0, 0, 255],
[255, 0, 0, 255]], dtype=uint8)
>>> lut.plot()
"""
return lookup_table_ndarray(self.GetTable(), table=self)
@values.setter
def values(self, new_values):
self._values_manual = True
self._cmap = None
new_values = np.asarray(new_values).astype(np.uint8, copy=False)
self.SetTable(_vtk.numpy_to_vtk(new_values))
@property
def n_values(self) -> int: # numpydoc ignore=RT01
"""Return or set the number of values in the lookup table.
Examples
--------
Plot the ``"reds"`` colormap with 10 values.
>>> import pyvista as pv
>>> lut = pv.LookupTable('reds')
>>> lut.n_values = 10
>>> lut.plot()
Plot the default colormap with 1024 values.
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> lut.n_values = 1024
>>> lut.plot()
"""
return self.GetNumberOfColors()
@n_values.setter
def n_values(self, value: int):
if self._cmap is not None:
self.apply_cmap(self._cmap, value)
self.SetNumberOfTableValues(value)
elif self._values_manual:
msg = (
'Number of values cannot be set when the values array has been manually set. '
'Reassign the values array if you wish to change the number of values.'
)
raise RuntimeError(msg)
else:
self.SetNumberOfColors(value)
self.ForceBuild()
@property
def annotations(self) -> dict[float, str]: # numpydoc ignore=RT01
"""Return or set annotations.
Pass a dictionary of annotations. Keys are the float values in the
scalars range to annotate on the scalar bar and the values are the
string annotations.
Examples
--------
Assign annotations to the lookup table.
>>> import pyvista as pv
>>> lut = pv.LookupTable('magma')
>>> lut.annotations = {0: 'low', 0.5: 'medium', 1: 'high'}
>>> lut.plot()
"""
vtk_values = self.GetAnnotatedValues()
if vtk_values is None:
return {} # type: ignore[unreachable]
n_items = vtk_values.GetSize()
keys = [vtk_values.GetValue(ii).ToFloat() for ii in range(n_items)] # type: ignore[attr-defined]
vtk_str = self.GetAnnotations()
values = [str(vtk_str.GetValue(ii)) for ii in range(n_items)]
return dict(zip(keys, values))
@annotations.setter
def annotations(self, values: dict[float, str] | None):
self.ResetAnnotations()
if values is not None:
for val, anno in values.items():
self.SetAnnotation(float(val), str(anno)) # type: ignore[call-overload]
@property
def _lookup_type(self) -> str:
"""Return the lookup type."""
if self.cmap:
if hasattr(self.cmap, 'name'):
return f'{self.cmap.name}'
else: # pragma: no cover
return f'{self.cmap}'
elif self._values_manual:
return 'From values array'
else:
return 'PyVista Lookup Table'
def plot(self, **kwargs):
"""Plot this lookup table.
Parameters
----------
**kwargs : dict, optional
Optional keyword arguments passed to :func:`pyvista.Plotter.show`.
Examples
--------
Plot the ``"viridis"`` colormap with the below and above colors.
>>> import pyvista as pv
>>> lut = pv.LookupTable('viridis', n_values=8)
>>> lut.below_range_color = 'black'
>>> lut.above_range_color = 'grey'
>>> lut.nan_color = 'r'
>>> lut.plot()
Plot only ``"blues"`` colormap.
>>> import pyvista as pv
>>> lut = pv.LookupTable('blues', n_values=1024)
>>> lut.plot()
"""
# need a trivial polydata for this
mesh = pyvista.PolyData(np.zeros((2, 3)))
mesh['Lookup Table'] = self.scalar_range
pl = pyvista.Plotter(window_size=[800, 230], off_screen=kwargs.pop('off_screen', None))
actor = pl.add_mesh(mesh, scalars=None, show_scalar_bar=False)
actor.mapper.lookup_table = self
actor.visibility = False
scalar_bar_kwargs = {
'color': 'k',
'title': self._lookup_type + '\n',
'outline': False,
'title_font_size': 40,
}
label_level = 0
if self.below_range_color:
scalar_bar_kwargs['below_label'] = 'below'
label_level = 1
if self.above_range_color:
scalar_bar_kwargs['above_label'] = 'above'
label_level = 1
label_level += self._nan_color_set
scalar_bar = pl.add_scalar_bar(**scalar_bar_kwargs)
scalar_bar.SetLookupTable(self)
scalar_bar.SetMaximumNumberOfColors(self.n_values)
scalar_bar.SetPosition(0.03, 0.1 + label_level * 0.1)
scalar_bar.SetPosition2(0.95, 0.9 - label_level * 0.1)
# scalar_bar.SetTextPad(-10)
if self._nan_color_set and self.nan_opacity > 0:
scalar_bar.SetDrawNanAnnotation(self._nan_color_set)
pl.background_color = kwargs.pop('background', 'w')
pl.show(**kwargs)
def to_color_tf(self) -> _vtk.vtkColorTransferFunction:
"""Return the VTK color transfer function of this table.
Returns
-------
:vtk:`vtkColorTransferFunction`
VTK color transfer function.
Examples
--------
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> tf = lut.to_color_tf()
>>> tf
<vtkmodules.vtkRenderingCore.vtkColorTransferFunction(...) at ...>
"""
color_tf = _vtk.vtkColorTransferFunction()
mn, mx = self.scalar_range
for value in np.linspace(mn, mx, self.n_values):
# Be sure to index the point by the value to map the scalar range
color_tf.AddRGBPoint(value, *self.map_value(value, opacity=False))
return color_tf
@_deprecate_positional_args
def to_opacity_tf(
self,
clamping: bool = True, # noqa: FBT001, FBT002
max_clip: float = 0.998,
) -> _vtk.vtkPiecewiseFunction:
"""Return the opacity transfer function of this table.
Parameters
----------
clamping : bool, optional
When zero range clamping is False, values returns 0.0 when a value is requested
outside of the points specified.
.. versionadded:: 0.44
max_clip : float, default: 0.998
The maximum value to clip the opacity to. This is useful for volume rendering
to avoid the jarring effect of completely opaque values.
.. versionadded:: 0.45
Returns
-------
:vtk:`vtkPiecewiseFunction`
Piecewise function of the opacity of this color table.
Examples
--------
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> tf = lut.to_opacity_tf()
>>> tf
<vtkmodules.vtkCommonDataModel.vtkPiecewiseFunction(...) at ...>
"""
opacity_tf = _vtk.vtkPiecewiseFunction()
opacity_tf.SetClamping(clamping)
mn, mx = self.scalar_range
for ii, value in enumerate(np.linspace(mn, mx, self.n_values)):
alpha = self.values[ii, 3]
# vtkPiecewiseFunction expects alphas between 0 and 1
# our lookup table is between 0 and 255
alpha = alpha / 255
alpha = min(alpha, max_clip)
opacity_tf.AddPoint(value, alpha)
return opacity_tf
@_deprecate_positional_args(allowed=['value'])
def map_value(
self,
value: float,
opacity: bool = True, # noqa: FBT001, FBT002
) -> tuple[float, float, float] | tuple[float, float, float, float]:
"""Map a single value through the lookup table, returning an RBG(A) color.
Parameters
----------
value : float
Scalar value to map to an RGB(A) color.
opacity : bool, default: True
Map the opacity as well.
Returns
-------
tuple
Mapped RGB(A) color.
Examples
--------
>>> import pyvista as pv
>>> lut = pv.LookupTable()
>>> rgba_color = lut.map_value(0.0)
>>> rgba_color
(1.0, 0.0, 0.0, 1.0)
"""
color = [0.0, 0.0, 0.0]
self.GetColor(value, color)
if opacity:
color.append(self.GetOpacity(value))
return cast(
'tuple[float, float, float] | tuple[float, float, float, float]',
tuple(color),
)
def __call__(self, value):
"""Implement a Matplotlib colormap-like call."""
if isinstance(value, (int, float)):
return self.map_value(value)
else:
try:
return np.array([self.map_value(item) for item in value])
except (TypeError, ValueError):
msg = 'LookupTable __call__ expects a single value or an iterable.'
raise TypeError(msg)