vispy 0.14.3__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.15.2__cp312-cp312-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 +46 -11
- vispy/color/colormap.py +106 -27
- vispy/color/tests/test_color.py +27 -1
- vispy/ext/cocoapy.py +1 -21
- vispy/geometry/meshdata.py +3 -1
- vispy/geometry/tests/test_triangulation.py +88 -0
- vispy/geometry/triangulation.py +11 -13
- vispy/gloo/program.py +2 -2
- vispy/scene/cameras/arcball.py +1 -2
- vispy/scene/cameras/base_camera.py +63 -50
- vispy/scene/cameras/panzoom.py +4 -1
- vispy/scene/cameras/perspective.py +6 -1
- vispy/scene/cameras/turntable.py +11 -1
- vispy/scene/widgets/grid.py +191 -71
- vispy/scene/widgets/widget.py +11 -0
- vispy/version.py +9 -4
- vispy/visuals/_scalable_textures.py +5 -4
- vispy/visuals/filters/mesh.py +6 -1
- vispy/visuals/gridlines.py +61 -5
- vispy/visuals/image.py +19 -7
- vispy/visuals/surface_plot.py +1 -1
- vispy/visuals/tests/test_gridlines.py +30 -0
- vispy/visuals/tests/test_image.py +17 -15
- vispy/visuals/tests/test_scalable_textures.py +16 -0
- vispy/visuals/tests/test_surface_plot.py +8 -3
- vispy/visuals/text/_sdf_cpu.cpython-312-aarch64-linux-gnu.so +0 -0
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/METADATA +50 -27
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/RECORD +455 -454
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/WHEEL +1 -1
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info/licenses}/LICENSE.txt +1 -1
- {vispy-0.14.3.dist-info → vispy-0.15.2.dist-info}/top_level.txt +0 -0
vispy/app/backends/_qt.py
CHANGED
|
@@ -493,41 +493,57 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
493
493
|
def mousePressEvent(self, ev):
|
|
494
494
|
if self._vispy_canvas is None:
|
|
495
495
|
return
|
|
496
|
-
self._vispy_mouse_press(
|
|
496
|
+
vispy_event = self._vispy_mouse_press(
|
|
497
497
|
native=ev,
|
|
498
498
|
pos=_get_event_xy(ev),
|
|
499
499
|
button=BUTTONMAP.get(ev.button(), 0),
|
|
500
500
|
modifiers=self._modifiers(ev),
|
|
501
501
|
)
|
|
502
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
503
|
+
if not vispy_event.handled:
|
|
504
|
+
ev.ignore()
|
|
502
505
|
|
|
503
506
|
def mouseReleaseEvent(self, ev):
|
|
504
507
|
if self._vispy_canvas is None:
|
|
505
508
|
return
|
|
506
|
-
self._vispy_mouse_release(
|
|
509
|
+
vispy_event = self._vispy_mouse_release(
|
|
507
510
|
native=ev,
|
|
508
511
|
pos=_get_event_xy(ev),
|
|
509
512
|
button=BUTTONMAP[ev.button()],
|
|
510
513
|
modifiers=self._modifiers(ev),
|
|
511
514
|
)
|
|
515
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
516
|
+
if not vispy_event.handled:
|
|
517
|
+
ev.ignore()
|
|
512
518
|
|
|
513
519
|
def mouseDoubleClickEvent(self, ev):
|
|
514
520
|
if self._vispy_canvas is None:
|
|
515
521
|
return
|
|
516
|
-
self._vispy_mouse_double_click(
|
|
522
|
+
vispy_event = self._vispy_mouse_double_click(
|
|
517
523
|
native=ev,
|
|
518
524
|
pos=_get_event_xy(ev),
|
|
519
525
|
button=BUTTONMAP.get(ev.button(), 0),
|
|
520
526
|
modifiers=self._modifiers(ev),
|
|
521
527
|
)
|
|
528
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
529
|
+
if not vispy_event.handled:
|
|
530
|
+
ev.ignore()
|
|
522
531
|
|
|
523
532
|
def mouseMoveEvent(self, ev):
|
|
524
533
|
if self._vispy_canvas is None:
|
|
525
534
|
return
|
|
526
|
-
|
|
535
|
+
# NB ignores events, returns None for events in quick succession
|
|
536
|
+
vispy_event = self._vispy_mouse_move(
|
|
527
537
|
native=ev,
|
|
528
538
|
pos=_get_event_xy(ev),
|
|
529
539
|
modifiers=self._modifiers(ev),
|
|
530
540
|
)
|
|
541
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
542
|
+
# Note that the handler can return None, this is equivalent to not handling the event
|
|
543
|
+
if vispy_event is None or not vispy_event.handled:
|
|
544
|
+
# Theoretically, a parent widget might want to listen to all of
|
|
545
|
+
# the mouse move events, including those that VisPy ignores
|
|
546
|
+
ev.ignore()
|
|
531
547
|
|
|
532
548
|
def wheelEvent(self, ev):
|
|
533
549
|
if self._vispy_canvas is None:
|
|
@@ -544,12 +560,15 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
544
560
|
delta = ev.angleDelta()
|
|
545
561
|
deltax, deltay = delta.x() / 120.0, delta.y() / 120.0
|
|
546
562
|
# Emit event
|
|
547
|
-
self._vispy_canvas.events.mouse_wheel(
|
|
563
|
+
vispy_event = self._vispy_canvas.events.mouse_wheel(
|
|
548
564
|
native=ev,
|
|
549
565
|
delta=(deltax, deltay),
|
|
550
566
|
pos=_get_event_xy(ev),
|
|
551
567
|
modifiers=self._modifiers(ev),
|
|
552
568
|
)
|
|
569
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
570
|
+
if not vispy_event.handled:
|
|
571
|
+
ev.ignore()
|
|
553
572
|
|
|
554
573
|
def keyPressEvent(self, ev):
|
|
555
574
|
self._keyEvent(self._vispy_canvas.events.key_press, ev)
|
|
@@ -571,17 +590,20 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
571
590
|
pos = self.mapFromGlobal(ev.globalPos())
|
|
572
591
|
pos = pos.x(), pos.y()
|
|
573
592
|
|
|
593
|
+
vispy_event = None
|
|
574
594
|
if t == QtCore.Qt.NativeGestureType.BeginNativeGesture:
|
|
575
|
-
self._vispy_canvas.events.touch(
|
|
595
|
+
vispy_event = self._vispy_canvas.events.touch(
|
|
576
596
|
type='gesture_begin',
|
|
577
597
|
pos=_get_event_xy(ev),
|
|
598
|
+
modifiers=self._modifiers(ev),
|
|
578
599
|
)
|
|
579
600
|
elif t == QtCore.Qt.NativeGestureType.EndNativeGesture:
|
|
580
601
|
self._native_touch_total_rotation = []
|
|
581
602
|
self._native_touch_total_scale = []
|
|
582
|
-
self._vispy_canvas.events.touch(
|
|
603
|
+
vispy_event = self._vispy_canvas.events.touch(
|
|
583
604
|
type='gesture_end',
|
|
584
605
|
pos=_get_event_xy(ev),
|
|
606
|
+
modifiers=self._modifiers(ev),
|
|
585
607
|
)
|
|
586
608
|
elif t == QtCore.Qt.NativeGestureType.RotateNativeGesture:
|
|
587
609
|
angle = ev.value()
|
|
@@ -592,12 +614,13 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
592
614
|
)
|
|
593
615
|
self._native_gesture_rotation_values.append(angle)
|
|
594
616
|
total_rotation_angle = math.fsum(self._native_gesture_rotation_values)
|
|
595
|
-
self._vispy_canvas.events.touch(
|
|
617
|
+
vispy_event = self._vispy_canvas.events.touch(
|
|
596
618
|
type="gesture_rotate",
|
|
597
619
|
pos=pos,
|
|
598
620
|
rotation=angle,
|
|
599
621
|
last_rotation=last_angle,
|
|
600
622
|
total_rotation_angle=total_rotation_angle,
|
|
623
|
+
modifiers=self._modifiers(ev),
|
|
601
624
|
)
|
|
602
625
|
elif t == QtCore.Qt.NativeGestureType.ZoomNativeGesture:
|
|
603
626
|
scale = ev.value()
|
|
@@ -608,12 +631,13 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
608
631
|
)
|
|
609
632
|
self._native_gesture_scale_values.append(scale)
|
|
610
633
|
total_scale_factor = math.fsum(self._native_gesture_scale_values)
|
|
611
|
-
self._vispy_canvas.events.touch(
|
|
634
|
+
vispy_event = self._vispy_canvas.events.touch(
|
|
612
635
|
type="gesture_zoom",
|
|
613
636
|
pos=pos,
|
|
614
637
|
last_scale=last_scale,
|
|
615
638
|
scale=scale,
|
|
616
639
|
total_scale_factor=total_scale_factor,
|
|
640
|
+
modifiers=self._modifiers(ev),
|
|
617
641
|
)
|
|
618
642
|
# QtCore.Qt.NativeGestureType.PanNativeGesture
|
|
619
643
|
# Qt6 docs seem to imply this is only supported on Wayland but I have
|
|
@@ -622,6 +646,11 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
622
646
|
# On macOS, more fingers are usually swallowed by the OS (by spaces,
|
|
623
647
|
# mission control, etc.).
|
|
624
648
|
|
|
649
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
650
|
+
# Note that some handlers return None, this is equivalent to not handling the event
|
|
651
|
+
if vispy_event is None or not vispy_event.handled:
|
|
652
|
+
ev.ignore()
|
|
653
|
+
|
|
625
654
|
def event(self, ev):
|
|
626
655
|
out = super(QtBaseCanvasBackend, self).event(ev)
|
|
627
656
|
|
|
@@ -644,7 +673,10 @@ class QtBaseCanvasBackend(BaseCanvasBackend):
|
|
|
644
673
|
else:
|
|
645
674
|
key = None
|
|
646
675
|
mod = self._modifiers(ev)
|
|
647
|
-
func(native=ev, key=key, text=str(ev.text()), modifiers=mod)
|
|
676
|
+
vispy_event = func(native=ev, key=key, text=str(ev.text()), modifiers=mod)
|
|
677
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
678
|
+
if not vispy_event.handled:
|
|
679
|
+
ev.ignore()
|
|
648
680
|
|
|
649
681
|
def _modifiers(self, event):
|
|
650
682
|
# Convert the QT modifier state into a tuple of active modifier keys.
|
|
@@ -771,7 +803,10 @@ class CanvasBackendEgl(QtBaseCanvasBackend, QWidget):
|
|
|
771
803
|
|
|
772
804
|
def resizeEvent(self, event):
|
|
773
805
|
w, h = event.size().width(), event.size().height()
|
|
774
|
-
self._vispy_canvas.events.resize(size=(w, h))
|
|
806
|
+
vispy_event = self._vispy_canvas.events.resize(size=(w, h))
|
|
807
|
+
# If vispy did not handle the event, clear the accept parameter of the qt event
|
|
808
|
+
if not vispy_event.handled:
|
|
809
|
+
event.ignore()
|
|
775
810
|
|
|
776
811
|
def paintEvent(self, event):
|
|
777
812
|
self._vispy_canvas.events.draw(region=None)
|
vispy/color/colormap.py
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
from __future__ import division # just to be safe...
|
|
6
6
|
import warnings
|
|
7
7
|
|
|
8
|
+
import re
|
|
8
9
|
import numpy as np
|
|
9
10
|
|
|
10
|
-
from .color_array import ColorArray
|
|
11
|
+
from .color_array import ColorArray, Color
|
|
11
12
|
from ..ext.cubehelix import cubehelix
|
|
12
13
|
from hsluv import hsluv_to_rgb
|
|
13
14
|
from ..util.check_environment import has_matplotlib
|
|
@@ -158,11 +159,12 @@ def _glsl_mix(controls=None, colors=None, texture_map_data=None):
|
|
|
158
159
|
LUT[:, 0, 2] = np.interp(x, controls, c_rgba[:, 2])
|
|
159
160
|
LUT[:, 0, 3] = np.interp(x, controls, c_rgba[:, 3])
|
|
160
161
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
return """
|
|
163
|
+
uniform sampler2D texture2D_LUT;
|
|
164
|
+
vec4 colormap(float t) {
|
|
165
|
+
return texture2D(texture2D_LUT, vec2(0.0, clamp(t, 0.0, 1.0)));
|
|
166
|
+
}
|
|
167
|
+
"""
|
|
166
168
|
|
|
167
169
|
|
|
168
170
|
def _glsl_step(controls=None, colors=None, texture_map_data=None):
|
|
@@ -192,11 +194,12 @@ def _glsl_step(controls=None, colors=None, texture_map_data=None):
|
|
|
192
194
|
colors_rgba = ColorArray(colors[:])._rgba
|
|
193
195
|
LUT[:, 0, :] = colors_rgba[j]
|
|
194
196
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
return """
|
|
198
|
+
uniform sampler2D texture2D_LUT;
|
|
199
|
+
vec4 colormap(float t) {
|
|
200
|
+
return texture2D(texture2D_LUT, vec2(0.0, clamp(t, 0.0, 1.0)));
|
|
201
|
+
}
|
|
202
|
+
"""
|
|
200
203
|
|
|
201
204
|
|
|
202
205
|
# Mini GLSL template system for colors.
|
|
@@ -219,6 +222,12 @@ class BaseColormap(object):
|
|
|
219
222
|
----------
|
|
220
223
|
colors : list of lists, tuples, or ndarrays
|
|
221
224
|
The control colors used by the colormap (shape = (ncolors, 4)).
|
|
225
|
+
bad_color : None | array-like
|
|
226
|
+
The color mapping for NaN values.
|
|
227
|
+
high_color : None | array-like
|
|
228
|
+
The color mapping for values greater than or equal to 1.
|
|
229
|
+
low_color : None | array-like
|
|
230
|
+
The color mapping for values less than or equal to 0.
|
|
222
231
|
|
|
223
232
|
Notes
|
|
224
233
|
-----
|
|
@@ -234,6 +243,9 @@ class BaseColormap(object):
|
|
|
234
243
|
|
|
235
244
|
# Control colors used by the colormap.
|
|
236
245
|
colors = None
|
|
246
|
+
bad_color = None
|
|
247
|
+
high_color = None
|
|
248
|
+
low_color = None
|
|
237
249
|
|
|
238
250
|
# GLSL string with a function implementing the color map.
|
|
239
251
|
glsl_map = None
|
|
@@ -242,7 +254,7 @@ class BaseColormap(object):
|
|
|
242
254
|
# for luminance to RGBA conversion.
|
|
243
255
|
texture_map_data = None
|
|
244
256
|
|
|
245
|
-
def __init__(self, colors=None):
|
|
257
|
+
def __init__(self, colors=None, *, bad_color=None, low_color=None, high_color=None):
|
|
246
258
|
# Ensure the colors are arrays.
|
|
247
259
|
if colors is not None:
|
|
248
260
|
self.colors = colors
|
|
@@ -252,6 +264,51 @@ class BaseColormap(object):
|
|
|
252
264
|
if len(self.colors) > 0:
|
|
253
265
|
self.glsl_map = _process_glsl_template(self.glsl_map,
|
|
254
266
|
self.colors.rgba)
|
|
267
|
+
if high_color is not None:
|
|
268
|
+
self.high_color = Color(high_color)
|
|
269
|
+
self._set_high_color_glsl()
|
|
270
|
+
if low_color is not None:
|
|
271
|
+
self.low_color = Color(low_color)
|
|
272
|
+
self._set_low_color_glsl()
|
|
273
|
+
|
|
274
|
+
self.bad_color = Color((0, 0, 0, 0) if bad_color is None else bad_color)
|
|
275
|
+
self._set_bad_color_glsl()
|
|
276
|
+
|
|
277
|
+
def _set_bad_color_glsl(self):
|
|
278
|
+
"""Set the color mapping for NaN values."""
|
|
279
|
+
r, g, b, a = self.bad_color.rgba
|
|
280
|
+
|
|
281
|
+
bad_color_glsl = f"""
|
|
282
|
+
// Map NaN to bad_color
|
|
283
|
+
if (!(t <= 0.0 || 0.0 <= t)) {{
|
|
284
|
+
return vec4({r:.3f}, {g:.3f}, {b:.3f}, {a:.3f});
|
|
285
|
+
}}"""
|
|
286
|
+
|
|
287
|
+
self.glsl_map = re.sub(r'float t\) \{', f'float t) {{{bad_color_glsl}', self.glsl_map)
|
|
288
|
+
|
|
289
|
+
def _set_high_color_glsl(self):
|
|
290
|
+
"""Set the color mapping for values greater than or equal to max clim."""
|
|
291
|
+
r, g, b, a = self.high_color.rgba
|
|
292
|
+
|
|
293
|
+
high_color_glsl = f"""
|
|
294
|
+
// Map high_color
|
|
295
|
+
if (1 - t <= 1e-12) {{ // use epsilon to work around numerical imprecision
|
|
296
|
+
return vec4({r:.3f}, {g:.3f}, {b:.3f}, {a:.3f});
|
|
297
|
+
}}"""
|
|
298
|
+
|
|
299
|
+
self.glsl_map = re.sub(r'float t\) \{', f'float t) {{{high_color_glsl}', self.glsl_map)
|
|
300
|
+
|
|
301
|
+
def _set_low_color_glsl(self):
|
|
302
|
+
"""Set the color mapping for values less than or equal to min clim."""
|
|
303
|
+
r, g, b, a = self.low_color.rgba
|
|
304
|
+
|
|
305
|
+
low_color_glsl = f"""
|
|
306
|
+
// Map low_color
|
|
307
|
+
if (t <= 1e-12) {{ // use epsilon to work around numerical imprecision
|
|
308
|
+
return vec4({r:.3f}, {g:.3f}, {b:.3f}, {a:.3f});
|
|
309
|
+
}}"""
|
|
310
|
+
|
|
311
|
+
self.glsl_map = re.sub(r'float t\) \{', f'float t) {{{low_color_glsl}', self.glsl_map)
|
|
255
312
|
|
|
256
313
|
def map(self, item):
|
|
257
314
|
"""Return a rgba array for the requested items.
|
|
@@ -281,6 +338,15 @@ class BaseColormap(object):
|
|
|
281
338
|
"""
|
|
282
339
|
raise NotImplementedError()
|
|
283
340
|
|
|
341
|
+
def _map_edge_case_colors(self, param, colors):
|
|
342
|
+
"""Apply special mapping to edge cases (NaN and max/min clim)."""
|
|
343
|
+
colors = np.where(np.isnan(param.reshape(-1, 1)), self.bad_color.rgba, colors)
|
|
344
|
+
if self.high_color is not None:
|
|
345
|
+
colors = np.where((param == 1).reshape(-1, 1), self.high_color.rgba, colors)
|
|
346
|
+
if self.low_color is not None:
|
|
347
|
+
colors = np.where((param == 0).reshape(-1, 1), self.low_color.rgba, colors)
|
|
348
|
+
return colors
|
|
349
|
+
|
|
284
350
|
def texture_lut(self):
|
|
285
351
|
"""Return a texture2D object for LUT after its value is set. Can be None."""
|
|
286
352
|
return None
|
|
@@ -368,6 +434,12 @@ class Colormap(BaseColormap):
|
|
|
368
434
|
be 'zero'.
|
|
369
435
|
If 'linear', ncontrols = ncolors (one color per control point).
|
|
370
436
|
If 'zero', ncontrols = ncolors+1 (one color per bin).
|
|
437
|
+
bad_color : None | array-like
|
|
438
|
+
The color mapping for NaN values.
|
|
439
|
+
high_color : None | array-like
|
|
440
|
+
The color mapping for values greater than or equal to 1.
|
|
441
|
+
low_color : None | array-like
|
|
442
|
+
The color mapping for values less than or equal to 0.
|
|
371
443
|
|
|
372
444
|
Examples
|
|
373
445
|
--------
|
|
@@ -379,7 +451,8 @@ class Colormap(BaseColormap):
|
|
|
379
451
|
|
|
380
452
|
"""
|
|
381
453
|
|
|
382
|
-
def __init__(self, colors, controls=None, interpolation='linear'
|
|
454
|
+
def __init__(self, colors, controls=None, interpolation='linear', *,
|
|
455
|
+
bad_color=None, low_color=None, high_color=None):
|
|
383
456
|
self.interpolation = interpolation
|
|
384
457
|
ncontrols = self._ncontrols(len(colors))
|
|
385
458
|
# Default controls.
|
|
@@ -391,7 +464,8 @@ class Colormap(BaseColormap):
|
|
|
391
464
|
self.texture_map_data = np.zeros((LUT_len, 1, 4), dtype=np.float32)
|
|
392
465
|
self.glsl_map = self._glsl_map_generator(self._controls, colors,
|
|
393
466
|
self.texture_map_data)
|
|
394
|
-
super(Colormap, self).__init__(colors
|
|
467
|
+
super(Colormap, self).__init__(colors, bad_color=bad_color,
|
|
468
|
+
high_color=high_color, low_color=low_color)
|
|
395
469
|
|
|
396
470
|
@property
|
|
397
471
|
def interpolation(self):
|
|
@@ -428,7 +502,8 @@ class Colormap(BaseColormap):
|
|
|
428
502
|
colors : list
|
|
429
503
|
List of rgba colors.
|
|
430
504
|
"""
|
|
431
|
-
|
|
505
|
+
colors = self._map_function(self.colors.rgba, x, self._controls)
|
|
506
|
+
return self._map_edge_case_colors(x, colors)
|
|
432
507
|
|
|
433
508
|
def texture_lut(self):
|
|
434
509
|
"""Return a texture2D object for LUT after its value is set. Can be None."""
|
|
@@ -540,7 +615,8 @@ class _Fire(BaseColormap):
|
|
|
540
615
|
a, b, d = self.colors.rgba
|
|
541
616
|
c = _mix_simple(a, b, t)
|
|
542
617
|
e = _mix_simple(b, d, t**2)
|
|
543
|
-
|
|
618
|
+
colors = np.atleast_2d(_mix_simple(c, e, t))
|
|
619
|
+
return self._map_edge_case_colors(t, colors)
|
|
544
620
|
|
|
545
621
|
|
|
546
622
|
class _Grays(BaseColormap):
|
|
@@ -551,10 +627,8 @@ class _Grays(BaseColormap):
|
|
|
551
627
|
"""
|
|
552
628
|
|
|
553
629
|
def map(self, t):
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
else:
|
|
557
|
-
return np.array([t, t, t, 1.0], dtype=np.float32)
|
|
630
|
+
colors = np.c_[t, t, t, np.ones(t.shape)]
|
|
631
|
+
return self._map_edge_case_colors(t, colors)
|
|
558
632
|
|
|
559
633
|
|
|
560
634
|
class _Ice(BaseColormap):
|
|
@@ -565,11 +639,8 @@ class _Ice(BaseColormap):
|
|
|
565
639
|
"""
|
|
566
640
|
|
|
567
641
|
def map(self, t):
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
np.ones(t.shape)]).astype(np.float32)
|
|
571
|
-
else:
|
|
572
|
-
return np.array([t, t, 1.0, 1.0], dtype=np.float32)
|
|
642
|
+
colors = np.c_[t, t, np.ones(t.shape), np.ones(t.shape)]
|
|
643
|
+
return self._map_edge_case_colors(t, colors)
|
|
573
644
|
|
|
574
645
|
|
|
575
646
|
class _Hot(BaseColormap):
|
|
@@ -586,7 +657,8 @@ class _Hot(BaseColormap):
|
|
|
586
657
|
def map(self, t):
|
|
587
658
|
rgba = self.colors.rgba
|
|
588
659
|
smoothed = smoothstep(rgba[0, :3], rgba[1, :3], t)
|
|
589
|
-
|
|
660
|
+
colors = np.hstack((smoothed, np.ones((len(t), 1))))
|
|
661
|
+
return self._map_edge_case_colors(t, colors)
|
|
590
662
|
|
|
591
663
|
|
|
592
664
|
class _Winter(BaseColormap):
|
|
@@ -600,9 +672,15 @@ class _Winter(BaseColormap):
|
|
|
600
672
|
"""
|
|
601
673
|
|
|
602
674
|
def map(self, t):
|
|
603
|
-
|
|
675
|
+
colors = _mix_simple(self.colors.rgba[0],
|
|
604
676
|
self.colors.rgba[1],
|
|
605
677
|
np.sqrt(t))
|
|
678
|
+
return self._map_edge_case_colors(t, colors)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
class _HiLo(_Grays):
|
|
682
|
+
def __init__(self, *args, **kwargs):
|
|
683
|
+
super().__init__(*args, **kwargs, low_color='blue', high_color='red')
|
|
606
684
|
|
|
607
685
|
|
|
608
686
|
class SingleHue(Colormap):
|
|
@@ -1089,6 +1167,7 @@ _colormaps = dict(
|
|
|
1089
1167
|
husl=HSLuv(),
|
|
1090
1168
|
diverging=Diverging(),
|
|
1091
1169
|
RdYeBuCy=RedYellowBlueCyan(),
|
|
1170
|
+
HiLo=_HiLo(),
|
|
1092
1171
|
)
|
|
1093
1172
|
|
|
1094
1173
|
|
vispy/color/tests/test_color.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
# Distributed under the (new) BSD License. See LICENSE.txt for more info.
|
|
4
4
|
|
|
5
5
|
import numpy as np
|
|
6
|
-
from numpy.testing import assert_array_equal, assert_allclose
|
|
6
|
+
from numpy.testing import assert_array_equal, assert_array_almost_equal, assert_allclose
|
|
7
7
|
|
|
8
8
|
from vispy.color import (Color, ColorArray, get_color_names,
|
|
9
9
|
Colormap,
|
|
@@ -349,4 +349,30 @@ def test_normalize():
|
|
|
349
349
|
assert_allclose([y.min(), y.max()], [0.2975, 1-0.2975], 1e-1, 1e-1)
|
|
350
350
|
|
|
351
351
|
|
|
352
|
+
def test_colormap_bad_color():
|
|
353
|
+
"""Test NaN handling."""
|
|
354
|
+
red = (1, 0, 0, 1)
|
|
355
|
+
white = (1, 1, 1, 1)
|
|
356
|
+
green = (0, 1, 0, 1)
|
|
357
|
+
cm = Colormap([white, green], bad_color=red)
|
|
358
|
+
assert_array_equal(cm[0].rgba, [white])
|
|
359
|
+
assert_array_equal(cm[1].rgba, [green])
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def test_colormap_high_low_color():
|
|
363
|
+
"""Test handling of clim extremes."""
|
|
364
|
+
hilo = get_colormap('HiLo')
|
|
365
|
+
white = (1, 1, 1, 1)
|
|
366
|
+
gray = (0.5, 0.5, 0.5, 1)
|
|
367
|
+
black = (0, 0, 0, 1)
|
|
368
|
+
red = (1, 0, 0, 1)
|
|
369
|
+
blue = (0, 0, 1, 1)
|
|
370
|
+
|
|
371
|
+
assert_array_equal(hilo[0].rgba, [blue])
|
|
372
|
+
assert_array_almost_equal(hilo[0.000001].rgba, [black])
|
|
373
|
+
assert_array_equal(hilo[0.5].rgba, [gray])
|
|
374
|
+
assert_array_almost_equal(hilo[0.999999].rgba, [white])
|
|
375
|
+
assert_array_equal(hilo[1].rgba, [red])
|
|
376
|
+
|
|
377
|
+
|
|
352
378
|
run_tests_if_main()
|
vispy/ext/cocoapy.py
CHANGED
|
@@ -18,26 +18,6 @@ else:
|
|
|
18
18
|
string_types = basestring, # noqa
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
# handle dlopen cache changes in macOS 11 (Big Sur)
|
|
22
|
-
# ref https://stackoverflow.com/questions/63475461/unable-to-import-opengl-gl-in-python-on-macos
|
|
23
|
-
try:
|
|
24
|
-
import OpenGL.GL # noqa
|
|
25
|
-
except ImportError:
|
|
26
|
-
# print('Drat, patching for Big Sur')
|
|
27
|
-
orig_util_find_library = util.find_library
|
|
28
|
-
|
|
29
|
-
def new_util_find_library(name):
|
|
30
|
-
res = orig_util_find_library(name)
|
|
31
|
-
if res:
|
|
32
|
-
return res
|
|
33
|
-
lut = {
|
|
34
|
-
'objc': 'libobjc.dylib',
|
|
35
|
-
'quartz': 'Quartz.framework/Quartz'
|
|
36
|
-
}
|
|
37
|
-
return lut.get(name, name+'.framework/'+name)
|
|
38
|
-
util.find_library = new_util_find_library
|
|
39
|
-
|
|
40
|
-
|
|
41
21
|
# Based on Pyglet code
|
|
42
22
|
|
|
43
23
|
##############################################################################
|
|
@@ -1285,7 +1265,7 @@ NSApplicationActivationPolicyProhibited = 2
|
|
|
1285
1265
|
|
|
1286
1266
|
# QUARTZ / COREGRAPHICS
|
|
1287
1267
|
|
|
1288
|
-
quartz = cdll.LoadLibrary(util.find_library('
|
|
1268
|
+
quartz = cdll.LoadLibrary(util.find_library('Quartz'))
|
|
1289
1269
|
|
|
1290
1270
|
CGDirectDisplayID = c_uint32 # CGDirectDisplay.h
|
|
1291
1271
|
CGError = c_int32 # CGError.h
|
vispy/geometry/meshdata.py
CHANGED
|
@@ -386,7 +386,9 @@ class MeshData(object):
|
|
|
386
386
|
if indexed is None:
|
|
387
387
|
return self._vertex_normals
|
|
388
388
|
elif indexed == 'faces':
|
|
389
|
-
|
|
389
|
+
if self._vertex_normals_indexed_by_faces is None:
|
|
390
|
+
self._vertex_normals_indexed_by_faces = self._vertex_normals[self.get_faces()]
|
|
391
|
+
return self._vertex_normals_indexed_by_faces
|
|
390
392
|
|
|
391
393
|
def get_vertex_colors(self, indexed=None):
|
|
392
394
|
"""Get vertex colors
|
|
@@ -2,6 +2,7 @@ import sys
|
|
|
2
2
|
from unittest import SkipTest
|
|
3
3
|
|
|
4
4
|
import numpy as np
|
|
5
|
+
import pytest
|
|
5
6
|
from numpy.testing import assert_array_almost_equal
|
|
6
7
|
|
|
7
8
|
from vispy.testing import run_tests_if_main
|
|
@@ -503,4 +504,91 @@ def test_edge_event():
|
|
|
503
504
|
t.triangulate()
|
|
504
505
|
|
|
505
506
|
|
|
507
|
+
def test_triangulate_triangle():
|
|
508
|
+
pts = np.array([
|
|
509
|
+
[4, 4],
|
|
510
|
+
[1, 4],
|
|
511
|
+
[1, 2],
|
|
512
|
+
])
|
|
513
|
+
t = _triangulation_from_points(pts)
|
|
514
|
+
|
|
515
|
+
t.triangulate()
|
|
516
|
+
|
|
517
|
+
assert len(t.tris) == 1
|
|
518
|
+
_assert_triangle_pts_in_input(t, pts)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def test_triangulate_square():
|
|
522
|
+
pts = np.array([
|
|
523
|
+
[4, 4],
|
|
524
|
+
[1, 4],
|
|
525
|
+
[1, 2],
|
|
526
|
+
[4, 2],
|
|
527
|
+
])
|
|
528
|
+
t = _triangulation_from_points(pts)
|
|
529
|
+
|
|
530
|
+
t.triangulate()
|
|
531
|
+
|
|
532
|
+
assert len(t.tris) == 2
|
|
533
|
+
_assert_triangle_pts_in_input(t, pts)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_triangulate_triangle_with_collinear_pts():
|
|
537
|
+
pts = np.array([
|
|
538
|
+
[4, 4],
|
|
539
|
+
[3, 4],
|
|
540
|
+
[1, 4],
|
|
541
|
+
[1, 2],
|
|
542
|
+
])
|
|
543
|
+
t = _triangulation_from_points(pts)
|
|
544
|
+
|
|
545
|
+
t.triangulate()
|
|
546
|
+
|
|
547
|
+
assert len(t.tris) in (1, 2)
|
|
548
|
+
_assert_triangle_pts_in_input(t, pts)
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def test_triangulate_collinear_path():
|
|
552
|
+
pts = np.array([
|
|
553
|
+
[4, 4],
|
|
554
|
+
[3, 4],
|
|
555
|
+
[1, 4],
|
|
556
|
+
])
|
|
557
|
+
t = _triangulation_from_points(pts)
|
|
558
|
+
|
|
559
|
+
t.triangulate()
|
|
560
|
+
|
|
561
|
+
assert len(t.tris) == 0
|
|
562
|
+
_assert_triangle_pts_in_input(t, pts)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
@pytest.mark.xfail(reason="See https://github.com/vispy/vispy/issues/2247")
|
|
566
|
+
def test_triangulate_collinear_path_with_repeat():
|
|
567
|
+
pts = np.array([
|
|
568
|
+
[4, 4],
|
|
569
|
+
[3, 4],
|
|
570
|
+
[1, 4],
|
|
571
|
+
[4, 4],
|
|
572
|
+
[1, 2],
|
|
573
|
+
])
|
|
574
|
+
t = _triangulation_from_points(pts)
|
|
575
|
+
|
|
576
|
+
t.triangulate()
|
|
577
|
+
|
|
578
|
+
assert len(t.tris) == 0
|
|
579
|
+
_assert_triangle_pts_in_input(t, pts)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def _assert_triangle_pts_in_input(t, input_pts):
|
|
583
|
+
pt_indices_in_tris = set(v for tri in t.tris for v in tri)
|
|
584
|
+
for i in pt_indices_in_tris:
|
|
585
|
+
assert np.any(np.all(t.pts[i] == input_pts, axis=1))
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _triangulation_from_points(points):
|
|
589
|
+
inds = np.arange(points.shape[0])[:, np.newaxis]
|
|
590
|
+
edges = np.hstack([inds, np.roll(inds, -1)])
|
|
591
|
+
return T(points, edges)
|
|
592
|
+
|
|
593
|
+
|
|
506
594
|
run_tests_if_main()
|
vispy/geometry/triangulation.py
CHANGED
|
@@ -99,7 +99,7 @@ class Triangulation(object):
|
|
|
99
99
|
self._tops = self.edges.max(axis=1)
|
|
100
100
|
self._bottoms = self.edges.min(axis=1)
|
|
101
101
|
|
|
102
|
-
#
|
|
102
|
+
# initialize sweep front
|
|
103
103
|
# values in this list are indexes into self.pts
|
|
104
104
|
self._front = [0, 2, 1]
|
|
105
105
|
|
|
@@ -185,7 +185,7 @@ class Triangulation(object):
|
|
|
185
185
|
for j in self._bottoms[self._tops == i]:
|
|
186
186
|
# Make sure edge (j, i) is present in mesh
|
|
187
187
|
# because edge event may have created a new front list
|
|
188
|
-
self._edge_event(i, j)
|
|
188
|
+
self._edge_event(i, int(j))
|
|
189
189
|
front = self._front
|
|
190
190
|
|
|
191
191
|
self._finalize()
|
|
@@ -203,7 +203,7 @@ class Triangulation(object):
|
|
|
203
203
|
while k < idx-1:
|
|
204
204
|
# if edges lie in counterclockwise direction, then signed area
|
|
205
205
|
# is positive
|
|
206
|
-
if self.
|
|
206
|
+
if self._orientation((front[k], front[k+1]), front[k+2]) < 0:
|
|
207
207
|
self._add_tri(front[k], front[k+1], front[k+2])
|
|
208
208
|
front.pop(k+1)
|
|
209
209
|
idx -= 1
|
|
@@ -398,7 +398,7 @@ class Triangulation(object):
|
|
|
398
398
|
|
|
399
399
|
upper_polygon.append(front[front_index+front_dir])
|
|
400
400
|
|
|
401
|
-
# (iii)
|
|
401
|
+
# (iii) triangulate empty areas
|
|
402
402
|
|
|
403
403
|
for polygon in [lower_polygon, upper_polygon]:
|
|
404
404
|
dist = self._distances_from_line((i, j), polygon)
|
|
@@ -673,13 +673,6 @@ class Triangulation(object):
|
|
|
673
673
|
d = (a + c - b) / ((4 * a * c)**0.5)
|
|
674
674
|
return d
|
|
675
675
|
|
|
676
|
-
def _iscounterclockwise(self, a, b, c):
|
|
677
|
-
# Check if the points lie in counter-clockwise order or not
|
|
678
|
-
A = self.pts[a]
|
|
679
|
-
B = self.pts[b]
|
|
680
|
-
C = self.pts[c]
|
|
681
|
-
return _cross_2d(B - A, C - B) > 0
|
|
682
|
-
|
|
683
676
|
def _edges_intersect(self, edge1, edge2):
|
|
684
677
|
"""Return 1 if edges intersect completely (endpoints excluded)"""
|
|
685
678
|
h12 = self._intersect_edge_arrays(self.pts[np.array(edge1)],
|
|
@@ -748,7 +741,7 @@ class Triangulation(object):
|
|
|
748
741
|
# sanity check
|
|
749
742
|
assert a != b and b != c and c != a
|
|
750
743
|
|
|
751
|
-
# ignore
|
|
744
|
+
# ignore tris with duplicate points
|
|
752
745
|
pa = self.pts[a]
|
|
753
746
|
pb = self.pts[b]
|
|
754
747
|
pc = self.pts[c]
|
|
@@ -761,8 +754,13 @@ class Triangulation(object):
|
|
|
761
754
|
raise Exception("Cannot add %s; already have %s" %
|
|
762
755
|
((a, b, c), t))
|
|
763
756
|
|
|
757
|
+
# ignore lines
|
|
758
|
+
orientation = self._orientation((a, b), c)
|
|
759
|
+
if orientation == 0:
|
|
760
|
+
return
|
|
761
|
+
|
|
764
762
|
# TODO: should add to edges_lookup after legalization??
|
|
765
|
-
if
|
|
763
|
+
if orientation < 0:
|
|
766
764
|
assert (a, b) not in self._edges_lookup
|
|
767
765
|
assert (b, c) not in self._edges_lookup
|
|
768
766
|
assert (c, a) not in self._edges_lookup
|