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/testing/_testing.py
CHANGED
|
@@ -14,7 +14,7 @@ import gc
|
|
|
14
14
|
import pytest
|
|
15
15
|
import functools
|
|
16
16
|
|
|
17
|
-
from
|
|
17
|
+
from packaging.version import Version
|
|
18
18
|
|
|
19
19
|
from ..util.check_environment import has_backend
|
|
20
20
|
|
|
@@ -251,7 +251,7 @@ def has_ipython(version='3.0'):
|
|
|
251
251
|
except Exception:
|
|
252
252
|
return False, "IPython library not found"
|
|
253
253
|
else:
|
|
254
|
-
if
|
|
254
|
+
if Version(IPython.__version__) >= Version(version):
|
|
255
255
|
return True, "IPython present"
|
|
256
256
|
else:
|
|
257
257
|
message = (
|
|
@@ -284,8 +284,7 @@ def _has_scipy(min_version):
|
|
|
284
284
|
try:
|
|
285
285
|
assert isinstance(min_version, str)
|
|
286
286
|
import scipy # noqa, analysis:ignore
|
|
287
|
-
|
|
288
|
-
this_version = LooseVersion(scipy.__version__)
|
|
287
|
+
this_version = Version(scipy.__version__)
|
|
289
288
|
if this_version < min_version:
|
|
290
289
|
return False
|
|
291
290
|
except Exception:
|
vispy/util/check_environment.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# Copyright (c) Vispy Development Team. All Rights Reserved.
|
|
3
3
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
4
4
|
import os
|
|
5
|
-
from
|
|
5
|
+
from packaging.version import Version
|
|
6
6
|
|
|
7
7
|
from vispy.util import use_log_level
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ def has_matplotlib(version='1.2'):
|
|
|
14
14
|
except Exception:
|
|
15
15
|
has_mpl = False
|
|
16
16
|
else:
|
|
17
|
-
if
|
|
17
|
+
if Version(matplotlib.__version__) >= Version(version):
|
|
18
18
|
has_mpl = True
|
|
19
19
|
else:
|
|
20
20
|
has_mpl = False
|
|
@@ -27,8 +27,8 @@ def has_skimage(version='0.11'):
|
|
|
27
27
|
import skimage
|
|
28
28
|
except ImportError:
|
|
29
29
|
return False
|
|
30
|
-
sk_version =
|
|
31
|
-
return sk_version >=
|
|
30
|
+
sk_version = Version(skimage.__version__)
|
|
31
|
+
return sk_version >= Version(version)
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
def has_backend(backend, has=(), capable=(), out=()):
|
vispy/util/gallery_scraper.py
CHANGED
|
@@ -60,7 +60,7 @@ class VisPyGalleryScraper:
|
|
|
60
60
|
example_fn = block_vars["src_file"]
|
|
61
61
|
frame_num_list = self._get_frame_list_from_source(example_fn)
|
|
62
62
|
image_path_iterator = block_vars['image_path_iterator']
|
|
63
|
-
canvas_or_widget =
|
|
63
|
+
canvas_or_widget = get_canvaslike_from_globals(block_vars["example_globals"])
|
|
64
64
|
if not frame_num_list:
|
|
65
65
|
image_paths = []
|
|
66
66
|
elif isinstance(frame_num_list[0], str):
|
|
@@ -89,6 +89,7 @@ class VisPyGalleryScraper:
|
|
|
89
89
|
frame_grabber.save_animation(image_path)
|
|
90
90
|
else:
|
|
91
91
|
frame_grabber.save_frame(image_path)
|
|
92
|
+
frame_grabber.cleanup()
|
|
92
93
|
if 'images' in gallery_conf['compress_images']:
|
|
93
94
|
optipng(image_path, gallery_conf['compress_images_args'])
|
|
94
95
|
return [image_path]
|
|
@@ -133,36 +134,38 @@ class VisPyGalleryScraper:
|
|
|
133
134
|
frame_paths.append(frame_fn)
|
|
134
135
|
return frame_paths
|
|
135
136
|
|
|
136
|
-
def _get_canvaslike_from_globals(self, globals_dict):
|
|
137
|
-
qt_widget = self._get_qt_top_parent(globals_dict)
|
|
138
|
-
if qt_widget is not None:
|
|
139
|
-
return qt_widget
|
|
140
|
-
|
|
141
|
-
# Get canvas
|
|
142
|
-
if "canvas" in globals_dict:
|
|
143
|
-
return globals_dict["canvas"]
|
|
144
|
-
if "Canvas" in globals_dict:
|
|
145
|
-
return globals_dict["Canvas"]()
|
|
146
|
-
if "fig" in globals_dict:
|
|
147
|
-
return globals_dict["fig"]
|
|
148
|
-
return None
|
|
149
137
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
138
|
+
def get_canvaslike_from_globals(globals_dict):
|
|
139
|
+
qt_widget = _get_qt_top_parent(globals_dict)
|
|
140
|
+
if qt_widget is not None:
|
|
141
|
+
return qt_widget
|
|
142
|
+
|
|
143
|
+
# Get canvas
|
|
144
|
+
if "canvas" in globals_dict:
|
|
145
|
+
return globals_dict["canvas"]
|
|
146
|
+
if "Canvas" in globals_dict:
|
|
147
|
+
return globals_dict["Canvas"]()
|
|
148
|
+
if "fig" in globals_dict:
|
|
149
|
+
return globals_dict["fig"]
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _get_qt_top_parent(globals_dict):
|
|
154
|
+
if "QWidget" not in globals_dict and "QMainWindow" not in globals_dict and "QtWidgets" not in globals_dict:
|
|
164
155
|
return None
|
|
165
156
|
|
|
157
|
+
qtwidgets = globals_dict.get("QtWidgets")
|
|
158
|
+
qmainwindow = globals_dict.get("QMainWindow", getattr(qtwidgets, "QMainWindow", None))
|
|
159
|
+
qwidget = globals_dict.get("QWidget", getattr(qtwidgets, "QWidget", qmainwindow))
|
|
160
|
+
all_qt_widgets = [widget for widget in globals_dict.values()
|
|
161
|
+
if isinstance(widget, qwidget) and widget is not None]
|
|
162
|
+
all_qt_mains = [widget for widget in all_qt_widgets if isinstance(widget, qmainwindow)]
|
|
163
|
+
if all_qt_mains:
|
|
164
|
+
return all_qt_mains[0]
|
|
165
|
+
if all_qt_widgets:
|
|
166
|
+
return all_qt_widgets[0]
|
|
167
|
+
return None
|
|
168
|
+
|
|
166
169
|
|
|
167
170
|
class FrameGrabber:
|
|
168
171
|
"""Helper to grab a series of screenshots from the current Canvas-like object."""
|
|
@@ -174,6 +177,13 @@ class FrameGrabber:
|
|
|
174
177
|
self._collected_images = []
|
|
175
178
|
self._frames_to_grab = frame_grab_list[:] # copy so original list is preserved
|
|
176
179
|
|
|
180
|
+
def cleanup(self):
|
|
181
|
+
from PyQt5.QtWidgets import QApplication
|
|
182
|
+
for child_widget in QApplication.allWidgets():
|
|
183
|
+
if hasattr(child_widget, 'close'):
|
|
184
|
+
child_widget.close()
|
|
185
|
+
QApplication.processEvents()
|
|
186
|
+
|
|
177
187
|
def on_draw(self, _):
|
|
178
188
|
if self._done:
|
|
179
189
|
return # Grab only once
|
|
@@ -204,13 +214,19 @@ class FrameGrabber:
|
|
|
204
214
|
|
|
205
215
|
def _grab_qt_screenshot(self):
|
|
206
216
|
from PyQt5.QtWidgets import QApplication
|
|
217
|
+
from PyQt5.QtCore import QTimer
|
|
207
218
|
self._canvas.show()
|
|
208
219
|
# Qt is going to grab from the screen so we need the window on top
|
|
209
220
|
self._canvas.raise_()
|
|
210
221
|
# We need to give the GUI event loop and OS time to draw everything
|
|
222
|
+
time.sleep(1.5)
|
|
211
223
|
QApplication.processEvents()
|
|
224
|
+
QTimer.singleShot(1000, self._grab_widget_screenshot)
|
|
212
225
|
time.sleep(1.5)
|
|
213
226
|
QApplication.processEvents()
|
|
227
|
+
|
|
228
|
+
def _grab_widget_screenshot(self):
|
|
229
|
+
from PyQt5.QtWidgets import QApplication
|
|
214
230
|
screen = QApplication.screenAt(self._canvas.pos())
|
|
215
231
|
screenshot = screen.grabWindow(int(self._canvas.windowHandle().winId()))
|
|
216
232
|
arr = self._qpixmap_to_ndarray(screenshot)
|
|
@@ -218,14 +234,16 @@ class FrameGrabber:
|
|
|
218
234
|
|
|
219
235
|
@staticmethod
|
|
220
236
|
def _qpixmap_to_ndarray(pixmap):
|
|
237
|
+
from PyQt5 import QtGui
|
|
221
238
|
import numpy as np
|
|
222
|
-
im = pixmap.toImage()
|
|
239
|
+
im = pixmap.toImage().convertToFormat(QtGui.QImage.Format.Format_RGB32)
|
|
223
240
|
size = pixmap.size()
|
|
224
241
|
width = size.width()
|
|
225
242
|
height = size.height()
|
|
226
|
-
im_bits = im.
|
|
227
|
-
im_bits.setsize(height * width * 4)
|
|
228
|
-
|
|
243
|
+
im_bits = im.constBits()
|
|
244
|
+
im_bits.setsize(height * width * 4)
|
|
245
|
+
# Convert 0xffRRGGBB buffer -> (B, G, R, 0xff) -> (R, G, B)
|
|
246
|
+
return np.array(im_bits).reshape((height, width, 4))[:, :, 2::-1]
|
|
229
247
|
|
|
230
248
|
def _grab_vispy_screenshots(self):
|
|
231
249
|
os.environ['VISPY_IGNORE_OLD_VERSION'] = 'true'
|
vispy/util/transforms.py
CHANGED
vispy/util/wrappers.py
CHANGED
|
@@ -9,7 +9,7 @@ and vispy.gloo.gl can be used independently, they are not complely
|
|
|
9
9
|
independent for some configureation. E.g. when using real ES 2.0,
|
|
10
10
|
the app backend should use EGL and not a desktop OpenGL context. Also,
|
|
11
11
|
we probably want it to be easy to configure vispy to use the ipython
|
|
12
|
-
notebook backend, which requires
|
|
12
|
+
notebook backend, which requires specific config of both app and gl.
|
|
13
13
|
|
|
14
14
|
This module does not have to be aware of the available app and gl
|
|
15
15
|
backends, but it should be(come) aware of (in)compatibilities between
|
vispy/version.py
CHANGED
vispy/visuals/__init__.py
CHANGED
|
@@ -17,9 +17,11 @@ from .cube import CubeVisual # noqa
|
|
|
17
17
|
from .ellipse import EllipseVisual # noqa
|
|
18
18
|
from .gridlines import GridLinesVisual # noqa
|
|
19
19
|
from .image import ImageVisual # noqa
|
|
20
|
+
from .image_complex import ComplexImageVisual # noqa
|
|
20
21
|
from .gridmesh import GridMeshVisual # noqa
|
|
21
22
|
from .histogram import HistogramVisual # noqa
|
|
22
23
|
from .infinite_line import InfiniteLineVisual # noqa
|
|
24
|
+
from .instanced_mesh import InstancedMeshVisual # noqa
|
|
23
25
|
from .isocurve import IsocurveVisual # noqa
|
|
24
26
|
from .isoline import IsolineVisual # noqa
|
|
25
27
|
from .isosurface import IsosurfaceVisual # noqa
|
|
@@ -5,8 +5,7 @@ import warnings
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
|
-
from vispy.gloo import Texture2D, Texture3D
|
|
9
|
-
from vispy.gloo.texture import should_cast_to_f32
|
|
8
|
+
from vispy.gloo.texture import Texture2D, Texture3D, convert_dtype_and_clip
|
|
10
9
|
|
|
11
10
|
|
|
12
11
|
def get_default_clim_from_dtype(dtype):
|
|
@@ -210,7 +209,11 @@ class _ScaledTextureMixin:
|
|
|
210
209
|
|
|
211
210
|
def scale_and_set_data(self, data, offset=None, copy=False):
|
|
212
211
|
"""Upload new data to the GPU."""
|
|
213
|
-
|
|
212
|
+
# we need to call super here or we get infinite recursion
|
|
213
|
+
return super().set_data(data, offset=offset, copy=copy)
|
|
214
|
+
|
|
215
|
+
def set_data(self, data, offset=None, copy=False):
|
|
216
|
+
self.scale_and_set_data(data, offset=offset, copy=copy)
|
|
214
217
|
|
|
215
218
|
|
|
216
219
|
class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
@@ -295,29 +298,31 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
295
298
|
|
|
296
299
|
@staticmethod
|
|
297
300
|
def _scale_data_on_cpu(data, clim, copy=True):
|
|
298
|
-
|
|
299
|
-
should_cast_to_f32(data.dtype)
|
|
300
|
-
data = np.array(data, dtype=np.float32, copy=copy)
|
|
301
|
-
elif not copy and not np.issubdtype(data.dtype, np.floating):
|
|
302
|
-
raise ValueError("Data must be of floating type for no copying to occur.")
|
|
303
|
-
|
|
301
|
+
data = np.array(data, dtype=np.float32, copy=copy)
|
|
304
302
|
if clim[0] != clim[1]:
|
|
303
|
+
# we always must copy the data if we change it here, otherwise it might change
|
|
304
|
+
# unexpectedly the data held outside of here
|
|
305
|
+
if not copy:
|
|
306
|
+
data = data.copy()
|
|
305
307
|
data -= clim[0]
|
|
306
|
-
data *= 1
|
|
307
|
-
if should_cast_to_f32(data.dtype):
|
|
308
|
-
data = data.astype(np.float32)
|
|
308
|
+
data *= 1 / (clim[1] - clim[0])
|
|
309
309
|
return data
|
|
310
310
|
|
|
311
311
|
def scale_and_set_data(self, data, offset=None, copy=True):
|
|
312
312
|
"""Upload new data to the GPU, scaling if necessary."""
|
|
313
|
-
self._data_dtype
|
|
313
|
+
if self._data_dtype is None:
|
|
314
|
+
data.dtype == self._data_dtype
|
|
315
|
+
|
|
316
|
+
# ensure dtype is the same as it was before, or funny things happen
|
|
317
|
+
# no copy is performed unless asked for or necessary
|
|
318
|
+
data = convert_dtype_and_clip(data, self._data_dtype, copy=copy)
|
|
314
319
|
|
|
315
320
|
clim = self._clim
|
|
316
321
|
is_auto = isinstance(clim, str) and clim == 'auto'
|
|
317
322
|
if data.ndim == self._ndim or data.shape[self._ndim] == 1:
|
|
318
323
|
if is_auto:
|
|
319
324
|
clim = get_default_clim_from_data(data)
|
|
320
|
-
data = self._scale_data_on_cpu(data, clim, copy=
|
|
325
|
+
data = self._scale_data_on_cpu(data, clim, copy=False)
|
|
321
326
|
data_limits = clim
|
|
322
327
|
else:
|
|
323
328
|
data_limits = get_default_clim_from_dtype(data.dtype)
|
|
@@ -326,7 +331,7 @@ class CPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
326
331
|
|
|
327
332
|
self._clim = float(clim[0]), float(clim[1])
|
|
328
333
|
self._data_limits = data_limits
|
|
329
|
-
return super().scale_and_set_data(data, offset=offset, copy=
|
|
334
|
+
return super().scale_and_set_data(data, offset=offset, copy=False)
|
|
330
335
|
|
|
331
336
|
|
|
332
337
|
class GPUScaledTextureMixin(_ScaledTextureMixin):
|
|
@@ -346,7 +351,6 @@ class GPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
346
351
|
also give the texture permission to change formats in the future if
|
|
347
352
|
new data is provided with a different data type.
|
|
348
353
|
|
|
349
|
-
|
|
350
354
|
This class should only be used internally. For similar features where
|
|
351
355
|
scaling occurs on the CPU see
|
|
352
356
|
:class:`vispy.visuals._scalable_textures.CPUScaledTextureMixin`.
|
|
@@ -390,7 +394,6 @@ class GPUScaledTextureMixin(_ScaledTextureMixin):
|
|
|
390
394
|
texture_format = np.dtype(texture_format).type
|
|
391
395
|
if texture_format not in self._texture_dtype_format:
|
|
392
396
|
raise ValueError("Can't determine internal texture format for '{}'".format(texture_format))
|
|
393
|
-
should_cast_to_f32(texture_format)
|
|
394
397
|
texture_format = self._texture_dtype_format[texture_format]
|
|
395
398
|
# adjust internalformat for format of data (RGBA vs L)
|
|
396
399
|
texture_format = texture_format.replace('r', 'rgba'[:num_channels])
|
|
@@ -52,7 +52,7 @@ class ArrayList(object):
|
|
|
52
52
|
an error is raised.
|
|
53
53
|
|
|
54
54
|
If `itemsize` is 1-D array, the array will be divided into
|
|
55
|
-
elements whose
|
|
55
|
+
elements whose successive sizes will be picked from itemsize.
|
|
56
56
|
If the sum of itemsize values is different from array size,
|
|
57
57
|
an error is raised.
|
|
58
58
|
|
|
@@ -291,7 +291,7 @@ class ArrayList(object):
|
|
|
291
291
|
an error is raised.
|
|
292
292
|
|
|
293
293
|
If `itemsize` is 1-D array, the array will be divided into
|
|
294
|
-
elements whose
|
|
294
|
+
elements whose successive sizes will be picked from itemsize.
|
|
295
295
|
If the sum of itemsize values is different from array size,
|
|
296
296
|
an error is raised.
|
|
297
297
|
"""
|
|
@@ -394,7 +394,7 @@ class ArrayList(object):
|
|
|
394
394
|
an error is raised.
|
|
395
395
|
|
|
396
396
|
If `itemsize` is 1-D array, the array will be divided into
|
|
397
|
-
elements whose
|
|
397
|
+
elements whose successive sizes will be picked from itemsize.
|
|
398
398
|
If the sum of itemsize values is different from array size,
|
|
399
399
|
an error is raised.
|
|
400
400
|
"""
|
|
@@ -237,7 +237,7 @@ class BaseCollection(object):
|
|
|
237
237
|
an error is raised.
|
|
238
238
|
|
|
239
239
|
If `itemsize` is 1-D array, the array will be divided into
|
|
240
|
-
elements whose
|
|
240
|
+
elements whose successive sizes will be picked from itemsize.
|
|
241
241
|
If the sum of itemsize values is different from array size,
|
|
242
242
|
an error is raised.
|
|
243
243
|
"""
|
vispy/visuals/ellipse.py
CHANGED
|
@@ -79,7 +79,7 @@ class EllipseVisual(PolygonVisual):
|
|
|
79
79
|
|
|
80
80
|
vertices = np.empty([num_segments + 2, 2], dtype=np.float32)
|
|
81
81
|
|
|
82
|
-
# split the total angle into num_segments
|
|
82
|
+
# split the total angle into num_segments instances
|
|
83
83
|
theta = np.linspace(start_angle,
|
|
84
84
|
start_angle + np.deg2rad(span_angle),
|
|
85
85
|
num_segments + 1)
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
# Copyright (c) Vispy Development Team. All Rights Reserved.
|
|
3
3
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
4
4
|
|
|
5
|
-
from .base_filter import Filter # noqa
|
|
5
|
+
from .base_filter import Filter, PrimitivePickingFilter # noqa
|
|
6
6
|
from .clipper import Clipper # noqa
|
|
7
7
|
from .color import Alpha, ColorFilter, IsolineFilter, ZColormapFilter # noqa
|
|
8
8
|
from .picking import PickingFilter # noqa
|
|
9
|
-
from .
|
|
9
|
+
from .markers import MarkerPickingFilter # noqa
|
|
10
|
+
from .mesh import TextureFilter, ShadingFilter, InstancedShadingFilter, WireframeFilter, FacePickingFilter # noqa
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
# Copyright (c) Vispy Development Team. All Rights Reserved.
|
|
3
3
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
4
4
|
|
|
5
|
+
from abc import ABCMeta, abstractmethod
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from vispy.gloo import VertexBuffer
|
|
5
10
|
from ..shaders import Function
|
|
6
11
|
|
|
7
12
|
|
|
@@ -120,3 +125,118 @@ class Filter(BaseFilter):
|
|
|
120
125
|
|
|
121
126
|
self._attached = False
|
|
122
127
|
self._visual = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class PrimitivePickingFilter(Filter, metaclass=ABCMeta):
|
|
131
|
+
"""Abstract base class for Visual-specific filters to implement a
|
|
132
|
+
primitive-picking mode.
|
|
133
|
+
|
|
134
|
+
Subclasses must (and usually only need to) implement
|
|
135
|
+
:py:meth:`_get_picking_ids`.
|
|
136
|
+
"""
|
|
137
|
+
|
|
138
|
+
def __init__(self, fpos=9, *, discard_transparent=False):
|
|
139
|
+
# fpos is set to 9 by default to put it near the end, but before the
|
|
140
|
+
# default PickingFilter
|
|
141
|
+
vfunc = Function("""\
|
|
142
|
+
varying vec4 v_marker_picking_color;
|
|
143
|
+
void prepare_marker_picking() {
|
|
144
|
+
v_marker_picking_color = $ids;
|
|
145
|
+
}
|
|
146
|
+
""")
|
|
147
|
+
ffunc = Function("""\
|
|
148
|
+
varying vec4 v_marker_picking_color;
|
|
149
|
+
void marker_picking_filter() {
|
|
150
|
+
if ( $enabled != 1 ) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if ( $discard_transparent == 1 && gl_FragColor.a == 0.0 ) {
|
|
154
|
+
discard;
|
|
155
|
+
}
|
|
156
|
+
gl_FragColor = v_marker_picking_color;
|
|
157
|
+
}
|
|
158
|
+
""")
|
|
159
|
+
|
|
160
|
+
self._id_colors = VertexBuffer(np.zeros((0, 4), dtype=np.float32))
|
|
161
|
+
vfunc['ids'] = self._id_colors
|
|
162
|
+
self._n_primitives = 0
|
|
163
|
+
super().__init__(vcode=vfunc, fcode=ffunc, fpos=fpos)
|
|
164
|
+
self.enabled = False
|
|
165
|
+
self.discard_transparent = discard_transparent
|
|
166
|
+
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def _get_picking_ids(self):
|
|
169
|
+
"""Return a 1D array of picking IDs for the vertices in the visual.
|
|
170
|
+
|
|
171
|
+
Generally, this method should be implemented to:
|
|
172
|
+
1. Calculate the number of primitives in the visual (may be
|
|
173
|
+
persisted in `self._n_primitives`).
|
|
174
|
+
2. Calculate a range of picking ids for each primitive in the
|
|
175
|
+
visual. IDs should start from 1, reserving 0 for the background. If
|
|
176
|
+
primitives comprise multiple vertices (triangles), ids may need to
|
|
177
|
+
be repeated.
|
|
178
|
+
|
|
179
|
+
The return value should be an array of uint32 with shape
|
|
180
|
+
(num_vertices,).
|
|
181
|
+
|
|
182
|
+
If no change to the picking IDs is needed (for example, the number of
|
|
183
|
+
primitives has not changed), this method should return `None`.
|
|
184
|
+
"""
|
|
185
|
+
raise NotImplementedError(self)
|
|
186
|
+
|
|
187
|
+
def _update_id_colors(self):
|
|
188
|
+
"""Calculate the colors encoding the picking IDs for the visual.
|
|
189
|
+
|
|
190
|
+
For performance, this method will not update the id colors VertexBuffer
|
|
191
|
+
if :py:meth:`_get_picking_ids` returns `None`.
|
|
192
|
+
"""
|
|
193
|
+
# this should remain untouched
|
|
194
|
+
ids = self._get_picking_ids()
|
|
195
|
+
if ids is not None:
|
|
196
|
+
id_colors = self._pack_ids_into_rgba(ids)
|
|
197
|
+
self._id_colors.set_data(id_colors)
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def _pack_ids_into_rgba(ids):
|
|
201
|
+
"""Pack an array of uint32 primitive ids into float32 RGBA colors."""
|
|
202
|
+
if ids.dtype != np.uint32:
|
|
203
|
+
raise ValueError(f"ids must be uint32, got {ids.dtype}")
|
|
204
|
+
|
|
205
|
+
return np.divide(
|
|
206
|
+
ids.view(np.uint8).reshape(-1, 4),
|
|
207
|
+
255,
|
|
208
|
+
dtype=np.float32
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
def _on_data_updated(self, event=None):
|
|
212
|
+
if not self.attached:
|
|
213
|
+
return
|
|
214
|
+
self._update_id_colors()
|
|
215
|
+
|
|
216
|
+
@property
|
|
217
|
+
def enabled(self):
|
|
218
|
+
return self._enabled
|
|
219
|
+
|
|
220
|
+
@enabled.setter
|
|
221
|
+
def enabled(self, e):
|
|
222
|
+
self._enabled = bool(e)
|
|
223
|
+
self.fshader['enabled'] = int(self._enabled)
|
|
224
|
+
self._on_data_updated()
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def discard_transparent(self):
|
|
228
|
+
return self._discard_transparent
|
|
229
|
+
|
|
230
|
+
@discard_transparent.setter
|
|
231
|
+
def discard_transparent(self, d):
|
|
232
|
+
self._discard_transparent = bool(d)
|
|
233
|
+
self.fshader['discard_transparent'] = int(self._discard_transparent)
|
|
234
|
+
|
|
235
|
+
def _attach(self, visual):
|
|
236
|
+
super()._attach(visual)
|
|
237
|
+
visual.events.data_updated.connect(self._on_data_updated)
|
|
238
|
+
self._on_data_updated()
|
|
239
|
+
|
|
240
|
+
def _detach(self, visual):
|
|
241
|
+
visual.events.data_updated.disconnect(self._on_data_updated)
|
|
242
|
+
super()._detach(visual)
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
# Copyright (c) Vispy Development Team. All Rights Reserved.
|
|
3
3
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
4
4
|
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
5
9
|
from functools import lru_cache
|
|
6
10
|
|
|
7
11
|
import numpy as np
|
|
@@ -37,7 +41,7 @@ class PlanesClipper(Filter):
|
|
|
37
41
|
}
|
|
38
42
|
"""
|
|
39
43
|
|
|
40
|
-
def __init__(self, clipping_planes=None, coord_system='scene'):
|
|
44
|
+
def __init__(self, clipping_planes: Optional[np.ndarray] = None, coord_system: str = 'scene'):
|
|
41
45
|
tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']
|
|
42
46
|
if coord_system not in tr:
|
|
43
47
|
raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')
|
|
@@ -48,6 +52,11 @@ class PlanesClipper(Filter):
|
|
|
48
52
|
fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,
|
|
49
53
|
)
|
|
50
54
|
|
|
55
|
+
# initialize clipping planes
|
|
56
|
+
self._clipping_planes = np.empty((0, 2, 3), dtype=np.float32)
|
|
57
|
+
self._clipping_planes_func = Function(self._build_clipping_planes_glsl(0))
|
|
58
|
+
self.fshader['clip_with_planes'] = self._clipping_planes_func
|
|
59
|
+
|
|
51
60
|
v_position = Varying('v_position', 'vec4')
|
|
52
61
|
self.vshader['v_position'] = v_position
|
|
53
62
|
self.fshader['v_position'] = v_position
|
|
@@ -55,7 +64,7 @@ class PlanesClipper(Filter):
|
|
|
55
64
|
self.clipping_planes = clipping_planes
|
|
56
65
|
|
|
57
66
|
@property
|
|
58
|
-
def coord_system(self):
|
|
67
|
+
def coord_system(self) -> str:
|
|
59
68
|
"""
|
|
60
69
|
Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
|
|
61
70
|
"""
|
|
@@ -68,7 +77,7 @@ class PlanesClipper(Filter):
|
|
|
68
77
|
|
|
69
78
|
@staticmethod
|
|
70
79
|
@lru_cache(maxsize=10)
|
|
71
|
-
def
|
|
80
|
+
def _build_clipping_planes_glsl(n_planes: int) -> str:
|
|
72
81
|
"""Build the code snippet used to clip the volume based on self.clipping_planes."""
|
|
73
82
|
func_template = '''
|
|
74
83
|
float clip_planes(vec3 loc) {{
|
|
@@ -87,24 +96,27 @@ class PlanesClipper(Filter):
|
|
|
87
96
|
for idx in range(n_planes):
|
|
88
97
|
all_clips.append(clip_template.format(idx=idx))
|
|
89
98
|
formatted_code = func_template.format(clips=''.join(all_clips))
|
|
90
|
-
return
|
|
99
|
+
return formatted_code
|
|
91
100
|
|
|
92
101
|
@property
|
|
93
|
-
def clipping_planes(self):
|
|
102
|
+
def clipping_planes(self) -> np.ndarray:
|
|
94
103
|
"""Get the set of planes used to clip the mesh.
|
|
95
104
|
Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)
|
|
96
105
|
"""
|
|
97
106
|
return self._clipping_planes
|
|
98
107
|
|
|
99
108
|
@clipping_planes.setter
|
|
100
|
-
def clipping_planes(self, value):
|
|
109
|
+
def clipping_planes(self, value: Optional[np.ndarray]):
|
|
101
110
|
if value is None:
|
|
102
|
-
value = np.empty(
|
|
103
|
-
|
|
111
|
+
value = np.empty((0, 2, 3), dtype=np.float32)
|
|
112
|
+
|
|
113
|
+
# only recreate function if amount of clipping planes changes
|
|
114
|
+
if len(value) != len(self._clipping_planes):
|
|
115
|
+
self._clipping_planes_func = Function(self._build_clipping_planes_glsl(len(value)))
|
|
116
|
+
self.fshader['clip_with_planes'] = self._clipping_planes_func
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
self.fshader['clip_with_planes'] = clip_func
|
|
118
|
+
self._clipping_planes = value
|
|
107
119
|
|
|
108
120
|
for idx, plane in enumerate(value):
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
self._clipping_planes_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])
|
|
122
|
+
self._clipping_planes_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from vispy.visuals.filters import PrimitivePickingFilter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MarkerPickingFilter(PrimitivePickingFilter):
|
|
7
|
+
"""Filter used to color markers by a picking ID.
|
|
8
|
+
|
|
9
|
+
Note that the ID color uses the alpha channel, so this may not be used
|
|
10
|
+
with blending enabled.
|
|
11
|
+
|
|
12
|
+
Examples
|
|
13
|
+
--------
|
|
14
|
+
:ref:`sphx_glr_gallery_scene_marker_picking.py`
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def _get_picking_ids(self):
|
|
18
|
+
if self._visual._data is None:
|
|
19
|
+
n_markers = 0
|
|
20
|
+
else:
|
|
21
|
+
n_markers = len(self._visual._data['a_position'])
|
|
22
|
+
|
|
23
|
+
# we only care about the number of markers changing
|
|
24
|
+
if self._n_primitives == n_markers:
|
|
25
|
+
return
|
|
26
|
+
self._n_primitives = n_markers
|
|
27
|
+
|
|
28
|
+
return np.arange(1, n_markers + 1, dtype=np.uint32)
|