/
volume_property.py
359 lines (287 loc) · 12.3 KB
/
volume_property.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
"""This module contains the VolumeProperty class."""
import pyvista as pv
from pyvista import _vtk
from pyvista.utilities.misc import no_new_attr
@no_new_attr
class VolumeProperty(_vtk.vtkVolumeProperty):
"""Wrap the VTK class 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. Default 1.0.
specular : float, optional
The specular lighting coefficient. Default 0.0.
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, [0, 3.0, -0, 1.0, 0, 1.0], dim=(40, 40, 40))
>>> grid['scalars'] -= grid['scalars'].min()
>>> grid['scalars']*= 255/grid['scalars'].max()
>>> pl = pv.Plotter()
>>> actor = pl.add_volume(grid, show_scalar_bar=False)
>>> lut = pv.LookupTable(cmap='bwr')
>>> lut.apply_opacity([1.0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.0, 0.3])
>>> actor.prop.apply_lookup_table(lut)
>>> pl.show()
"""
def __init__(
self,
lookup_table=None,
interpolation_type=None,
ambient=None,
diffuse=None,
specular=None,
specular_power=None,
shade=None,
opacity_unit_distance=None,
):
"""Initialize the vtkVolumeProperty class."""
super().__init__()
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
def apply_lookup_table(self, lookup_table: 'pv.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, [0, 3.0, -0, 1.0, 0, 1.0], dim=(40, 40, 40))
>>> grid['scalars'] -= grid['scalars'].min()
>>> grid['scalars']*= 255/grid['scalars'].max()
>>> pl = pv.Plotter()
>>> actor = pl.add_volume(grid, show_scalar_bar=False)
>>> lut = pv.LookupTable(cmap='bwr')
>>> lut.apply_opacity([1.0, 0.0, 0.0, 0.3, 0.0, 0.0, 0.0, 0.3])
>>> actor.prop.apply_lookup_table(lut)
>>> pl.show()
"""
if not isinstance(lookup_table, pv.LookupTable):
raise TypeError('`lookup_table` must be a `pyvista.LookupTable`')
self.SetColor(lookup_table.to_color_tf())
self.SetScalarOpacity(lookup_table.to_opacity_tf())
@property
def interpolation_type(self) -> str:
"""Return or set the interpolation type.
Value must be either ``'linear'`` or ``'nearest'``.
Examples
--------
Create a sample :class:`pyvista.UniformGrid` dataset.
>>> import numpy as np
>>> import pyvista as pv
>>> n = 21
>>> c = -(n-1)/2
>>> vol = pv.UniformGrid(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:
raise ValueError('`interpolation_type` must be either "linear" or "nearest"')
@property
def opacity_unit_distance(self) -> float:
"""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:
"""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:
"""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:
"""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:
"""Return or set the diffuse lighting coefficient.
Default 1.0.
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:
"""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:
"""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() + ':'
value = getattr(self, attr)
if callable(value):
continue
if isinstance(value, str):
value = f'"{value}"'
props.append(f' {name:28s} {value}')
return '\n'.join(props)