1
# coding: utf-8
2

3 7
from __future__ import absolute_import
4

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

10 7
import pytest
11 7
import numpy as np
12

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

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

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

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

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

33

34 7
def make_render_window():
35

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

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

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

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

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

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

74 7
    uniform.set_active_scalars("Spatial Cell Data")
75

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

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

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

97 7
    dims = image_data.GetDimensions()
98

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

106

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

111

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

116

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

122

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

126

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

132

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

137 7
    pane_from_url = VTK(url)
138

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

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

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

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

163

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

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

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

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

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

195 7
    model = pane.get_root(document, comm=comm)
196 7
    assert isinstance(model, VTKSynchronizedPlot)
197

198 7
    pane.param.trigger('object')
199

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

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

210

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

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

220 7
    assert isinstance(pane1, VTKRenderWindowSynchronized)
221 7
    assert isinstance(pane2, VTKRenderWindowSynchronized)
222

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

227 7
    assert isinstance(model1, VTKSynchronizedPlot)
228 7
    assert isinstance(model2, VTKSynchronizedPlot)
229

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

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

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

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

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

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

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

265
    # Cleanup
266 7
    pane1._cleanup(model1)
267 7
    pane2._cleanup(model2)
268

269

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

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

280 7
    colorbars_infered = pane.construct_colorbars().object
281

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

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

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

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

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

317

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

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

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

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

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

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

360

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

Read our documentation on viewing source code .

Loading