vispy 0.9.5__cp38-cp38-win_amd64.whl → 0.14.0__cp38-cp38-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vispy might be problematic. Click here for more details.
- vispy/app/backends/_glfw.py +2 -2
- vispy/app/backends/_pyglet.py +8 -2
- vispy/app/backends/_qt.py +88 -63
- vispy/app/backends/_wx.py +6 -1
- vispy/app/canvas.py +4 -2
- vispy/app/tests/test_canvas.py +52 -1
- vispy/app/tests/test_context.py +5 -3
- vispy/color/color_array.py +8 -1
- vispy/color/colormap.py +5 -25
- vispy/geometry/meshdata.py +76 -38
- vispy/geometry/rect.py +6 -0
- vispy/geometry/tests/test_meshdata.py +72 -0
- vispy/gloo/buffer.py +12 -0
- vispy/gloo/gl/_constants.py +9 -5
- vispy/gloo/gl/_es2.py +8 -4
- vispy/gloo/gl/_gl2.py +2 -3
- vispy/gloo/gl/_proxy.py +1 -1
- vispy/gloo/gl/_pyopengl2.py +12 -7
- vispy/gloo/gl/tests/test_names.py +3 -0
- vispy/gloo/glir.py +26 -13
- vispy/gloo/program.py +39 -22
- vispy/gloo/tests/test_program.py +9 -2
- vispy/gloo/tests/test_texture.py +19 -2
- vispy/gloo/texture.py +46 -16
- vispy/gloo/wrappers.py +4 -2
- vispy/glsl/build_spatial_filters.py +241 -293
- vispy/glsl/misc/spatial-filters.frag +1299 -254
- vispy/io/_data/spatial-filters.npy +0 -0
- vispy/io/datasets.py +2 -2
- vispy/io/image.py +1 -1
- vispy/io/stl.py +3 -3
- vispy/scene/cameras/base_camera.py +6 -2
- vispy/scene/cameras/panzoom.py +10 -14
- vispy/scene/cameras/perspective.py +6 -0
- vispy/scene/cameras/tests/test_cameras.py +27 -0
- vispy/scene/cameras/tests/test_perspective.py +37 -0
- vispy/scene/cameras/turntable.py +39 -23
- vispy/scene/canvas.py +9 -5
- vispy/scene/events.py +9 -0
- vispy/scene/node.py +19 -2
- vispy/scene/tests/test_canvas.py +30 -1
- vispy/scene/tests/test_visuals.py +113 -0
- vispy/scene/visuals.py +6 -1
- vispy/scene/widgets/viewbox.py +3 -2
- vispy/testing/_runners.py +6 -12
- vispy/testing/_testing.py +3 -4
- vispy/util/check_environment.py +4 -4
- vispy/util/gallery_scraper.py +50 -32
- vispy/util/tests/test_gallery_scraper.py +2 -0
- vispy/util/transforms.py +1 -1
- vispy/util/wrappers.py +1 -1
- vispy/version.py +2 -3
- vispy/visuals/__init__.py +2 -0
- vispy/visuals/_scalable_textures.py +20 -17
- vispy/visuals/collections/array_list.py +3 -3
- vispy/visuals/collections/base_collection.py +1 -1
- vispy/visuals/ellipse.py +1 -1
- vispy/visuals/filters/__init__.py +3 -2
- vispy/visuals/filters/base_filter.py +120 -0
- vispy/visuals/filters/clipping_planes.py +24 -12
- vispy/visuals/filters/markers.py +28 -0
- vispy/visuals/filters/mesh.py +61 -6
- vispy/visuals/filters/tests/test_primitive_picking_filters.py +70 -0
- vispy/visuals/graphs/graph.py +1 -1
- vispy/visuals/image.py +114 -26
- vispy/visuals/image_complex.py +130 -0
- vispy/visuals/instanced_mesh.py +152 -0
- vispy/visuals/isocurve.py +1 -1
- vispy/visuals/line/dash_atlas.py +46 -41
- vispy/visuals/line/line.py +2 -5
- vispy/visuals/markers.py +310 -384
- vispy/visuals/mesh.py +2 -2
- vispy/visuals/shaders/function.py +3 -0
- vispy/visuals/shaders/tests/test_function.py +6 -0
- vispy/visuals/tests/test_axis.py +2 -2
- vispy/visuals/tests/test_image.py +92 -2
- vispy/visuals/tests/test_image_complex.py +36 -0
- vispy/visuals/tests/test_instanced_mesh.py +50 -0
- vispy/visuals/tests/test_markers.py +6 -0
- vispy/visuals/tests/test_mesh.py +17 -0
- vispy/visuals/tests/test_text.py +11 -0
- vispy/visuals/tests/test_volume.py +218 -12
- vispy/visuals/text/_sdf_cpu.cp38-win_amd64.pyd +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +21 -23
- vispy/visuals/text/text.py +9 -3
- vispy/visuals/tube.py +2 -2
- vispy/visuals/visual.py +144 -3
- vispy/visuals/volume.py +300 -131
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/LICENSE.txt +1 -1
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/METADATA +218 -198
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/RECORD +93 -96
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/WHEEL +1 -1
- vispy/glsl/antialias/__init__.py +0 -0
- vispy/glsl/arrowheads/__init__.py +0 -0
- vispy/glsl/arrows/__init__.py +0 -0
- vispy/glsl/collections/__init__.py +0 -0
- vispy/glsl/colormaps/__init__.py +0 -0
- vispy/glsl/lines/__init__.py +0 -0
- vispy/glsl/markers/__init__.py +0 -0
- vispy/glsl/math/__init__.py +0 -0
- vispy/glsl/misc/__init__.py +0 -0
- vispy/glsl/transforms/__init__.py +0 -0
- {vispy-0.9.5.dist-info → vispy-0.14.0.dist-info}/top_level.txt +0 -0
vispy/visuals/mesh.py
CHANGED
|
@@ -283,8 +283,8 @@ class MeshVisual(Visual):
|
|
|
283
283
|
clim_func = 'float cmap(float val) { return (val - $cmin) / ($cmax - $cmin); }'
|
|
284
284
|
if colors.ndim == 2 and colors.shape[1] == 1:
|
|
285
285
|
fun = Function(clim_func)
|
|
286
|
-
fun['cmin'] = self.
|
|
287
|
-
fun['cmax'] = self.
|
|
286
|
+
fun['cmin'] = self._clim_values[0]
|
|
287
|
+
fun['cmax'] = self._clim_values[1]
|
|
288
288
|
fun = FunctionChain(None, [fun, Function(self.cmap.glsl_map)])
|
|
289
289
|
else:
|
|
290
290
|
fun = Function(null_color_transform)
|
|
@@ -241,6 +241,12 @@ def test_function_basics():
|
|
|
241
241
|
assert_equal(fun.name, 'main')
|
|
242
242
|
assert len(fun.template_vars) == 2
|
|
243
243
|
assert_in('foo', fun.template_vars)
|
|
244
|
+
|
|
245
|
+
# Test that `var in fun` syntax works as well
|
|
246
|
+
assert 'foo' in fun
|
|
247
|
+
assert 'bar' in fun
|
|
248
|
+
assert 'baz' not in fun
|
|
249
|
+
|
|
244
250
|
assert_in('bar', fun.template_vars)
|
|
245
251
|
|
|
246
252
|
# Test setting verbatim expressions
|
vispy/visuals/tests/test_axis.py
CHANGED
|
@@ -50,12 +50,12 @@ def test_rotation_angle():
|
|
|
50
50
|
view.camera.elevation = 90.
|
|
51
51
|
|
|
52
52
|
assert_allclose(axis1._rotation_angle, 0)
|
|
53
|
-
assert_allclose(axis2._rotation_angle, -30)
|
|
53
|
+
assert_allclose(axis2._rotation_angle, -30, rtol=1e-3)
|
|
54
54
|
|
|
55
55
|
view.camera.elevation = 45.
|
|
56
56
|
|
|
57
57
|
assert_allclose(axis1._rotation_angle, 0)
|
|
58
|
-
assert_allclose(axis2._rotation_angle, -22.207653)
|
|
58
|
+
assert_allclose(axis2._rotation_angle, -22.207653, rtol=1e-3)
|
|
59
59
|
|
|
60
60
|
view.camera.fov = 20.
|
|
61
61
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
from unittest import mock
|
|
3
3
|
|
|
4
|
-
from vispy.scene
|
|
4
|
+
from vispy.scene import Image, PanZoomCamera
|
|
5
5
|
from vispy.testing import (requires_application, TestingCanvas,
|
|
6
6
|
run_tests_if_main, IS_CI)
|
|
7
7
|
from vispy.testing.image_tester import assert_image_approved, downsample
|
|
@@ -76,7 +76,7 @@ def _get_orig_and_new_clims(input_dtype):
|
|
|
76
76
|
def test_image_clims_and_gamma(input_dtype, texture_format, num_channels,
|
|
77
77
|
clim_on_init, data_on_init):
|
|
78
78
|
"""Test image visual with clims and gamma on shader."""
|
|
79
|
-
size = (
|
|
79
|
+
size = (80, 80)
|
|
80
80
|
if texture_format == '__dtype__':
|
|
81
81
|
texture_format = input_dtype
|
|
82
82
|
shape = size + (num_channels,) if num_channels > 0 else size
|
|
@@ -297,4 +297,94 @@ def test_change_clim_float(dtype, init_clim):
|
|
|
297
297
|
assert np.allclose(rendered1, rendered2)
|
|
298
298
|
|
|
299
299
|
|
|
300
|
+
@requires_application()
|
|
301
|
+
def test_image_interpolation():
|
|
302
|
+
"""Test different interpolations"""
|
|
303
|
+
size = (81, 81)
|
|
304
|
+
data = np.array([[0, 1]], dtype=int)
|
|
305
|
+
left = (40, 0)
|
|
306
|
+
right = (40, 80)
|
|
307
|
+
center_left = (40, 39)
|
|
308
|
+
center = (40, 40)
|
|
309
|
+
center_right = (40, 41)
|
|
310
|
+
white = (255, 255, 255, 255)
|
|
311
|
+
black = (0, 0, 0, 255)
|
|
312
|
+
gray = (128, 128, 128, 255)
|
|
313
|
+
|
|
314
|
+
with TestingCanvas(size=size[::-1], bgcolor="w") as c:
|
|
315
|
+
view = c.central_widget.add_view(border_width=0)
|
|
316
|
+
view.camera = PanZoomCamera((0, 0, 2, 1))
|
|
317
|
+
image = Image(data=data, cmap='grays',
|
|
318
|
+
parent=view.scene)
|
|
319
|
+
|
|
320
|
+
# needed to properly initialize the canvas
|
|
321
|
+
render = c.render()
|
|
322
|
+
|
|
323
|
+
image.interpolation = 'nearest'
|
|
324
|
+
render = c.render()
|
|
325
|
+
assert np.allclose(render[left], black)
|
|
326
|
+
assert np.allclose(render[right], white)
|
|
327
|
+
assert np.allclose(render[center_left], black)
|
|
328
|
+
assert np.allclose(render[center_right], white)
|
|
329
|
+
|
|
330
|
+
image.interpolation = 'bilinear'
|
|
331
|
+
render = c.render()
|
|
332
|
+
assert np.allclose(render[left], black)
|
|
333
|
+
assert np.allclose(render[right], white)
|
|
334
|
+
assert np.allclose(render[center], gray, atol=5) # we just want gray, this is not quantitative
|
|
335
|
+
|
|
336
|
+
image.interpolation = 'custom'
|
|
337
|
+
image.custom_kernel = np.array([[0]]) # no sampling
|
|
338
|
+
render = c.render()
|
|
339
|
+
assert np.allclose(render[left], black)
|
|
340
|
+
assert np.allclose(render[right], black)
|
|
341
|
+
assert np.allclose(render[center], black)
|
|
342
|
+
|
|
343
|
+
image.custom_kernel = np.array([[1]]) # same as linear
|
|
344
|
+
render = c.render()
|
|
345
|
+
assert np.allclose(render[left], black)
|
|
346
|
+
assert np.allclose(render[right], white)
|
|
347
|
+
assert np.allclose(render[center], gray, atol=5) # we just want gray, this is not quantitative
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@requires_application()
|
|
351
|
+
def test_image_set_data_different_dtype():
|
|
352
|
+
size = (80, 80)
|
|
353
|
+
data = np.array([[0, 127]], dtype=np.int8)
|
|
354
|
+
left = (40, 10)
|
|
355
|
+
right = (40, 70)
|
|
356
|
+
white = (255, 255, 255, 255)
|
|
357
|
+
black = (0, 0, 0, 255)
|
|
358
|
+
|
|
359
|
+
with TestingCanvas(size=size[::-1], bgcolor="w") as c:
|
|
360
|
+
view = c.central_widget.add_view()
|
|
361
|
+
view.camera = PanZoomCamera((0, 0, 2, 1))
|
|
362
|
+
image = Image(data=data, cmap='grays', clim=[0, 127],
|
|
363
|
+
parent=view.scene)
|
|
364
|
+
|
|
365
|
+
render = c.render()
|
|
366
|
+
assert np.allclose(render[left], black)
|
|
367
|
+
assert np.allclose(render[right], white)
|
|
368
|
+
|
|
369
|
+
# same data as float should change nothing
|
|
370
|
+
image.set_data(data.astype(np.float32))
|
|
371
|
+
render = c.render()
|
|
372
|
+
assert np.allclose(render[left], black)
|
|
373
|
+
assert np.allclose(render[right], white)
|
|
374
|
+
|
|
375
|
+
# something inverted, different dtype
|
|
376
|
+
new_data = np.array([[127, 0]], dtype=np.float16)
|
|
377
|
+
image.set_data(new_data)
|
|
378
|
+
render = c.render()
|
|
379
|
+
assert np.allclose(render[left], white)
|
|
380
|
+
assert np.allclose(render[right], black)
|
|
381
|
+
|
|
382
|
+
# out of bounds should clip (2000 > 127)
|
|
383
|
+
new_data = np.array([[0, 2000]], dtype=np.float64)
|
|
384
|
+
image.set_data(new_data)
|
|
385
|
+
render = c.render()
|
|
386
|
+
assert np.allclose(render[left], black)
|
|
387
|
+
assert np.allclose(render[right], white)
|
|
388
|
+
|
|
389
|
+
|
|
300
390
|
run_tests_if_main()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from vispy.scene.visuals import ComplexImage
|
|
2
|
+
from vispy.visuals.image_complex import CPU_COMPLEX_TRANSFORMS
|
|
3
|
+
from vispy.testing import requires_application, TestingCanvas
|
|
4
|
+
from vispy.testing.image_tester import downsample
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from vispy.testing.rendered_array_tester import compare_render
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# we add the np.float32 case to test that ComplexImage falls back to Image behavior
|
|
13
|
+
# if the data is not complex
|
|
14
|
+
@requires_application()
|
|
15
|
+
@pytest.mark.parametrize("input_dtype", [np.complex64, np.complex128, np.float32])
|
|
16
|
+
@pytest.mark.parametrize("complex_mode", ["magnitude", "real", "imaginary", "phase"])
|
|
17
|
+
def test_image_complex(input_dtype, complex_mode):
|
|
18
|
+
"""Test rendering of complex-valued image data."""
|
|
19
|
+
shape = (40, 40)
|
|
20
|
+
np.random.seed(0)
|
|
21
|
+
data = np.random.random(shape).astype(input_dtype)
|
|
22
|
+
if np.iscomplexobj(data):
|
|
23
|
+
data.imag = np.random.random(shape)
|
|
24
|
+
|
|
25
|
+
with TestingCanvas(size=shape, bgcolor="w") as c:
|
|
26
|
+
ComplexImage(data, cmap="grays", complex_mode=complex_mode, parent=c.scene)
|
|
27
|
+
# render to canvas
|
|
28
|
+
rendered = c.render()
|
|
29
|
+
shape_ratio = rendered.shape[0] // data.shape[0]
|
|
30
|
+
rendered = downsample(rendered, shape_ratio, axis=(0, 1))
|
|
31
|
+
|
|
32
|
+
# perform (auto-clim) rendering on cpu
|
|
33
|
+
exp = CPU_COMPLEX_TRANSFORMS[complex_mode](data) if np.iscomplexobj(data) else data
|
|
34
|
+
exp -= exp.min()
|
|
35
|
+
exp /= exp.max()
|
|
36
|
+
compare_render(exp, rendered)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from vispy import scene, use
|
|
3
|
+
|
|
4
|
+
from vispy.testing import (TestingCanvas, requires_application,
|
|
5
|
+
run_tests_if_main, requires_pyopengl)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def setup_module(module):
|
|
9
|
+
use(gl='gl+')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def teardown_module(module):
|
|
13
|
+
use(gl='gl2')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@requires_pyopengl()
|
|
17
|
+
@requires_application()
|
|
18
|
+
def test_mesh_with_vertex_values():
|
|
19
|
+
size = (80, 60)
|
|
20
|
+
with TestingCanvas(size=size) as c:
|
|
21
|
+
use(gl='gl+')
|
|
22
|
+
vert = np.array([[0, 0, 0], [0, 30, 0], [40, 0, 0]])
|
|
23
|
+
faces = np.array([0, 1, 2])
|
|
24
|
+
pos = np.array([[0, 0, 0], [80, 60, 0]])
|
|
25
|
+
# identity and rotate 180
|
|
26
|
+
trans = np.array([
|
|
27
|
+
[
|
|
28
|
+
[1, 0, 0],
|
|
29
|
+
[0, 1, 0],
|
|
30
|
+
[0, 0, 1],
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
[-1, 0, 0],
|
|
34
|
+
[0, -1, 0],
|
|
35
|
+
[0, 0, 1],
|
|
36
|
+
],
|
|
37
|
+
])
|
|
38
|
+
colors = ['red', 'blue']
|
|
39
|
+
mesh = scene.visuals.InstancedMesh(
|
|
40
|
+
vertices=vert, faces=faces, instance_positions=pos, instance_transforms=trans, instance_colors=colors
|
|
41
|
+
)
|
|
42
|
+
v = c.central_widget.add_view(border_width=0)
|
|
43
|
+
v.add(mesh)
|
|
44
|
+
render = c.render()
|
|
45
|
+
assert np.allclose(render[10, 10], (255, 0, 0, 255))
|
|
46
|
+
assert np.allclose(render[-10, -10], (0, 0, 255, 255))
|
|
47
|
+
assert np.allclose(render[30, 40], (0, 0, 0, 255))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
run_tests_if_main()
|
|
@@ -45,4 +45,10 @@ def test_markers_edge_width():
|
|
|
45
45
|
with pytest.raises(ValueError):
|
|
46
46
|
marker.set_data(pos=data, edge_width_rel=edge_width - 1, edge_width=None)
|
|
47
47
|
|
|
48
|
+
|
|
49
|
+
def test_empty_markers_symbol():
|
|
50
|
+
markers = Markers()
|
|
51
|
+
markers.symbol = 'o'
|
|
52
|
+
|
|
53
|
+
|
|
48
54
|
run_tests_if_main()
|
vispy/visuals/tests/test_mesh.py
CHANGED
|
@@ -31,6 +31,23 @@ def test_mesh_color():
|
|
|
31
31
|
np.testing.assert_allclose(vertices['position'], new_vertices)
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
@requires_pyopengl()
|
|
35
|
+
@requires_application()
|
|
36
|
+
def test_mesh_with_vertex_values():
|
|
37
|
+
size = (45, 40)
|
|
38
|
+
with TestingCanvas(size=size) as c:
|
|
39
|
+
v = c.central_widget.add_view(border_width=0)
|
|
40
|
+
vertices = np.array([(0, 0, 0), (0, 0, 1), (1, 0, 0)], dtype=float)
|
|
41
|
+
faces = np.array([(0, 1, 2)])
|
|
42
|
+
mesh = scene.visuals.Mesh(
|
|
43
|
+
vertices=vertices,
|
|
44
|
+
faces=faces,
|
|
45
|
+
vertex_values=np.ones(len(vertices)),
|
|
46
|
+
)
|
|
47
|
+
v.add(mesh)
|
|
48
|
+
c.render()
|
|
49
|
+
|
|
50
|
+
|
|
34
51
|
@requires_pyopengl()
|
|
35
52
|
@requires_application()
|
|
36
53
|
@pytest.mark.parametrize('shading', [None, 'flat', 'smooth'])
|
vispy/visuals/tests/test_text.py
CHANGED
|
@@ -81,4 +81,15 @@ def test_face_bold_italic():
|
|
|
81
81
|
assert font1 is font4
|
|
82
82
|
|
|
83
83
|
|
|
84
|
+
def test_text_depth_test():
|
|
85
|
+
t = Text(depth_test=False)
|
|
86
|
+
assert not t._vshare.gl_state["depth_test"]
|
|
87
|
+
|
|
88
|
+
t = Text(depth_test=True)
|
|
89
|
+
assert t._vshare.gl_state["depth_test"]
|
|
90
|
+
|
|
91
|
+
t = Text() # Default is false
|
|
92
|
+
assert not t._vshare.gl_state["depth_test"]
|
|
93
|
+
|
|
94
|
+
|
|
84
95
|
run_tests_if_main()
|
|
@@ -238,7 +238,7 @@ def _make_test_data(shape, input_dtype):
|
|
|
238
238
|
@requires_pyopengl()
|
|
239
239
|
def test_set_data_does_not_change_input():
|
|
240
240
|
# Create volume
|
|
241
|
-
V = scene.visuals.Volume(np.zeros((20, 20, 20)))
|
|
241
|
+
V = scene.visuals.Volume(np.zeros((20, 20, 20), dtype=np.float32))
|
|
242
242
|
|
|
243
243
|
# calling Volume.set_data() should NOT alter the values of the input array
|
|
244
244
|
# regardless of data type
|
|
@@ -249,12 +249,36 @@ def test_set_data_does_not_change_input():
|
|
|
249
249
|
V.set_data(vol_copy, clim=(0, 200))
|
|
250
250
|
assert np.allclose(vol, vol_copy)
|
|
251
251
|
|
|
252
|
-
#
|
|
253
|
-
#
|
|
254
|
-
|
|
252
|
+
# dtype has to be the same as the one used to init the texture, or it will
|
|
253
|
+
# be first coerced to the same dtype as the init
|
|
254
|
+
|
|
255
|
+
vol2 = np.array(vol, dtype=np.float32, copy=True)
|
|
255
256
|
assert np.allclose(vol, vol2)
|
|
257
|
+
# we explicitly create a copy when data would be altered by the texture,
|
|
258
|
+
# no matter what the user asks, so the data outside should never change
|
|
256
259
|
V.set_data(vol2, clim=(0, 200), copy=False)
|
|
257
|
-
assert
|
|
260
|
+
assert np.allclose(vol, vol2)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@requires_pyopengl()
|
|
264
|
+
def test_set_data_changes_shape():
|
|
265
|
+
dtype = np.float32
|
|
266
|
+
# Create initial volume
|
|
267
|
+
V = scene.visuals.Volume(np.zeros((20, 20, 20), dtype=dtype))
|
|
268
|
+
|
|
269
|
+
# Sending new three dimensional data of different shape should alter volume shape
|
|
270
|
+
vol = np.zeros((25, 25, 10), dtype=dtype)
|
|
271
|
+
V.set_data(vol)
|
|
272
|
+
assert V._vol_shape == (25, 25, 10)
|
|
273
|
+
|
|
274
|
+
# Sending data of dimension other than 3 should raise a ValueError
|
|
275
|
+
vol2 = np.zeros((20, 20), dtype=dtype)
|
|
276
|
+
with pytest.raises(ValueError):
|
|
277
|
+
V.set_data(vol2)
|
|
278
|
+
|
|
279
|
+
vol2 = np.zeros((20, 20, 20, 20), dtype=dtype)
|
|
280
|
+
with pytest.raises(ValueError):
|
|
281
|
+
V.set_data(vol2)
|
|
258
282
|
|
|
259
283
|
|
|
260
284
|
@requires_pyopengl()
|
|
@@ -296,17 +320,17 @@ def test_changing_cmap():
|
|
|
296
320
|
@requires_pyopengl()
|
|
297
321
|
@requires_application()
|
|
298
322
|
def test_plane_depth():
|
|
299
|
-
with TestingCanvas(size=(
|
|
323
|
+
with TestingCanvas(size=(80, 80)) as c:
|
|
300
324
|
v = c.central_widget.add_view(border_width=0)
|
|
301
325
|
v.camera = 'arcball'
|
|
302
326
|
v.camera.fov = 0
|
|
303
|
-
v.camera.center = (
|
|
304
|
-
v.camera.scale_factor =
|
|
327
|
+
v.camera.center = (40, 40, 40)
|
|
328
|
+
v.camera.scale_factor = 80.0
|
|
305
329
|
|
|
306
330
|
# two planes at 45 degrees relative to the camera. If depth is set correctly, we should see one half
|
|
307
331
|
# of the screen red and the other half white
|
|
308
332
|
scene.visuals.Volume(
|
|
309
|
-
np.ones((
|
|
333
|
+
np.ones((80, 80, 80), dtype=np.uint8),
|
|
310
334
|
interpolation="nearest",
|
|
311
335
|
clim=(0, 1),
|
|
312
336
|
cmap="grays",
|
|
@@ -316,7 +340,7 @@ def test_plane_depth():
|
|
|
316
340
|
)
|
|
317
341
|
|
|
318
342
|
scene.visuals.Volume(
|
|
319
|
-
np.ones((
|
|
343
|
+
np.ones((80, 80, 80), dtype=np.uint8),
|
|
320
344
|
interpolation="nearest",
|
|
321
345
|
clim=(0, 1),
|
|
322
346
|
cmap="reds",
|
|
@@ -327,10 +351,192 @@ def test_plane_depth():
|
|
|
327
351
|
|
|
328
352
|
# render with grays colormap
|
|
329
353
|
rendered = c.render()
|
|
330
|
-
left = rendered[
|
|
331
|
-
right = rendered[
|
|
354
|
+
left = rendered[40, 20]
|
|
355
|
+
right = rendered[40, 60]
|
|
332
356
|
assert np.array_equal(left, [255, 0, 0, 255])
|
|
333
357
|
assert np.array_equal(right, [255, 255, 255, 255])
|
|
334
358
|
|
|
335
359
|
|
|
360
|
+
@requires_pyopengl()
|
|
361
|
+
@requires_application()
|
|
362
|
+
def test_volume_depth():
|
|
363
|
+
"""Check that depth setting is properly performed for the volume visual
|
|
364
|
+
|
|
365
|
+
Render a volume with a blue ball in front of a red plane in front of a
|
|
366
|
+
blue plane, checking that the output image contains both red and blue pixels.
|
|
367
|
+
"""
|
|
368
|
+
# A blue strip behind a red strip
|
|
369
|
+
# If depth is set correctly, we should see only red pixels
|
|
370
|
+
# the screen
|
|
371
|
+
blue_vol = np.zeros((80, 80, 80), dtype=np.uint8)
|
|
372
|
+
blue_vol[:, -1, :] = 1 # back plane blue
|
|
373
|
+
blue_vol[30:50, 30:50, 30:50] = 1 # blue in center
|
|
374
|
+
|
|
375
|
+
red_vol = np.zeros((80, 80, 80), dtype=np.uint8)
|
|
376
|
+
red_vol[:, -5, :] = 1 # red plane in front of blue plane
|
|
377
|
+
|
|
378
|
+
with TestingCanvas(size=(80, 80)) as c:
|
|
379
|
+
v = c.central_widget.add_view(border_width=0)
|
|
380
|
+
v.camera = 'arcball'
|
|
381
|
+
v.camera.fov = 0
|
|
382
|
+
v.camera.center = (40, 40, 40)
|
|
383
|
+
v.camera.scale_factor = 80.0
|
|
384
|
+
|
|
385
|
+
scene.visuals.Volume(
|
|
386
|
+
red_vol,
|
|
387
|
+
interpolation="nearest",
|
|
388
|
+
clim=(0, 1),
|
|
389
|
+
cmap="reds",
|
|
390
|
+
parent=v.scene,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
scene.visuals.Volume(
|
|
394
|
+
blue_vol,
|
|
395
|
+
interpolation="nearest",
|
|
396
|
+
clim=(0, 1),
|
|
397
|
+
cmap="blues",
|
|
398
|
+
parent=v.scene,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
# render
|
|
402
|
+
rendered = c.render()
|
|
403
|
+
reds = np.sum(rendered[:, :, 0])
|
|
404
|
+
greens = np.sum(rendered[:, :, 1])
|
|
405
|
+
blues = np.sum(rendered[:, :, 2])
|
|
406
|
+
assert reds > 0
|
|
407
|
+
np.testing.assert_allclose(greens, 0)
|
|
408
|
+
assert blues > 0
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@requires_pyopengl()
|
|
412
|
+
@requires_application()
|
|
413
|
+
def test_mip_cutoff():
|
|
414
|
+
"""
|
|
415
|
+
Ensure fragments are properly discarded based on the mip_cutoff
|
|
416
|
+
for the mip and attenuated_mip rendering methods
|
|
417
|
+
"""
|
|
418
|
+
with TestingCanvas(size=(80, 80)) as c:
|
|
419
|
+
v = c.central_widget.add_view(border_width=0)
|
|
420
|
+
v.camera = 'arcball'
|
|
421
|
+
v.camera.fov = 0
|
|
422
|
+
v.camera.center = (40, 40, 40)
|
|
423
|
+
v.camera.scale_factor = 80.0
|
|
424
|
+
|
|
425
|
+
vol = scene.visuals.Volume(
|
|
426
|
+
np.ones((80, 80, 80), dtype=np.uint8),
|
|
427
|
+
interpolation="nearest",
|
|
428
|
+
clim=(0, 1),
|
|
429
|
+
cmap="grays",
|
|
430
|
+
parent=v.scene,
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
# we should see white
|
|
434
|
+
rendered = c.render()
|
|
435
|
+
assert np.array_equal(rendered[40, 40], [255, 255, 255, 255])
|
|
436
|
+
|
|
437
|
+
vol.mip_cutoff = 10
|
|
438
|
+
# we should see black
|
|
439
|
+
rendered = c.render()
|
|
440
|
+
assert np.array_equal(rendered[40, 40], [0, 0, 0, 255])
|
|
441
|
+
|
|
442
|
+
# repeat for attenuated_mip
|
|
443
|
+
vol.method = 'attenuated_mip'
|
|
444
|
+
vol.mip_cutoff = None
|
|
445
|
+
|
|
446
|
+
# we should see white
|
|
447
|
+
rendered = c.render()
|
|
448
|
+
assert np.array_equal(rendered[40, 40], [255, 255, 255, 255])
|
|
449
|
+
|
|
450
|
+
vol.mip_cutoff = 10
|
|
451
|
+
# we should see black
|
|
452
|
+
rendered = c.render()
|
|
453
|
+
assert np.array_equal(rendered[40, 40], [0, 0, 0, 255])
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@requires_pyopengl()
|
|
457
|
+
@requires_application()
|
|
458
|
+
def test_minip_cutoff():
|
|
459
|
+
"""
|
|
460
|
+
Ensure fragments are properly discarded based on the minip_cutoff
|
|
461
|
+
for the minip rendering method
|
|
462
|
+
"""
|
|
463
|
+
with TestingCanvas(size=(80, 80)) as c:
|
|
464
|
+
v = c.central_widget.add_view(border_width=0)
|
|
465
|
+
v.camera = 'arcball'
|
|
466
|
+
v.camera.fov = 0
|
|
467
|
+
v.camera.center = (40, 40, 40)
|
|
468
|
+
v.camera.scale_factor = 120.0
|
|
469
|
+
|
|
470
|
+
# just surface of the cube is ones, but it should win over the twos inside
|
|
471
|
+
data = np.ones((80, 80, 80), dtype=np.uint8)
|
|
472
|
+
data[1:-1, 1:-1, 1:-1] = 2
|
|
473
|
+
|
|
474
|
+
vol = scene.visuals.Volume(
|
|
475
|
+
data,
|
|
476
|
+
interpolation="nearest",
|
|
477
|
+
method='minip',
|
|
478
|
+
clim=(0, 2),
|
|
479
|
+
cmap="grays",
|
|
480
|
+
parent=v.scene,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
# we should see gray (half of cmap)
|
|
484
|
+
rendered = c.render()
|
|
485
|
+
assert np.array_equal(rendered[40, 40], [128, 128, 128, 255])
|
|
486
|
+
|
|
487
|
+
# discard fragments above -10 (everything)
|
|
488
|
+
vol.minip_cutoff = -10
|
|
489
|
+
# we should see black
|
|
490
|
+
rendered = c.render()
|
|
491
|
+
assert np.array_equal(rendered[40, 40], [0, 0, 0, 255])
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
@requires_pyopengl()
|
|
495
|
+
@requires_application()
|
|
496
|
+
def test_volume_set_data_different_dtype():
|
|
497
|
+
size = (80, 80)
|
|
498
|
+
data = np.array([[[0, 127]]], dtype=np.int8)
|
|
499
|
+
left = (40, 10)
|
|
500
|
+
right = (40, 70)
|
|
501
|
+
white = (255, 255, 255, 255)
|
|
502
|
+
black = (0, 0, 0, 255)
|
|
503
|
+
|
|
504
|
+
with TestingCanvas(size=size[::-1], bgcolor="w") as c:
|
|
505
|
+
view = c.central_widget.add_view()
|
|
506
|
+
view.camera = 'arcball'
|
|
507
|
+
view.camera.fov = 0
|
|
508
|
+
view.camera.center = 0.5, 0, 0
|
|
509
|
+
view.camera.scale_factor = 2
|
|
510
|
+
volume = scene.visuals.Volume(
|
|
511
|
+
data,
|
|
512
|
+
cmap='grays',
|
|
513
|
+
clim=[0, 127],
|
|
514
|
+
parent=view.scene
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
render = c.render()
|
|
518
|
+
assert np.allclose(render[left], black)
|
|
519
|
+
assert np.allclose(render[right], white)
|
|
520
|
+
|
|
521
|
+
# same data as float should change nothing
|
|
522
|
+
volume.set_data(data.astype(np.float32))
|
|
523
|
+
render = c.render()
|
|
524
|
+
assert np.allclose(render[left], black)
|
|
525
|
+
assert np.allclose(render[right], white)
|
|
526
|
+
|
|
527
|
+
# something inverted, different dtype
|
|
528
|
+
new_data = np.array([[[127, 0]]], dtype=np.float16)
|
|
529
|
+
volume.set_data(new_data)
|
|
530
|
+
render = c.render()
|
|
531
|
+
assert np.allclose(render[left], white)
|
|
532
|
+
assert np.allclose(render[right], black)
|
|
533
|
+
|
|
534
|
+
# out of bounds should clip (2000 > 127)
|
|
535
|
+
new_data = np.array([[[0, 2000]]], dtype=np.float64)
|
|
536
|
+
volume.set_data(new_data)
|
|
537
|
+
render = c.render()
|
|
538
|
+
assert np.allclose(render[left], black)
|
|
539
|
+
assert np.allclose(render[right], white)
|
|
540
|
+
|
|
541
|
+
|
|
336
542
|
run_tests_if_main()
|
|
Binary file
|
vispy/visuals/text/_sdf_cpu.pyx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
1
|
+
# cython: language_level=3, boundscheck=False, cdivision=True, wraparound=False, initializedcheck=False, nonecheck=False
|
|
3
2
|
# A Cython implementation of the "eight-points signed sequential Euclidean
|
|
4
3
|
# distance transform algorithm" (8SSEDT)
|
|
5
4
|
|
|
@@ -18,13 +17,14 @@ ctypedef np.complex64_t DTYPE_ct
|
|
|
18
17
|
cdef DTYPE_ct MAX_VAL = (1e6 + 1e6j)
|
|
19
18
|
|
|
20
19
|
|
|
21
|
-
@cython.boundscheck(False) # designed to stay within bounds
|
|
22
|
-
@cython.wraparound(False) # we don't use negative indexing
|
|
23
20
|
def _calc_distance_field(np.ndarray[DTYPE_t, ndim=2] pixels,
|
|
24
21
|
int w, int h, DTYPE_t sp_f):
|
|
25
22
|
# initialize grids
|
|
26
|
-
cdef np.ndarray[DTYPE_ct, ndim=2]
|
|
27
|
-
cdef np.ndarray[DTYPE_ct, ndim=2]
|
|
23
|
+
cdef np.ndarray[DTYPE_ct, ndim=2] g0_arr = np.zeros((h, w), dtype_c)
|
|
24
|
+
cdef np.ndarray[DTYPE_ct, ndim=2] g1_arr = np.zeros((h, w), dtype_c)
|
|
25
|
+
cdef DTYPE_ct[:, ::1] g0 = g0_arr
|
|
26
|
+
cdef DTYPE_ct[:, ::1] g1 = g1_arr
|
|
27
|
+
cdef DTYPE_t[:, :] pixels_view = pixels
|
|
28
28
|
cdef Py_ssize_t y, x
|
|
29
29
|
for y in range(h):
|
|
30
30
|
g0[y, 0] = MAX_VAL
|
|
@@ -32,9 +32,9 @@ def _calc_distance_field(np.ndarray[DTYPE_t, ndim=2] pixels,
|
|
|
32
32
|
g1[y, 0] = MAX_VAL
|
|
33
33
|
g1[y, w-1] = MAX_VAL
|
|
34
34
|
for x in range(1, w-1):
|
|
35
|
-
if
|
|
35
|
+
if pixels_view[y, x] > 0:
|
|
36
36
|
g0[y, x] = MAX_VAL
|
|
37
|
-
if
|
|
37
|
+
if pixels_view[y, x] < 1:
|
|
38
38
|
g1[y, x] = MAX_VAL
|
|
39
39
|
for x in range(w):
|
|
40
40
|
g0[0, x] = MAX_VAL
|
|
@@ -50,37 +50,35 @@ def _calc_distance_field(np.ndarray[DTYPE_t, ndim=2] pixels,
|
|
|
50
50
|
cdef DTYPE_t r_sp_f_2 = 1. / (sp_f * 2.)
|
|
51
51
|
for y in range(1, h-1):
|
|
52
52
|
for x in range(1, w-1):
|
|
53
|
-
|
|
54
|
-
if
|
|
55
|
-
|
|
53
|
+
pixels_view[y, x] = sqrt(dist(g0[y, x])) - sqrt(dist(g1[y, x]))
|
|
54
|
+
if pixels_view[y, x] < 0:
|
|
55
|
+
pixels_view[y, x] = (pixels_view[y, x] + sp_f) * r_sp_f_2
|
|
56
56
|
else:
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
pixels_view[y, x] = 0.5 + pixels_view[y, x] * r_sp_f_2
|
|
58
|
+
pixels_view[y, x] = max(min(pixels_view[y, x], 1), 0)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
@cython.wraparound(False) # we don't use negative indexing
|
|
63
|
-
cdef Py_ssize_t compare(DTYPE_ct *cell, DTYPE_ct xy, DTYPE_t *current):
|
|
61
|
+
cdef inline Py_ssize_t compare(DTYPE_ct *cell, DTYPE_ct xy, DTYPE_t *current) noexcept nogil:
|
|
64
62
|
cdef DTYPE_t val = dist(xy)
|
|
65
63
|
if val < current[0]:
|
|
66
64
|
cell[0] = xy
|
|
67
65
|
current[0] = val
|
|
68
66
|
|
|
69
67
|
|
|
70
|
-
|
|
71
|
-
@cython.wraparound(False) # we don't use negative indexing
|
|
72
|
-
cdef DTYPE_t dist(DTYPE_ct val):
|
|
68
|
+
cdef DTYPE_t dist(DTYPE_ct val) noexcept nogil:
|
|
73
69
|
return val.real*val.real + val.imag*val.imag
|
|
74
70
|
|
|
75
71
|
|
|
76
|
-
|
|
77
|
-
@cython.wraparound(False) # we don't use negative indexing
|
|
78
|
-
cdef void _propagate(np.ndarray[DTYPE_ct, ndim=2] grid):
|
|
72
|
+
cdef void _propagate(DTYPE_ct[:, :] grid) noexcept nogil:
|
|
79
73
|
cdef Py_ssize_t height = grid.shape[0]
|
|
80
74
|
cdef Py_ssize_t width = grid.shape[1]
|
|
81
75
|
cdef Py_ssize_t y, x
|
|
82
76
|
cdef DTYPE_t current
|
|
83
|
-
cdef DTYPE_ct a0
|
|
77
|
+
cdef DTYPE_ct a0, a1, a2, a3
|
|
78
|
+
a0 = -1
|
|
79
|
+
a1 = -1j
|
|
80
|
+
a2 = -1 - 1j
|
|
81
|
+
a3 = 1 - 1j
|
|
84
82
|
cdef DTYPE_ct b0=1
|
|
85
83
|
cdef DTYPE_ct c0=1, c1=1j, c2=-1+1j, c3=1+1j
|
|
86
84
|
cdef DTYPE_ct d0=-1
|