1
# coding: utf-8
2

3 2
from __future__ import absolute_import
4

5 2
import os
6 2
import base64
7 2
from io import BytesIO
8 2
from zipfile import ZipFile
9

10 2
import pytest
11 2
import numpy as np
12

13 2
try:
14 2
    import vtk
15 0
except Exception:
16 0
    vtk = None
17

18 2
try:
19 2
    import pyvista as pv
20 0
except Exception:
21 0
    pv = None
22

23 2
from six import string_types
24 2
from bokeh.models import ColorBar
25

26 2
from panel.models.vtk import VTKJSPlot, VTKVolumePlot, VTKAxes, VTKSynchronizedPlot
27 2
from panel.pane import PaneBase, VTKVolume, VTK
28 2
from panel.pane.vtk.vtk import VTKJS, VTKRenderWindowSynchronized, VTKRenderWindow
29

30 2
vtk_available = pytest.mark.skipif(vtk is None, reason="requires vtk")
31 2
pyvista_available = pytest.mark.skipif(pv is None, reason="requires pyvista")
32

33

34 2
def make_render_window():
35

36
    #cone actor
37 2
    cone = vtk.vtkConeSource()
38 2
    coneMapper = vtk.vtkPolyDataMapper()
39 2
    coneMapper.SetInputConnection(cone.GetOutputPort())
40 2
    coneActor = vtk.vtkActor()
41 2
    coneActor.SetMapper(coneMapper)
42

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

51 2
    ren = vtk.vtkRenderer()
52 2
    ren.AddActor(coneActor)
53 2
    ren.AddActor(textActor)
54 2
    textActor.SetCamera(ren.GetActiveCamera())
55

56 2
    renWin = vtk.vtkRenderWindow()
57 2
    renWin.AddRenderer(ren)
58 2
    return renWin
59

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

70 2
    scalars=sphere.points[:, 2]
71 2
    sphere._add_point_array(scalars, 'test', set_active=True) #allow to test scalars
72

73 2
    uniform.set_active_scalars("Spatial Cell Data")
74

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

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

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

96 2
    dims = image_data.GetDimensions()
97

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

105

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

110

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

115

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

121

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

125

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

131

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

136 2
    pane_from_url = VTK(url)
137

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

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

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

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

162

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

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

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

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

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

194 2
    model = pane.get_root(document, comm=comm)
195 2
    assert isinstance(model, VTKSynchronizedPlot)
196

197 2
    pane.param.trigger('object')
198

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

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

209

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

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

219 2
    assert isinstance(pane1, VTKRenderWindowSynchronized)
220 2
    assert isinstance(pane2, VTKRenderWindowSynchronized)
221

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

226 2
    assert isinstance(model1, VTKSynchronizedPlot)
227 2
    assert isinstance(model2, VTKSynchronizedPlot)
228

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

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

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

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

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

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

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

264
    # Cleanup
265 2
    pane1._cleanup(model1)
266 2
    pane2._cleanup(model2)
267

268

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

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

279 2
    colorbars_infered = pane.construct_colorbars().object
280

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

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

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

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

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

316

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

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

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

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

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

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

359

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

Read our documentation on viewing source code .

Loading