vispy 0.14.1__cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.14.3__cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.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/_qt.py +7 -7
- vispy/geometry/__init__.py +1 -1
- vispy/geometry/calculations.py +30 -2
- vispy/geometry/triangulation.py +4 -2
- vispy/gloo/buffer.py +4 -4
- vispy/gloo/gl/tests/test_functionality.py +4 -2
- vispy/gloo/glir.py +10 -2
- vispy/gloo/texture.py +4 -3
- vispy/plot/plotwidget.py +1 -1
- vispy/testing/_runners.py +3 -1
- vispy/testing/image_tester.py +21 -0
- vispy/util/__init__.py +15 -0
- vispy/version.py +2 -2
- vispy/visuals/_scalable_textures.py +2 -1
- vispy/visuals/collections/array_list.py +4 -4
- vispy/visuals/ellipse.py +4 -5
- vispy/visuals/image.py +2 -1
- vispy/visuals/markers.py +23 -14
- vispy/visuals/surface_plot.py +2 -2
- vispy/visuals/tests/test_surface_plot.py +52 -0
- vispy/visuals/text/_sdf_cpu.cpython-310-aarch64-linux-gnu.so +0 -0
- vispy/visuals/text/_sdf_cpu.pyx +2 -0
- vispy/visuals/volume.py +35 -4
- {vispy-0.14.1.dist-info → vispy-0.14.3.dist-info}/LICENSE.txt +1 -1
- {vispy-0.14.1.dist-info → vispy-0.14.3.dist-info}/METADATA +6 -4
- {vispy-0.14.1.dist-info → vispy-0.14.3.dist-info}/RECORD +462 -461
- {vispy-0.14.1.dist-info → vispy-0.14.3.dist-info}/WHEEL +1 -1
- {vispy-0.14.1.dist-info → vispy-0.14.3.dist-info}/top_level.txt +0 -0
vispy/app/backends/_qt.py
CHANGED
|
@@ -386,9 +386,9 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
386
386
|
|
|
387
387
|
# must set physical size before setting visible or fullscreen
|
|
388
388
|
# operations may make the size invalid
|
|
389
|
-
if hasattr(self, '
|
|
389
|
+
if hasattr(self, 'devicePixelRatioF'):
|
|
390
390
|
# handle high DPI displays in PyQt5
|
|
391
|
-
ratio = self.
|
|
391
|
+
ratio = self.devicePixelRatioF()
|
|
392
392
|
else:
|
|
393
393
|
ratio = 1
|
|
394
394
|
self._physical_size = (p.size[0] * ratio, p.size[1] * ratio)
|
|
@@ -421,7 +421,7 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
421
421
|
|
|
422
422
|
If display resolutions are the same this is essentially a no-op except for the redraw.
|
|
423
423
|
If the display resolutions differ (HiDPI versus regular displays) the canvas needs to
|
|
424
|
-
be redrawn to reset the physical size based on the current `
|
|
424
|
+
be redrawn to reset the physical size based on the current `devicePixelRatioF()` and
|
|
425
425
|
redrawn with that new size.
|
|
426
426
|
|
|
427
427
|
"""
|
|
@@ -909,11 +909,11 @@ class CanvasBackendDesktop(QtBaseCanvasBackend, QGLWidget):
|
|
|
909
909
|
def resizeGL(self, w, h):
|
|
910
910
|
if self._vispy_canvas is None:
|
|
911
911
|
return
|
|
912
|
-
if hasattr(self, '
|
|
913
|
-
# We take into account
|
|
912
|
+
if hasattr(self, 'devicePixelRatioF'):
|
|
913
|
+
# We take into account devicePixelRatioF, which is non-unity on
|
|
914
914
|
# e.g HiDPI displays.
|
|
915
|
-
# self.
|
|
916
|
-
ratio = self.
|
|
915
|
+
# self.devicePixelRatioF() is a float and should have been in Qt5 according to the documentation
|
|
916
|
+
ratio = self.devicePixelRatioF()
|
|
917
917
|
w = int(w * ratio)
|
|
918
918
|
h = int(h * ratio)
|
|
919
919
|
self._vispy_set_physical_size(w, h)
|
vispy/geometry/__init__.py
CHANGED
|
@@ -16,7 +16,7 @@ from .meshdata import MeshData # noqa
|
|
|
16
16
|
from .rect import Rect # noqa
|
|
17
17
|
from .triangulation import Triangulation, triangulate # noqa
|
|
18
18
|
from .torusknot import TorusKnot # noqa
|
|
19
|
-
from .calculations import (_calculate_normals, _fast_cross_3d, # noqa
|
|
19
|
+
from .calculations import (_calculate_normals, _cross_2d, _fast_cross_3d, # noqa
|
|
20
20
|
resize) # noqa
|
|
21
21
|
from .generation import (create_arrow, create_box, create_cone, # noqa
|
|
22
22
|
create_cube, create_cylinder, create_grid_mesh, # noqa
|
vispy/geometry/calculations.py
CHANGED
|
@@ -7,8 +7,33 @@
|
|
|
7
7
|
import numpy as np
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
def _cross_2d(x, y):
|
|
11
|
+
"""Compute the z-component of the cross product of (arrays of) 2D vectors.
|
|
12
|
+
|
|
13
|
+
This is meant to replicate the 2D functionality of np.cross(), which is
|
|
14
|
+
deprecated in numpy 2.0.
|
|
15
|
+
|
|
16
|
+
x and y must have broadcastable shapes, with the last dimension being 2.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
x : array
|
|
21
|
+
Input array 1, shape (..., 2).
|
|
22
|
+
y : array
|
|
23
|
+
Input array 2, shape (..., 2).
|
|
24
|
+
|
|
25
|
+
Returns
|
|
26
|
+
-------
|
|
27
|
+
z : array
|
|
28
|
+
z-component of cross products of x and y.
|
|
29
|
+
|
|
30
|
+
See: https://github.com/numpy/numpy/issues/26620
|
|
31
|
+
"""
|
|
32
|
+
if x.shape[-1] != 2 or y.shape[-1] != 2:
|
|
33
|
+
raise ValueError("Input arrays must have shape (..., 2)")
|
|
34
|
+
|
|
35
|
+
return x[..., 0] * y[..., 1] - x[..., 1] * y[..., 0]
|
|
36
|
+
|
|
12
37
|
|
|
13
38
|
def _fast_cross_3d(x, y):
|
|
14
39
|
"""Compute cross product between list of 3D vectors
|
|
@@ -47,6 +72,9 @@ def _fast_cross_3d(x, y):
|
|
|
47
72
|
return np.cross(x, y)
|
|
48
73
|
|
|
49
74
|
|
|
75
|
+
###############################################################################
|
|
76
|
+
# These fast normal calculation routines are adapted from mne-python
|
|
77
|
+
|
|
50
78
|
def _calculate_normals(rr, tris):
|
|
51
79
|
"""Efficiently compute vertex normals for triangulated surface"""
|
|
52
80
|
# ensure highest precision for our summation/vectorization "trick"
|
vispy/geometry/triangulation.py
CHANGED
|
@@ -7,6 +7,8 @@ import numpy as np
|
|
|
7
7
|
|
|
8
8
|
from collections import OrderedDict
|
|
9
9
|
|
|
10
|
+
from .calculations import _cross_2d
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class Triangulation(object):
|
|
12
14
|
"""Constrained delaunay triangulation
|
|
@@ -676,7 +678,7 @@ class Triangulation(object):
|
|
|
676
678
|
A = self.pts[a]
|
|
677
679
|
B = self.pts[b]
|
|
678
680
|
C = self.pts[c]
|
|
679
|
-
return
|
|
681
|
+
return _cross_2d(B - A, C - B) > 0
|
|
680
682
|
|
|
681
683
|
def _edges_intersect(self, edge1, edge2):
|
|
682
684
|
"""Return 1 if edges intersect completely (endpoints excluded)"""
|
|
@@ -739,7 +741,7 @@ class Triangulation(object):
|
|
|
739
741
|
"""
|
|
740
742
|
v1 = self.pts[point] - self.pts[edge[0]]
|
|
741
743
|
v2 = self.pts[edge[1]] - self.pts[edge[0]]
|
|
742
|
-
c =
|
|
744
|
+
c = _cross_2d(v1, v2) # positive if v1 is CW from v2
|
|
743
745
|
return 1 if c > 0 else (-1 if c < 0 else 0)
|
|
744
746
|
|
|
745
747
|
def _add_tri(self, a, b, c):
|
vispy/gloo/buffer.py
CHANGED
|
@@ -10,7 +10,7 @@ from traceback import extract_stack, format_list
|
|
|
10
10
|
import weakref
|
|
11
11
|
|
|
12
12
|
from . globject import GLObject
|
|
13
|
-
from ..util import logger
|
|
13
|
+
from ..util import logger, np_copy_if_needed
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# ------------------------------------------------------------ Buffer class ---
|
|
@@ -70,7 +70,7 @@ class Buffer(GLObject):
|
|
|
70
70
|
data is actually uploaded to GPU memory.
|
|
71
71
|
Asking explicitly for a copy will prevent this behavior.
|
|
72
72
|
"""
|
|
73
|
-
data = np.array(data, copy=copy)
|
|
73
|
+
data = np.array(data, copy=copy or np_copy_if_needed)
|
|
74
74
|
nbytes = data.nbytes
|
|
75
75
|
|
|
76
76
|
if offset < 0:
|
|
@@ -98,7 +98,7 @@ class Buffer(GLObject):
|
|
|
98
98
|
data is actually uploaded to GPU memory.
|
|
99
99
|
Asking explicitly for a copy will prevent this behavior.
|
|
100
100
|
"""
|
|
101
|
-
data = np.array(data, copy=copy)
|
|
101
|
+
data = np.array(data, copy=copy or np_copy_if_needed)
|
|
102
102
|
nbytes = data.nbytes
|
|
103
103
|
|
|
104
104
|
if nbytes != self._nbytes:
|
|
@@ -283,7 +283,7 @@ class DataBuffer(Buffer):
|
|
|
283
283
|
|
|
284
284
|
# Make sure data is an array
|
|
285
285
|
if not isinstance(data, np.ndarray):
|
|
286
|
-
data = np.array(data, dtype=self.dtype
|
|
286
|
+
data = np.array(data, dtype=self.dtype)
|
|
287
287
|
|
|
288
288
|
# Make sure data is big enough
|
|
289
289
|
if data.size < stop - start:
|
|
@@ -192,8 +192,10 @@ quad = np.array([[0, 0, 0], [-1, 0, 0], [-1, -1, 0],
|
|
|
192
192
|
N = quad.shape[0] * 4
|
|
193
193
|
|
|
194
194
|
# buf3 contains coordinates in device coordinates for four quadrants
|
|
195
|
-
buf3 = np.
|
|
196
|
-
|
|
195
|
+
buf3 = np.vstack([
|
|
196
|
+
quad + (0, 0, 0), quad + (0, 1, 0),
|
|
197
|
+
quad + (1, 1, 0), quad + (1, 0, 0),
|
|
198
|
+
]).astype(np.float32)
|
|
197
199
|
|
|
198
200
|
# buf2 is texture coords. Note that this is a view on buf2
|
|
199
201
|
buf2 = ((buf3+1.0)*0.5)[:, :2] # not C-contiguous
|
vispy/gloo/glir.py
CHANGED
|
@@ -1045,6 +1045,9 @@ class GlirProgram(GlirObject):
|
|
|
1045
1045
|
'vec2': (2, gl.GL_FLOAT, np.float32),
|
|
1046
1046
|
'vec3': (3, gl.GL_FLOAT, np.float32),
|
|
1047
1047
|
'vec4': (4, gl.GL_FLOAT, np.float32),
|
|
1048
|
+
'ivec2': (2, gl.GL_INT, np.int32),
|
|
1049
|
+
'ivec3': (3, gl.GL_INT, np.int32),
|
|
1050
|
+
'ivec4': (4, gl.GL_INT, np.int32),
|
|
1048
1051
|
'int': (1, gl.GL_INT, np.int32),
|
|
1049
1052
|
'bool': (1, gl.GL_BOOL, np.int32)
|
|
1050
1053
|
}
|
|
@@ -1280,8 +1283,13 @@ class GlirProgram(GlirObject):
|
|
|
1280
1283
|
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, vbo_handle)
|
|
1281
1284
|
gl.glEnableVertexAttribArray(attr_handle)
|
|
1282
1285
|
func(attr_handle, *args)
|
|
1283
|
-
if
|
|
1284
|
-
gl.glVertexAttribDivisor(attr_handle, divisor)
|
|
1286
|
+
if hasattr(gl, "glVertexAttribDivisor"):
|
|
1287
|
+
gl.glVertexAttribDivisor(attr_handle, divisor or 0)
|
|
1288
|
+
elif divisor is not None:
|
|
1289
|
+
logger.warning(
|
|
1290
|
+
'Instanced rendering is not supported by the current'
|
|
1291
|
+
f'backend ("{gl.current_backend.__name__}")'
|
|
1292
|
+
)
|
|
1285
1293
|
else:
|
|
1286
1294
|
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, 0)
|
|
1287
1295
|
gl.glDisableVertexAttribArray(attr_handle)
|
vispy/gloo/texture.py
CHANGED
|
@@ -11,6 +11,7 @@ import warnings
|
|
|
11
11
|
|
|
12
12
|
from .globject import GLObject
|
|
13
13
|
from .util import check_enum
|
|
14
|
+
from ..util import np_copy_if_needed
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def get_dtype_limits(dtype):
|
|
@@ -29,7 +30,7 @@ def convert_dtype_and_clip(data, dtype, copy=False):
|
|
|
29
30
|
new_min, new_max = get_dtype_limits(dtype)
|
|
30
31
|
if new_max >= old_max and new_min <= old_min:
|
|
31
32
|
# no need to clip
|
|
32
|
-
return np.array(data, dtype=dtype, copy=copy)
|
|
33
|
+
return np.array(data, dtype=dtype, copy=copy or np_copy_if_needed)
|
|
33
34
|
else:
|
|
34
35
|
# to reduce copying, we clip into a pre-generated array of the right dtype
|
|
35
36
|
new_data = np.empty_like(data, dtype=dtype)
|
|
@@ -158,7 +159,7 @@ class BaseTexture(GLObject):
|
|
|
158
159
|
if data is not None:
|
|
159
160
|
if shape is not None:
|
|
160
161
|
raise ValueError('Texture needs data or shape, not both.')
|
|
161
|
-
data = np.array(data
|
|
162
|
+
data = np.array(data)
|
|
162
163
|
# So we can test the combination
|
|
163
164
|
self._resize(data.shape, format, internalformat)
|
|
164
165
|
self._set_data(data)
|
|
@@ -427,7 +428,7 @@ class BaseTexture(GLObject):
|
|
|
427
428
|
|
|
428
429
|
# Make sure data is an array
|
|
429
430
|
if not isinstance(data, np.ndarray):
|
|
430
|
-
data = np.array(data
|
|
431
|
+
data = np.array(data)
|
|
431
432
|
# Make sure data is big enough
|
|
432
433
|
if data.shape != shape:
|
|
433
434
|
data = np.resize(data, shape)
|
vispy/plot/plotwidget.py
CHANGED
|
@@ -267,7 +267,7 @@ class PlotWidget(scene.Widget):
|
|
|
267
267
|
if meshdata is not None:
|
|
268
268
|
if not all(x is None for x in (vertices, faces, fname)):
|
|
269
269
|
raise ValueError('vertices, faces, and fname must be None if '
|
|
270
|
-
'
|
|
270
|
+
'meshdata is not None')
|
|
271
271
|
else:
|
|
272
272
|
meshdata = MeshData(vertices, faces, vertex_colors=vertex_colors,
|
|
273
273
|
face_colors=face_colors)
|
vispy/testing/_runners.py
CHANGED
|
@@ -164,7 +164,9 @@ def _flake():
|
|
|
164
164
|
print('Running flake8... ') # if end='', first error gets ugly
|
|
165
165
|
sys.stdout.flush()
|
|
166
166
|
try:
|
|
167
|
-
main
|
|
167
|
+
# flake8 used to exit on failure, but instead `main` now returns an exit code
|
|
168
|
+
# see https://github.com/PyCQA/flake8/pull/1461
|
|
169
|
+
raise SystemExit(main())
|
|
168
170
|
except SystemExit as ex:
|
|
169
171
|
if ex.code in (None, 0):
|
|
170
172
|
pass # do not exit yet, we want to print a success msg
|
vispy/testing/image_tester.py
CHANGED
|
@@ -157,6 +157,27 @@ def assert_image_approved(image, standard_file, message=None, **kwargs):
|
|
|
157
157
|
_save_failed_test(image, std_image, standard_file)
|
|
158
158
|
raise
|
|
159
159
|
|
|
160
|
+
# TODO: check for more properties of image
|
|
161
|
+
def assert_image_reasonable(image):
|
|
162
|
+
"""Check that an image is reasonable.
|
|
163
|
+
|
|
164
|
+
The given image is checked to not be completely black or white.
|
|
165
|
+
|
|
166
|
+
Parameters:
|
|
167
|
+
-----------
|
|
168
|
+
image : (h, w, 4) ndarray or 'screenshot'
|
|
169
|
+
The test result to check
|
|
170
|
+
"""
|
|
171
|
+
if isinstance(image, str) and image == "screenshot":
|
|
172
|
+
image = _screenshot(alpha=True)
|
|
173
|
+
|
|
174
|
+
# check dimensions
|
|
175
|
+
assert image.ndim == 3
|
|
176
|
+
assert image.shape[2] == 4
|
|
177
|
+
|
|
178
|
+
# check white or black
|
|
179
|
+
assert image[...,:3].max() > 0
|
|
180
|
+
assert image[...,:3].min() < 255
|
|
160
181
|
|
|
161
182
|
def assert_image_match(im1, im2, min_corr=0.9, px_threshold=50.,
|
|
162
183
|
px_count=None, max_px_diff=None, avg_px_diff=None,
|
vispy/util/__init__.py
CHANGED
|
@@ -15,3 +15,18 @@ from . import fonts # noqa
|
|
|
15
15
|
from . import transforms # noqa
|
|
16
16
|
from .wrappers import use, run_subprocess # noqa
|
|
17
17
|
from .bunch import SimpleBunch # noqa
|
|
18
|
+
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
# `copy` keyword semantics changed in NumPy 2.0
|
|
24
|
+
# this maintains compatibility with older versions
|
|
25
|
+
# if/when NumPy 2.0 becomes the minimum version, we can remove this
|
|
26
|
+
# we don't worry about early dev versions of NumPy 2.0 (that may or may not have the kwarg) here
|
|
27
|
+
# see:
|
|
28
|
+
# https://numpy.org/devdocs/numpy_2_0_migration_guide.html#adapting-to-changes-in-the-copy-keyword
|
|
29
|
+
# https://github.com/scipy/scipy/pull/20172
|
|
30
|
+
np_copy_if_needed: Optional[bool] = None
|
|
31
|
+
if np.lib.NumpyVersion(np.__version__) < "1.28.0":
|
|
32
|
+
np_copy_if_needed = False
|
vispy/version.py
CHANGED
|
@@ -6,6 +6,7 @@ import warnings
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
from vispy.gloo.texture import Texture2D, Texture3D, convert_dtype_and_clip
|
|
9
|
+
from vispy.util import np_copy_if_needed
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def get_default_clim_from_dtype(dtype):
|
|
@@ -298,7 +299,7 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
298
299
|
|
|
299
300
|
@staticmethod
|
|
300
301
|
def _scale_data_on_cpu(data, clim, copy=True):
|
|
301
|
-
data = np.array(data, dtype=np.float32, copy=copy)
|
|
302
|
+
data = np.array(data, dtype=np.float32, copy=copy or np_copy_if_needed)
|
|
302
303
|
if clim[0] != clim[1]:
|
|
303
304
|
# we always must copy the data if we change it here, otherwise it might change
|
|
304
305
|
# unexpectedly the data held outside of here
|
|
@@ -73,7 +73,7 @@ class ArrayList(object):
|
|
|
73
73
|
if isinstance(data[0], (list, tuple)):
|
|
74
74
|
itemsize = [len(sublist) for sublist in data]
|
|
75
75
|
data = [item for sublist in data for item in sublist]
|
|
76
|
-
self._data = np.array(data
|
|
76
|
+
self._data = np.array(data)
|
|
77
77
|
self._size = self._data.size
|
|
78
78
|
|
|
79
79
|
# Default is one group with all data inside
|
|
@@ -88,7 +88,7 @@ class ArrayList(object):
|
|
|
88
88
|
_itemsize = np.ones(
|
|
89
89
|
self._count, dtype=int) * (self._size // self._count)
|
|
90
90
|
else:
|
|
91
|
-
_itemsize = np.array(itemsize
|
|
91
|
+
_itemsize = np.array(itemsize)
|
|
92
92
|
self._count = len(itemsize)
|
|
93
93
|
if _itemsize.sum() != self._size:
|
|
94
94
|
raise ValueError("Cannot partition data as requested")
|
|
@@ -302,7 +302,7 @@ class ArrayList(object):
|
|
|
302
302
|
itemsize = [len(sublist) for sublist in data]
|
|
303
303
|
data = [item for sublist in data for item in sublist]
|
|
304
304
|
|
|
305
|
-
data = np.array(data
|
|
305
|
+
data = np.array(data).ravel()
|
|
306
306
|
size = data.size
|
|
307
307
|
|
|
308
308
|
# Check item size and get item number
|
|
@@ -313,7 +313,7 @@ class ArrayList(object):
|
|
|
313
313
|
_count = size // itemsize
|
|
314
314
|
_itemsize = np.ones(_count, dtype=int) * (size // _count)
|
|
315
315
|
else:
|
|
316
|
-
_itemsize = np.array(itemsize
|
|
316
|
+
_itemsize = np.array(itemsize)
|
|
317
317
|
_count = len(itemsize)
|
|
318
318
|
if _itemsize.sum() != size:
|
|
319
319
|
raise ValueError("Cannot partition data as requested")
|
vispy/visuals/ellipse.py
CHANGED
|
@@ -27,7 +27,7 @@ class EllipseVisual(PolygonVisual):
|
|
|
27
27
|
The width of the border in pixels
|
|
28
28
|
Line widths > 1px are only
|
|
29
29
|
guaranteed to work when using `border_method='agg'` method.
|
|
30
|
-
radius : float | tuple
|
|
30
|
+
radius : float | tuple | list | numpy.ndarray
|
|
31
31
|
Radius or radii of the ellipse
|
|
32
32
|
Defaults to (0.1, 0.1)
|
|
33
33
|
start_angle : float
|
|
@@ -65,13 +65,12 @@ class EllipseVisual(PolygonVisual):
|
|
|
65
65
|
@staticmethod
|
|
66
66
|
def _generate_vertices(center, radius, start_angle, span_angle,
|
|
67
67
|
num_segments):
|
|
68
|
-
if isinstance(radius, (list, tuple)):
|
|
68
|
+
if isinstance(radius, (list, tuple, np.ndarray)):
|
|
69
69
|
if len(radius) == 2:
|
|
70
70
|
xr, yr = radius
|
|
71
71
|
else:
|
|
72
|
-
raise ValueError("radius must be float or 2 value tuple/list"
|
|
73
|
-
"
|
|
74
|
-
len(radius)))
|
|
72
|
+
raise ValueError("radius must be float or 2 value tuple/list/numpy.ndarray "
|
|
73
|
+
f"(got {type(radius)} of length {len(radius)})")
|
|
75
74
|
else:
|
|
76
75
|
xr = yr = radius
|
|
77
76
|
|
vispy/visuals/image.py
CHANGED
|
@@ -16,6 +16,7 @@ from .transforms import NullTransform
|
|
|
16
16
|
from .visual import Visual
|
|
17
17
|
from ..io import load_spatial_filters
|
|
18
18
|
from ._scalable_textures import CPUScaledTexture2D, GPUScaledTexture2D
|
|
19
|
+
from ..util import np_copy_if_needed
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
_VERTEX_SHADER = """
|
|
@@ -380,7 +381,7 @@ class ImageVisual(Visual):
|
|
|
380
381
|
texture_format : str or None
|
|
381
382
|
|
|
382
383
|
"""
|
|
383
|
-
data = np.array(image, copy=copy)
|
|
384
|
+
data = np.array(image, copy=copy or np_copy_if_needed)
|
|
384
385
|
if np.iscomplexobj(data):
|
|
385
386
|
raise TypeError(
|
|
386
387
|
"Complex data types not supported. Please use 'ComplexImage' instead"
|
vispy/visuals/markers.py
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
5
5
|
# -----------------------------------------------------------------------------
|
|
6
6
|
"""Marker Visual and shader definitions."""
|
|
7
|
-
|
|
8
7
|
import numpy as np
|
|
9
8
|
|
|
10
9
|
from ..color import ColorArray
|
|
@@ -497,9 +496,11 @@ class MarkersVisual(Visual):
|
|
|
497
496
|
The symbol size in screen (or data, if scaling is on) px.
|
|
498
497
|
edge_width : float or array or None
|
|
499
498
|
The width of the symbol outline in screen (or data, if scaling is on) px.
|
|
499
|
+
Defaults to 1.0 if None or not provided and ``edge_width_rel`` is not
|
|
500
|
+
provided.
|
|
500
501
|
edge_width_rel : float or array or None
|
|
501
|
-
The width as a fraction of marker size.
|
|
502
|
-
|
|
502
|
+
The width as a fraction of marker size. Can not be specified along with
|
|
503
|
+
edge_width. A ValueError will be raised if both are provided.
|
|
503
504
|
edge_color : Color | ColorArray
|
|
504
505
|
The color used to draw each symbol outline.
|
|
505
506
|
face_color : Color | ColorArray
|
|
@@ -580,7 +581,7 @@ class MarkersVisual(Visual):
|
|
|
580
581
|
|
|
581
582
|
self.freeze()
|
|
582
583
|
|
|
583
|
-
def set_data(self, pos=None, size=10., edge_width=
|
|
584
|
+
def set_data(self, pos=None, size=10., edge_width=None, edge_width_rel=None,
|
|
584
585
|
edge_color='black', face_color='white',
|
|
585
586
|
symbol='o'):
|
|
586
587
|
"""Set the data used to display this visual.
|
|
@@ -593,9 +594,11 @@ class MarkersVisual(Visual):
|
|
|
593
594
|
The symbol size in screen (or data, if scaling is on) px.
|
|
594
595
|
edge_width : float or array or None
|
|
595
596
|
The width of the symbol outline in screen (or data, if scaling is on) px.
|
|
597
|
+
Defaults to 1.0 if None or not provided and ``edge_width_rel`` is not
|
|
598
|
+
provided.
|
|
596
599
|
edge_width_rel : float or array or None
|
|
597
|
-
The width as a fraction of marker size.
|
|
598
|
-
|
|
600
|
+
The width as a fraction of marker size. Can not be specified along with
|
|
601
|
+
edge_width. A ValueError will be raised if both are provided.
|
|
599
602
|
edge_color : Color | ColorArray
|
|
600
603
|
The color used to draw each symbol outline.
|
|
601
604
|
face_color : Color | ColorArray
|
|
@@ -603,9 +606,11 @@ class MarkersVisual(Visual):
|
|
|
603
606
|
symbol : str or array
|
|
604
607
|
The style of symbol used to draw each marker (see Notes).
|
|
605
608
|
"""
|
|
606
|
-
if
|
|
607
|
-
raise ValueError(
|
|
608
|
-
|
|
609
|
+
if edge_width is not None and edge_width_rel is not None:
|
|
610
|
+
raise ValueError("either edge_width or edge_width_rel "
|
|
611
|
+
"should be provided, not both")
|
|
612
|
+
elif edge_width is None and edge_width_rel is None:
|
|
613
|
+
edge_width = 1.0
|
|
609
614
|
|
|
610
615
|
if edge_width is not None:
|
|
611
616
|
edge_width = np.asarray(edge_width)
|
|
@@ -616,10 +621,6 @@ class MarkersVisual(Visual):
|
|
|
616
621
|
if np.any(edge_width_rel < 0):
|
|
617
622
|
raise ValueError('edge_width_rel cannot be negative')
|
|
618
623
|
|
|
619
|
-
if symbol is not None:
|
|
620
|
-
if not np.all(np.isin(np.asarray(symbol), self.symbols)):
|
|
621
|
-
raise ValueError(f'symbols must one of {self.symbols}')
|
|
622
|
-
|
|
623
624
|
edge_color = ColorArray(edge_color).rgba
|
|
624
625
|
if len(edge_color) == 1:
|
|
625
626
|
edge_color = edge_color[0]
|
|
@@ -648,7 +649,15 @@ class MarkersVisual(Visual):
|
|
|
648
649
|
data['a_position'][:, :pos.shape[1]] = pos
|
|
649
650
|
data['a_size'] = size
|
|
650
651
|
|
|
651
|
-
|
|
652
|
+
if symbol is None:
|
|
653
|
+
data["a_symbol"] = np.array(None)
|
|
654
|
+
else:
|
|
655
|
+
if isinstance(symbol, str):
|
|
656
|
+
symbol = [symbol]
|
|
657
|
+
try:
|
|
658
|
+
data['a_symbol'] = np.array([self._symbol_shader_values[x] for x in symbol])
|
|
659
|
+
except KeyError:
|
|
660
|
+
raise ValueError(f'symbols must one of {self.symbols}')
|
|
652
661
|
|
|
653
662
|
self._data = data
|
|
654
663
|
self._vbo.set_data(data)
|
vispy/visuals/surface_plot.py
CHANGED
|
@@ -70,8 +70,8 @@ class SurfacePlotVisual(MeshVisual):
|
|
|
70
70
|
if z is not None:
|
|
71
71
|
if self._x is not None and z.shape[0] != len(self._x):
|
|
72
72
|
raise TypeError('Z values must have shape (len(x), len(y))')
|
|
73
|
-
if self._y is not None and z.shape[1] !=
|
|
74
|
-
raise TypeError('Z values must have shape (len(x), len(y))')
|
|
73
|
+
if self._y is not None and z.shape[1] != self._y.shape[-1]:
|
|
74
|
+
raise TypeError('Z values must have shape (len(x), len(y)) or (x.shape[0], y.shape[1])')
|
|
75
75
|
self._z = z
|
|
76
76
|
if (self.__vertices is not None and
|
|
77
77
|
self._z.shape != self.__vertices.shape[:2]):
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from vispy.scene.visuals import SurfacePlot
|
|
3
|
+
from vispy.scene import TurntableCamera
|
|
4
|
+
from vispy.color import get_colormap
|
|
5
|
+
from vispy.testing import requires_application, TestingCanvas, run_tests_if_main
|
|
6
|
+
from vispy.testing.image_tester import assert_image_reasonable
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@requires_application()
|
|
13
|
+
@pytest.mark.parametrize('x1dim', [True, False])
|
|
14
|
+
@pytest.mark.parametrize('y1dim', [True, False])
|
|
15
|
+
def test_surface_plot(x1dim:bool, y1dim:bool):
|
|
16
|
+
"""Test SurfacePlot visual"""
|
|
17
|
+
with TestingCanvas(bgcolor='w') as c:
|
|
18
|
+
|
|
19
|
+
# create data
|
|
20
|
+
nx, ny = (100, 150)
|
|
21
|
+
x = np.linspace(-2, 2, nx)
|
|
22
|
+
y = np.linspace(-3, 3, ny)
|
|
23
|
+
xv, yv = np.meshgrid(x, y, indexing="ij")
|
|
24
|
+
z = np.sin(xv**2 + yv**2)
|
|
25
|
+
|
|
26
|
+
view = c.central_widget.add_view()
|
|
27
|
+
view.camera = TurntableCamera(up='z', fov=60)
|
|
28
|
+
|
|
29
|
+
# color vertices
|
|
30
|
+
cnorm = z / abs(np.amax(z))
|
|
31
|
+
colormap = get_colormap("viridis").map(cnorm)
|
|
32
|
+
colormap.reshape(z.shape + (-1,))
|
|
33
|
+
|
|
34
|
+
# 1 or 2 dimensional x and y data
|
|
35
|
+
x_input = x if x1dim else xv
|
|
36
|
+
y_input = y if y1dim else yv
|
|
37
|
+
|
|
38
|
+
# create figure
|
|
39
|
+
surface = SurfacePlot(z=z,
|
|
40
|
+
x=x_input,
|
|
41
|
+
y=y_input,
|
|
42
|
+
shading=None)
|
|
43
|
+
|
|
44
|
+
surface.mesh_data.set_vertex_colors(colormap)
|
|
45
|
+
|
|
46
|
+
# c.draw_visual(surface)
|
|
47
|
+
view.add(surface)
|
|
48
|
+
|
|
49
|
+
assert_image_reasonable(c.render())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
run_tests_if_main()
|
|
Binary file
|
vispy/visuals/text/_sdf_cpu.pyx
CHANGED
vispy/visuals/volume.py
CHANGED
|
@@ -417,8 +417,8 @@ _ATTENUATED_MIP_SNIPPETS = dict(
|
|
|
417
417
|
// Scale and clamp accumulation in `sumval` by contrast limits so that:
|
|
418
418
|
// * attenuation value does not depend on data values
|
|
419
419
|
// * negative values do not amplify instead of attenuate
|
|
420
|
-
sumval = sumval + clamp((val - clim.x) / (clim.y - clim.x), 0.0, 1.0);
|
|
421
|
-
scale = exp(-u_attenuation * (sumval - 1)
|
|
420
|
+
sumval = sumval + u_relative_step_size * clamp((val - clim.x) / (clim.y - clim.x), 0.0, 1.0);
|
|
421
|
+
scale = exp(-u_attenuation * (sumval - 1));
|
|
422
422
|
if( maxval > scale * clim.y ) {
|
|
423
423
|
// stop if no chance of finding a higher maxval
|
|
424
424
|
iter = nsteps;
|
|
@@ -1165,9 +1165,40 @@ class VolumeVisual(Visual):
|
|
|
1165
1165
|
|
|
1166
1166
|
@relative_step_size.setter
|
|
1167
1167
|
def relative_step_size(self, value):
|
|
1168
|
+
"""Set the relative step size used during raycasting.
|
|
1169
|
+
|
|
1170
|
+
Very small values give increased detail when rendering volumes with
|
|
1171
|
+
few voxels, but values that are too small give worse performance
|
|
1172
|
+
(framerate), in extreme cases causing a GPU hang and for the process
|
|
1173
|
+
to be killed by the OS. See discussion at:
|
|
1174
|
+
|
|
1175
|
+
https://github.com/vispy/vispy/pull/2587
|
|
1176
|
+
|
|
1177
|
+
For this reason, this setter issues a warning when the value is
|
|
1178
|
+
smaller than ``side_len / (2 * MAX_CANVAS_SIZE)``, where ``side_len``
|
|
1179
|
+
is the smallest side of the volume and ``MAX_CANVAS_SIZE`` is what
|
|
1180
|
+
we consider to be the largest likely monitor resolution along its
|
|
1181
|
+
longest side: 7680 pixels, equivalent to an 8K monitor.
|
|
1182
|
+
|
|
1183
|
+
This setter also raises a ValueError when the value is 0 or negative.
|
|
1184
|
+
"""
|
|
1168
1185
|
value = float(value)
|
|
1169
|
-
|
|
1170
|
-
|
|
1186
|
+
side_len = np.min(self._vol_shape)
|
|
1187
|
+
MAX_CANVAS_SIZE = 7680
|
|
1188
|
+
minimum_val = side_len / (2 * MAX_CANVAS_SIZE)
|
|
1189
|
+
if value <= 0:
|
|
1190
|
+
raise ValueError('relative_step_size cannot be 0 or negative.')
|
|
1191
|
+
elif value < minimum_val:
|
|
1192
|
+
warnings.warn(
|
|
1193
|
+
f'To display a volume of shape {self._vol_shape} without '
|
|
1194
|
+
f'artifacts, you need a step size no smaller than {side_len} /'
|
|
1195
|
+
f'(2 * {MAX_CANVAS_SIZE}) = {minimum_val:,.3g}. To prevent '
|
|
1196
|
+
'extreme degradation in rendering performance, the provided '
|
|
1197
|
+
f'value of {value} is being clipped to {minimum_val:,.3g}. If '
|
|
1198
|
+
'you believe you need a smaller step size, please raise an '
|
|
1199
|
+
'issue at https://github.com/vispy/vispy/issues.'
|
|
1200
|
+
)
|
|
1201
|
+
value = minimum_val
|
|
1171
1202
|
self._relative_step_size = value
|
|
1172
1203
|
self.shared_program['u_relative_step_size'] = value
|
|
1173
1204
|
|
|
@@ -3,7 +3,7 @@ Vispy licensing terms
|
|
|
3
3
|
|
|
4
4
|
Vispy is licensed under the terms of the (new) BSD license:
|
|
5
5
|
|
|
6
|
-
Copyright (c) 2013-
|
|
6
|
+
Copyright (c) 2013-2024, Vispy Development Team. All rights reserved.
|
|
7
7
|
|
|
8
8
|
Redistribution and use in source and binary forms, with or without
|
|
9
9
|
modification, are permitted provided that the following conditions are met:
|