holoviz / panel
1
# coding: utf-8
2

3 7
import os
4 7
import base64
5 7
from io import BytesIO
6 7
from zipfile import ZipFile
7

8 7
import pytest
9 7
import numpy as np
10

11 7
try:
12 7
    import vtk
13 0
except Exception:
14 0
    vtk = None
15

16 7
try:
17 7
    import pyvista as pv
18 0
except Exception:
19 0
    pv = None
20

21 7
from six import string_types
22 7
from bokeh.models import ColorBar
23

24 7
from panel.models.vtk import VTKJSPlot, VTKVolumePlot, VTKAxes, VTKSynchronizedPlot
25 7
from panel.pane import PaneBase, VTKVolume, VTK
26 7
from panel.pane.vtk.vtk import VTKJS, VTKRenderWindowSynchronized, VTKRenderWindow
27

28 7
vtk_available = pytest.mark.skipif(vtk is None, reason="requires vtk")
29 7
pyvista_available = pytest.mark.skipif(pv is None, reason="requires pyvista")
30

31

32 7
def make_render_window():
33

34
    #cone actor
35 7
    cone = vtk.vtkConeSource()
36 7
    coneMapper = vtk.vtkPolyDataMapper()
37 7
    coneMapper.SetInputConnection(cone.GetOutputPort())
38 7
    coneActor = vtk.vtkActor()
39 7
    coneActor.SetMapper(coneMapper)
40

41
    #text actor following camera
42 7
    text = vtk.vtkVectorText()
43 7
    text.SetText("Origin")
44 7
    textMapper = vtk.vtkPolyDataMapper()
45 7
    textMapper.SetInputConnection(text.GetOutputPort())
46 7
    textActor = vtk.vtkFollower()
47 7
    textActor.SetMapper(textMapper)
48

49 7
    ren = vtk.vtkRenderer()
50 7
    ren.AddActor(coneActor)
51 7
    ren.AddActor(textActor)
52 7
    textActor.SetCamera(ren.GetActiveCamera())
53

54 7
    renWin = vtk.vtkRenderWindow()
55 7
    renWin.AddRenderer(ren)
56 7
    return renWin
57

58 7
def pyvista_render_window():
59
    """
60
    Allow to download and create a more complex example easily
61
    """
62 7
    from pyvista import examples
63 7
    sphere = pv.Sphere() #test actor
64 7
    globe = examples.load_globe() #test texture
65 7
    head = examples.download_head() #test volume
66 7
    uniform = examples.load_uniform() #test structured grid
67

68 7
    scalars=sphere.points[:, 2]
69 7
    sphere.point_arrays['test'] = scalars #allow to test scalars
70 7
    sphere.set_active_scalars('test')
71

72 7
    uniform.set_active_scalars("Spatial Cell Data")
73

74
    #test datasetmapper
75 7
    threshed = uniform.threshold_percent([0.15, 0.50], invert=True)
76 7
    bodies = threshed.split_bodies()
77 7
    mapper = vtk.vtkCompositePolyDataMapper2()
78 7
    mapper.SetInputDataObject(0, bodies)
79 7
    multiblock = vtk.vtkActor()
80 7
    multiblock.SetMapper(mapper)
81

82 7
    pl = pv.Plotter()
83 7
    pl.add_mesh(globe)
84 7
    pl.add_mesh(sphere)
85 7
    pl.add_mesh(uniform)
86 7
    pl.add_actor(multiblock)
87 7
    pl.add_volume(head)
88 7
    return pl.ren_win
89

90 7
def make_image_data():
91 7
    image_data = vtk.vtkImageData()
92 7
    image_data.SetDimensions(3, 4, 5)
93 7
    image_data.AllocateScalars(vtk.VTK_DOUBLE, 1)
94

95 7
    dims = image_data.GetDimensions()
96

97
    # Fill every entry of the image data with random double
98 7
    for z in range(dims[2]):
99 7
        for y in range(dims[1]):
100 7
            for x in range(dims[0]):
101 7
                image_data.SetScalarComponentFromDouble(x, y, z, 0, np.random.rand())
102 7
    return image_data
103

104

105 7
def test_get_vtkjs_pane_type_from_url():
106 7
    url = r'https://raw.githubusercontent.com/Kitware/vtk-js/master/Data/StanfordDragon.vtkjs'
107 7
    assert PaneBase.get_pane_type(url) is VTKJS
108

109

110 7
def test_get_vtkjs_pane_type_from_file():
111 7
    file = r'StanfordDragon.vtkjs'
112 7
    assert PaneBase.get_pane_type(file) is VTKJS
113

114

115 7
@vtk_available
116 2
def test_get_vtk_pane_type_from_render_window():
117 7
    assert PaneBase.get_pane_type(vtk.vtkRenderWindow()) is VTKRenderWindowSynchronized
118 7
    assert PaneBase.get_pane_type(vtk.vtkRenderWindow(), serialize_on_instantiation=True) is VTKRenderWindow
119

120

121 7
def test_get_vtkvol_pane_type_from_np_array():
122 7
    assert PaneBase.get_pane_type(np.array([]).reshape((0,0,0))) is VTKVolume
123

124

125 7
@vtk_available
126 2
def test_get_vtkvol_pane_type_from_vtk_image():
127 7
    image_data = make_image_data()
128 7
    assert PaneBase.get_pane_type(image_data) is VTKVolume
129

130

131 7
def test_vtkjs_pane(document, comm, tmp_path):
132
    # from url
133 7
    url = r'https://raw.githubusercontent.com/Kitware/vtk-js/master/Data/StanfordDragon.vtkjs'
134

135 7
    pane_from_url = VTK(url)
136

137
    # Create pane
138 7
    model = pane_from_url.get_root(document, comm=comm)
139 7
    assert isinstance(model, VTKJSPlot)
140 7
    assert pane_from_url._models[model.ref['id']][0] is model
141 7
    assert isinstance(model.data, string_types)
142

143 7
    with BytesIO(base64.b64decode(model.data.encode())) as in_memory:
144 7
        with ZipFile(in_memory) as zf:
145 7
            filenames = zf.namelist()
146 7
            assert len(filenames) == 9
147 7
            assert 'StanfordDragon.obj/index.json' in filenames
148

149
    # Export Update and Read
150 7
    tmpfile = os.path.join(*tmp_path.joinpath('export.vtkjs').parts)
151 7
    pane_from_url.export_vtkjs(filename=tmpfile)
152 7
    with open(tmpfile, 'rb') as  file_exported:
153 7
        pane_from_url.object = file_exported
154

155
    #test from file
156 7
    pane_from_file = VTK(tmpfile)
157 7
    model_from_file = pane_from_file.get_root(document, comm=comm)
158 7
    assert isinstance(pane_from_file, VTKJS)
159 7
    assert isinstance(model_from_file, VTKJSPlot)
160

161

162 7
@vtk_available
163 2
def test_vtk_pane_from_renwin(document, comm):
164 7
    renWin = make_render_window()
165 7
    pane = VTK(renWin)
166

167
    # Create pane
168 7
    model = pane.get_root(document, comm=comm)
169 7
    assert isinstance(model, VTKSynchronizedPlot)
170 7
    assert pane._models[model.ref['id']][0] is model
171

172
    # Check array release when actor are removed from scene
173 7
    ctx = pane._contexts[model.id]
174 7
    assert len(ctx.dataArrayCache.keys()) == 5
175 7
    pane.remove_all_actors()
176
    # Default : 20s before removing arrays
177 7
    assert len(ctx.dataArrayCache.keys()) == 5
178
    # Force 0s for removing arrays
179 7
    ctx.checkForArraysToRelease(0)
180 7
    assert len(ctx.dataArrayCache.keys()) == 0
181

182
    # Cleanup
183 7
    pane._cleanup(model)
184 7
    assert pane._contexts == {}
185 7
    assert pane._models == {}
186

187 7
@vtk_available
188 2
def test_vtk_serialize_on_instantiation(document, comm, tmp_path):
189 7
    renWin = make_render_window()
190 7
    pane = VTK(renWin, serialize_on_instantiation=True)
191 7
    assert isinstance(pane, VTKRenderWindow)
192

193 7
    model = pane.get_root(document, comm=comm)
194 7
    assert isinstance(model, VTKSynchronizedPlot)
195

196 7
    pane.param.trigger('object')
197

198
    # test export to file
199 7
    tmpfile = os.path.join(*tmp_path.joinpath('scene').parts)
200 7
    exported_file = pane.export_scene(filename=tmpfile)
201 7
    assert exported_file.endswith('.synch')
202

203
    # test import from file
204 7
    imported_pane = VTK.import_scene(filename=exported_file,
205
                                     synchronizable=False)
206 7
    assert isinstance(imported_pane, VTKRenderWindow)
207

208

209 7
@vtk_available
210 2
def test_vtk_sync_helpers(document, comm):
211 7
    renWin1 = make_render_window()
212 7
    renWin2 = make_render_window()
213

214
    # Create 2 panes to compare each other
215 7
    pane1 = VTK(renWin1)
216 7
    pane2 = VTK(renWin2)
217

218 7
    assert isinstance(pane1, VTKRenderWindowSynchronized)
219 7
    assert isinstance(pane2, VTKRenderWindowSynchronized)
220

221
    # Create get models
222 7
    model1 = pane1.get_root(document, comm=comm)
223 7
    model2 = pane2.get_root(document, comm=comm)
224

225 7
    assert isinstance(model1, VTKSynchronizedPlot)
226 7
    assert isinstance(model2, VTKSynchronizedPlot)
227

228
    # Actors getter
229 7
    assert len(pane1.actors) == 2
230 7
    assert len(pane2.actors) == 2
231 7
    assert pane1.actors[0] is not pane2.actors[0]
232

233
    # Actors add
234 7
    pane1.add_actors(pane2.actors)
235 7
    assert len(pane1.actors) == 4
236 7
    assert pane1.actors[3] is pane2.actors[1]
237

238
    # Actors remove
239 7
    save_actor = pane1.actors[0]
240 7
    pane1.remove_actors([pane1.actors[0]])
241 7
    assert pane1.actors[2] is pane2.actors[1]
242

243
    # Actors remove all
244 7
    pane1.add_actors([save_actor])
245 7
    assert len(pane1.actors) == 4
246 7
    pane1.remove_all_actors()
247 7
    assert len(pane1.actors) == 0
248

249
    # Connect camera
250 7
    save_vtk_camera2 = pane2.vtk_camera
251 7
    assert pane1.vtk_camera is not save_vtk_camera2
252 7
    pane1.link_camera(pane2)
253 7
    assert pane1.vtk_camera is save_vtk_camera2
254

255
    # Unconnect camera
256 7
    pane2.unlink_camera()
257 7
    assert pane2.vtk_camera is not save_vtk_camera2
258

259
    # SetBackground
260 7
    pane1.set_background(0, 0, 0)
261 7
    assert list(renWin1.GetRenderers())[0].GetBackground() == (0, 0, 0)
262

263
    # Cleanup
264 7
    pane1._cleanup(model1)
265 7
    pane2._cleanup(model2)
266

267

268 7
@pyvista_available
269 2
def test_vtk_pane_more_complex(document, comm, tmp_path):
270 7
    renWin = pyvista_render_window()
271 7
    pane = VTK(renWin)
272

273
    # Create pane
274 7
    model = pane.get_root(document, comm=comm)
275 7
    assert isinstance(model, VTKSynchronizedPlot)
276 7
    assert pane._models[model.ref['id']][0] is model
277

278 7
    colorbars_infered = pane.construct_colorbars().object
279

280 7
    assert len(colorbars_infered.below) == 2 # infer only actor color bars
281 7
    assert all(isinstance(cb, ColorBar) for cb in colorbars_infered.below)
282

283 7
    colorbars_in_scene = pane.construct_colorbars(infer=False).object()
284 7
    assert len(colorbars_in_scene.below) == 3
285 7
    assert all(isinstance(cb, ColorBar) for cb in colorbars_in_scene.below)
286
    # add axes
287 7
    pane.axes = dict(
288
        origin = [-5, 5, -2],
289
        xticker = {'ticks': np.linspace(-5,5,5)},
290
        yticker = {'ticks': np.linspace(-5,5,5)},
291
        zticker = {'ticks': np.linspace(-2,2,5),
292
                   'labels': [''] + [str(int(item)) for item in np.linspace(-2,2,5)[1:]]},
293
        fontsize = 12,
294
        digits = 1,
295
        grid_opacity = 0.5,
296
        show_grid=True
297
    )
298 7
    assert isinstance(model.axes, VTKAxes)
299

300
    # test export to file
301 7
    tmpfile = os.path.join(*tmp_path.joinpath('scene').parts)
302 7
    exported_file = pane.export_scene(filename=tmpfile)
303 7
    assert exported_file.endswith('.synch')
304

305
    # test import from file
306
    # (TODO test if the scene imported is identical to the one exported)
307 7
    imported_scene = VTK.import_scene(filename=exported_file)
308 7
    assert isinstance(imported_scene, VTKRenderWindowSynchronized)
309

310
    # Cleanup
311 7
    pane._cleanup(model)
312 7
    assert pane._contexts == {}
313 7
    assert pane._models == {}
314

315

316 7
@vtk_available
317 2
def test_vtkvol_pane_from_np_array(document, comm):
318
    # Test empty initialisation
319 7
    pane = VTKVolume()
320 7
    model = pane.get_root(document, comm=comm)
321

322 7
    pane.object = np.ones((10,10,10))
323 7
    from operator import eq
324
    # Create pane
325 7
    assert isinstance(model, VTKVolumePlot)
326 7
    assert pane._models[model.ref['id']][0] is model
327 7
    assert np.all(np.frombuffer(base64.b64decode(model.data['buffer'].encode())) == 1)
328 7
    assert all([eq(getattr(pane, k), getattr(model, k))
329
                for k in ['slice_i', 'slice_j', 'slice_k']])
330

331
    # Test update data
332 7
    pane.object = 2*np.ones((10,10,10))
333 7
    assert np.all(np.frombuffer(base64.b64decode(model.data['buffer'].encode())) == 2)
334

335
    # Test size limitation of date sent
336 7
    pane.max_data_size = 0.1 # limit data size to 0.1MB
337
    # with uint8
338 7
    data = (255*np.random.rand(50,50,50)).astype(np.uint8)
339 7
    assert data.nbytes/1e6 > 0.1
340 7
    pane.object = data
341 7
    data_model = np.frombuffer(base64.b64decode(model.data['buffer'].encode()))
342 7
    assert data_model.nbytes/1e6 <= 0.1
343
    # with float64
344 7
    data = np.random.rand(50,50,50)
345 7
    assert data.nbytes/1e6 > 0.1
346 7
    pane.object = data
347 7
    data_model = np.frombuffer(base64.b64decode(model.data['buffer'].encode()), dtype=np.float64)
348 7
    assert data_model.nbytes/1e6 <= 0.1
349

350
    # Test conversion of the slice_i number with subsample array
351 7
    param = pane._process_property_change({'slice_i': (np.cbrt(data_model.size)-1)//2})
352 7
    assert param == {'slice_i': (50-1)//2}
353

354
    # Cleanup
355 7
    pane._cleanup(model)
356 7
    assert pane._models == {}
357

358

359 7
@vtk_available
360 2
def test_vtkvol_pane_from_image_data(document, comm):
361 7
    image_data = make_image_data()
362 7
    pane = VTKVolume(image_data)
363 7
    from operator import eq
364
    # Create pane
365 7
    model = pane.get_root(document, comm=comm)
366 7
    assert isinstance(model, VTKVolumePlot)
367 7
    assert pane._models[model.ref['id']][0] is model
368 7
    assert all([eq(getattr(pane, k), getattr(model, k))
369
                for k in ['slice_i', 'slice_j', 'slice_k']])
370
    # Cleanup
371 7
    pane._cleanup(model)
372 7
    assert pane._models == {}

Read our documentation on viewing source code .

Loading