vispy 0.13.0__cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl → 0.14.0__cp38-cp38-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.

@@ -250,7 +250,7 @@ class CanvasBackend(BaseCanvasBackend):
250
250
  raise ValueError('fullscreen must be <= %s'
251
251
  % len(monitor))
252
252
  monitor = monitor[p.fullscreen]
253
- use_size = glfw.get_video_mode(monitor)[:2]
253
+ use_size = glfw.get_video_mode(monitor)[0][:2]
254
254
  if use_size != tuple(p.size):
255
255
  logger.debug('Requested size %s, will be ignored to '
256
256
  'use fullscreen mode %s' % (p.size, use_size))
vispy/color/colormap.py CHANGED
@@ -1092,17 +1092,13 @@ _colormaps = dict(
1092
1092
  )
1093
1093
 
1094
1094
 
1095
- def get_colormap(name, *args, **kwargs):
1096
- """Obtain a colormap.
1095
+ def get_colormap(name):
1096
+ """Obtain a colormap by name.
1097
1097
 
1098
1098
  Parameters
1099
1099
  ----------
1100
1100
  name : str | Colormap
1101
1101
  Colormap name. Can also be a Colormap for pass-through.
1102
- *args:
1103
- Deprecated.
1104
- **kwargs
1105
- Deprecated.
1106
1102
 
1107
1103
  Examples
1108
1104
  --------
@@ -1111,18 +1107,10 @@ def get_colormap(name, *args, **kwargs):
1111
1107
 
1112
1108
  .. versionchanged: 0.7
1113
1109
 
1114
- Additional args/kwargs are no longer accepted. Colormap classes are
1115
- no longer created on the fly. To create a ``cubehelix``
1116
- (``CubeHelixColormap``), ``single_hue`` (``SingleHue``), ``hsl``
1117
- (``HSL``), ``husl`` (``HSLuv``), ``diverging`` (``Diverging``), or
1118
- ``RdYeBuCy`` (``RedYellowBlueCyan``) colormap you must import and
1119
- instantiate it directly from the ``vispy.color.colormap`` module.
1110
+ Additional args/kwargs are no longer accepted. Colormap instances are
1111
+ no longer created on the fly.
1120
1112
 
1121
1113
  """
1122
- if args or kwargs:
1123
- warnings.warn("Creating a Colormap instance with 'get_colormap' is "
1124
- "no longer supported. No additional arguments or "
1125
- "keyword arguments should be passed.", DeprecationWarning)
1126
1114
  if isinstance(name, BaseColormap):
1127
1115
  return name
1128
1116
 
@@ -1130,14 +1118,6 @@ def get_colormap(name, *args, **kwargs):
1130
1118
  raise TypeError('colormap must be a Colormap or string name')
1131
1119
  if name in _colormaps: # vispy cmap
1132
1120
  cmap = _colormaps[name]
1133
- if name in ("cubehelix", "single_hue", "hsl", "husl", "diverging", "RdYeBuCy"):
1134
- warnings.warn(
1135
- f"Colormap '{name}' has been deprecated since vispy 0.7. "
1136
- f"Please import and create 'vispy.color.colormap.{cmap.__class__.__name__}' "
1137
- "directly instead.",
1138
- DeprecationWarning,
1139
- stacklevel=2,
1140
- )
1141
1121
 
1142
1122
  elif has_matplotlib(): # matplotlib cmap
1143
1123
  try:
vispy/gloo/texture.py CHANGED
@@ -33,7 +33,9 @@ def convert_dtype_and_clip(data, dtype, copy=False):
33
33
  else:
34
34
  # to reduce copying, we clip into a pre-generated array of the right dtype
35
35
  new_data = np.empty_like(data, dtype=dtype)
36
- np.clip(data, new_min, new_max, out=new_data)
36
+ # allow "unsafe" casting here as we're explicitly clipping to the
37
+ # range of the new dtype - this was a default before numpy 1.25
38
+ np.clip(data, new_min, new_max, out=new_data, casting="unsafe")
37
39
  return new_data
38
40
 
39
41
 
vispy/scene/canvas.py CHANGED
@@ -493,11 +493,8 @@ class SceneCanvas(app.Canvas, Frozen):
493
493
  than triggering transform updates across the scene with every
494
494
  click.
495
495
  """
496
- try:
497
- self._scene.picking = True
496
+ with self._scene.set_picking():
498
497
  img = self.render(bgcolor=(0, 0, 0, 0), crop=crop)
499
- finally:
500
- self._scene.picking = False
501
498
  img = img.astype('int32') * [2**0, 2**8, 2**16, 2**24]
502
499
  id_ = img.sum(axis=2).astype('int32')
503
500
  return id_
vispy/scene/node.py CHANGED
@@ -5,6 +5,7 @@
5
5
  from __future__ import division
6
6
 
7
7
  import weakref
8
+ from contextlib import contextmanager
8
9
 
9
10
  from ..util.event import Event, EmitterGroup
10
11
  from ..visuals.transforms import (NullTransform, BaseTransform,
@@ -626,3 +627,18 @@ class Node(object):
626
627
  for c in self.children:
627
628
  c.picking = p
628
629
  self._picking = p
630
+
631
+ @contextmanager
632
+ def set_picking(self, *, picking=True):
633
+ """Context manager to temporarily set picking for this node and its children.
634
+
635
+ Note that this function will not alter the picking mode unless/until
636
+ the context manager is entered (using the `with` statement). Use
637
+ :py:attr:`~.picking` for setting the picking mode directly.
638
+ """
639
+ old_picking = self.picking
640
+ try:
641
+ self.picking = picking
642
+ yield self.picking
643
+ finally:
644
+ self.picking = old_picking
@@ -1,3 +1,5 @@
1
+ import pytest
2
+
1
3
  from vispy.scene import visuals, Node
2
4
  from vispy.scene.visuals import VisualNode
3
5
  import vispy.visuals
@@ -26,3 +28,114 @@ def test_visual_node_generation():
26
28
  vis_node = getattr(visuals, name[:-6])
27
29
  assert issubclass(vis_node, Node)
28
30
  assert issubclass(vis_node, obj)
31
+
32
+
33
+ def test_push_gl_state():
34
+ node = vispy.visuals.MeshVisual()
35
+ og_gl_state = node._vshare.gl_state.copy()
36
+ node.push_gl_state(blend=False, depth_test=False)
37
+ assert node._vshare.gl_state != og_gl_state
38
+ # preset is always set, unset kwargs should be absent
39
+ assert node._vshare.gl_state == {
40
+ "preset": None,
41
+ "blend": False,
42
+ "depth_test": False,
43
+ }
44
+ node.pop_gl_state()
45
+ assert node._vshare.gl_state == og_gl_state
46
+
47
+
48
+ def test_push_gl_state_update():
49
+ node = vispy.visuals.MeshVisual()
50
+ og_gl_state = node._vshare.gl_state.copy()
51
+ assert "blend" not in og_gl_state
52
+ assert node._vshare.gl_state["depth_test"]
53
+
54
+ node.push_gl_state_update(blend=False, depth_test=False)
55
+ assert node._vshare.gl_state != og_gl_state
56
+ assert not node._vshare.gl_state["blend"]
57
+ assert not node._vshare.gl_state["depth_test"]
58
+
59
+ node.pop_gl_state()
60
+ assert node._vshare.gl_state == og_gl_state
61
+
62
+
63
+ def test_pop_empty_gl_state():
64
+ node = vispy.visuals.MeshVisual()
65
+ assert node._prev_gl_state == []
66
+ og_gl_state = node._vshare.gl_state.copy()
67
+ node.pop_gl_state()
68
+ assert node._vshare.gl_state == og_gl_state
69
+
70
+
71
+ def test_update_gl_state():
72
+ node = vispy.visuals.MeshVisual()
73
+
74
+ og_gl_state = node._vshare.gl_state.copy()
75
+ assert og_gl_state
76
+ og_gl_state["blend"] = False
77
+
78
+ node.update_gl_state(blend=True)
79
+
80
+ # check that the state was updated
81
+ assert node._vshare.gl_state.pop("blend") != og_gl_state.pop("blend")
82
+ # the rest of the state should be unchanged
83
+ assert node._vshare.gl_state == og_gl_state
84
+
85
+
86
+ def test_update_gl_state_context_manager():
87
+ node = vispy.visuals.MeshVisual()
88
+
89
+ node.set_gl_state(blend=False)
90
+ og_gl_state = node._vshare.gl_state.copy()
91
+
92
+ with node.update_gl_state(blend=True):
93
+ # check that the state was updated
94
+ assert node._vshare.gl_state == {**og_gl_state, "blend": True}
95
+
96
+ # the update should be reverted once out of the context
97
+ assert node._vshare.gl_state == og_gl_state
98
+
99
+
100
+ def test_set_gl_state():
101
+ node = vispy.visuals.MeshVisual()
102
+
103
+ node.set_gl_state(blend=False, depth_test=False)
104
+ # preset is always set, unset kwargs should be absent
105
+ assert node._vshare.gl_state == {
106
+ "preset": None,
107
+ "blend": False,
108
+ "depth_test": False,
109
+ }
110
+
111
+ node.set_gl_state(blend=False)
112
+ # preset is always set, unset kwargs should be absent
113
+ assert node._vshare.gl_state == {"preset": None, "blend": False}
114
+
115
+
116
+ def test_set_gl_state_context_manager():
117
+ node = vispy.visuals.MeshVisual()
118
+
119
+ node.set_gl_state(blend=False)
120
+ og_gl_state = node._vshare.gl_state.copy()
121
+
122
+ with node.set_gl_state(blend=True):
123
+ # preset is always set, unset kwargs should be absent
124
+ assert node._vshare.gl_state == {"preset": None, "blend": True}
125
+
126
+ # the update should be reverted once out of the context
127
+ assert node._vshare.gl_state == og_gl_state
128
+
129
+
130
+ @pytest.mark.parametrize("enable_picking", [True, False])
131
+ def test_picking_context(enable_picking):
132
+ mesh = visuals.Mesh()
133
+ mesh.picking = not enable_picking
134
+
135
+ assert mesh.picking != enable_picking
136
+
137
+ with mesh.set_picking(picking=enable_picking) as p:
138
+ assert p == enable_picking
139
+ assert mesh.picking == enable_picking
140
+
141
+ assert mesh.picking != enable_picking
vispy/scene/visuals.py CHANGED
@@ -71,7 +71,10 @@ class VisualNode(Node):
71
71
  return
72
72
  self._picking = p
73
73
  self._picking_filter.enabled = p
74
- self.update_gl_state(blend=not p)
74
+ if p:
75
+ self.push_gl_state_update(blend=False)
76
+ else:
77
+ self.pop_gl_state()
75
78
 
76
79
  def _update_trsys(self, event):
77
80
  """Transform object(s) have changed for this Node; assign these to the
vispy/version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # file generated by setuptools_scm
2
2
  # don't change, don't track in version control
3
- __version__ = version = '0.13.0'
4
- __version_tuple__ = version_tuple = (0, 13, 0)
3
+ __version__ = version = '0.14.0'
4
+ __version_tuple__ = version_tuple = (0, 14, 0)
@@ -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 .mesh import TextureFilter, ShadingFilter, InstancedShadingFilter, WireframeFilter # noqa
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)
@@ -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)
@@ -7,7 +7,7 @@ import numpy as np
7
7
 
8
8
  from vispy.gloo import Texture2D, VertexBuffer
9
9
  from vispy.visuals.shaders import Function, Varying
10
- from vispy.visuals.filters import Filter
10
+ from vispy.visuals.filters import Filter, PrimitivePickingFilter
11
11
  from ...color import Color
12
12
 
13
13
 
@@ -177,6 +177,7 @@ void shade() {
177
177
  vec3 u = dFdx(v_pos_scene.xyz);
178
178
  vec3 v = dFdy(v_pos_scene.xyz);
179
179
  normal = cross(u, v);
180
+ } else {
180
181
  // Note(asnt): The normal calculated above always points in the
181
182
  // direction of the camera. Reintroduce the original orientation of the
182
183
  // face.
@@ -756,13 +757,40 @@ class WireframeFilter(Filter):
756
757
  bc = np.tile(bc[None, ...], (n_faces, 1, 1))
757
758
  self._bc.set_data(bc, convert=True)
758
759
 
759
- def on_mesh_data_updated(self, event):
760
+ def on_data_updated(self, event):
760
761
  self._update_data()
761
762
 
762
763
  def _attach(self, visual):
763
764
  super()._attach(visual)
764
- visual.events.data_updated.connect(self.on_mesh_data_updated)
765
+ visual.events.data_updated.connect(self.on_data_updated)
765
766
 
766
767
  def _detach(self, visual):
767
- visual.events.data_updated.disconnect(self.on_mesh_data_updated)
768
+ visual.events.data_updated.disconnect(self.on_data_updated)
768
769
  super()._detach(visual)
770
+
771
+
772
+ class FacePickingFilter(PrimitivePickingFilter):
773
+ """Filter used to color mesh faces by a picking ID.
774
+
775
+ Note that the ID color uses the alpha channel, so this may not be used
776
+ with blending enabled.
777
+
778
+ Examples
779
+ --------
780
+ :ref:`sphx_glr_gallery_scene_face_picking.py`
781
+ """
782
+
783
+ def _get_picking_ids(self):
784
+ if self._visual.mesh_data.is_empty():
785
+ n_faces = 0
786
+ else:
787
+ n_faces = len(self._visual.mesh_data.get_faces())
788
+
789
+ # we only care about the number of faces changing
790
+ if self._n_primitives == n_faces:
791
+ return None
792
+ self._n_primitives = n_faces
793
+
794
+ ids = np.arange(1, n_faces + 1, dtype=np.uint32)
795
+ ids = np.repeat(ids, 3, axis=0) # repeat id for each vertex
796
+ return ids
@@ -0,0 +1,70 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Copyright (c) Vispy Development Team. All Rights Reserved.
3
+ # Distributed under the (new) BSD License. See LICENSE.txt for more info.
4
+ import numpy as np
5
+
6
+ from vispy.geometry import create_plane
7
+ from vispy.scene.visuals import Markers, Mesh
8
+ from vispy.testing import requires_application, TestingCanvas
9
+ from vispy.visuals.filters import FacePickingFilter, MarkerPickingFilter
10
+
11
+
12
+ def test_empty_mesh_face_picking():
13
+ mesh = Mesh()
14
+ filter = FacePickingFilter()
15
+ mesh.attach(filter)
16
+ filter.enabled = True
17
+
18
+
19
+ @requires_application()
20
+ def test_mesh_face_picking():
21
+ vertices, faces, _ = create_plane(125, 125)
22
+ vertices = vertices["position"]
23
+ vertices[:, :2] += 125 / 2
24
+ mesh = Mesh(vertices=vertices, faces=faces)
25
+ filter = FacePickingFilter()
26
+ mesh.attach(filter)
27
+
28
+ with TestingCanvas(size=(125, 125)) as c:
29
+ view = c.central_widget.add_view()
30
+ view.add(mesh)
31
+ filter.enabled = True
32
+ mesh.update_gl_state(blend=False)
33
+ picking_render = c.render(bgcolor=(0, 0, 0, 0), alpha=True)
34
+
35
+ # unpack the IDs
36
+ ids = picking_render.view(np.uint32)
37
+ # the plane is made up of two triangles and nearly fills the view
38
+ # pick one point on each triangle
39
+ assert ids[125 // 2, 125 // 4] == 1
40
+ assert ids[125 // 2, 3 * 125 // 4] == 2
41
+
42
+
43
+ def test_empty_markers_picking():
44
+ markers = Markers()
45
+ filter = MarkerPickingFilter()
46
+ markers.attach(filter)
47
+ filter.enabled = True
48
+
49
+
50
+ @requires_application()
51
+ def test_markers_picking():
52
+ markers = Markers(
53
+ pos=np.array([[-0.5, -0.5], [0.5, 0.5]]),
54
+ size=5,
55
+ )
56
+ filter = MarkerPickingFilter()
57
+ markers.attach(filter)
58
+
59
+ with TestingCanvas(size=(125, 125)) as c:
60
+ view = c.central_widget.add_view(camera="panzoom")
61
+ view.camera.rect = (-1, -1, 2, 2)
62
+ view.add(markers)
63
+
64
+ filter.enabled = True
65
+ markers.update_gl_state(blend=False)
66
+ picking_render = c.render(bgcolor=(0, 0, 0, 0), alpha=True)
67
+ ids = picking_render.view(np.uint32)
68
+
69
+ assert ids[3 * 125 // 4, 125 // 4] == 1
70
+ assert ids[125 // 4, 3 * 125 // 4] == 2
@@ -2,6 +2,8 @@
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 functools import lru_cache
6
+
5
7
  import numpy as np
6
8
 
7
9
 
@@ -40,46 +42,49 @@ class DashAtlas(object):
40
42
  self._atlas[key] = [self._index / float(self._data.shape[0]), period]
41
43
  self._index += 1
42
44
  self._dirty = True
43
- # self.add_pattern(value)
44
-
45
- def make_pattern(self, pattern, caps=[1, 1]):
46
- """ """
47
-
48
- # A pattern is defined as on/off sequence of segments
49
- # It must be a multiple of 2
50
- if len(pattern) > 1 and len(pattern) % 2:
51
- pattern = [pattern[0] + pattern[-1]] + pattern[1:-1]
52
- P = np.array(pattern)
53
-
54
- # Period is the sum of all segment length
55
- period = np.cumsum(P)[-1]
56
-
57
- # Find all start and end of on-segment only
58
- C, c = [], 0
59
- for i in range(0, len(P) + 2, 2):
60
- a = max(0.0001, P[i % len(P)])
61
- b = max(0.0001, P[(i + 1) % len(P)])
62
- C.extend([c, c + a])
63
- c += a + b
64
- C = np.array(C)
65
-
66
- # Build pattern
45
+
46
+ def make_pattern(self, pattern, caps=(1, 1)):
67
47
  length = self._data.shape[1]
68
- Z = np.zeros((length, 4), dtype=np.float32)
69
- for i in np.arange(0, len(Z)):
70
- x = period * (i) / float(len(Z) - 1)
71
- index = np.argmin(abs(C - (x)))
72
- if index % 2 == 0:
73
- if x <= C[index]:
74
- dash_type = +1
75
- else:
76
- dash_type = 0
77
- dash_start, dash_end = C[index], C[index + 1]
48
+ return _make_pattern(length, pattern, caps)
49
+
50
+
51
+ @lru_cache(maxsize=32)
52
+ def _make_pattern(length, pattern, caps):
53
+ """Make a concrete dash pattern of a given length."""
54
+ # A pattern is defined as on/off sequence of segments
55
+ # It must be a multiple of 2
56
+ if len(pattern) > 1 and len(pattern) % 2:
57
+ pattern = [pattern[0] + pattern[-1]] + pattern[1:-1]
58
+ P = np.array(pattern)
59
+
60
+ # Period is the sum of all segment length
61
+ period = np.cumsum(P)[-1]
62
+
63
+ # Find all start and end of on-segment only
64
+ C, c = [], 0
65
+ for i in range(0, len(P) + 2, 2):
66
+ a = max(0.0001, P[i % len(P)])
67
+ b = max(0.0001, P[(i + 1) % len(P)])
68
+ C.extend([c, c + a])
69
+ c += a + b
70
+ C = np.array(C)
71
+
72
+ # Build pattern
73
+ Z = np.zeros((length, 4), dtype=np.float32)
74
+ for i in np.arange(0, len(Z)):
75
+ x = period * (i) / float(len(Z) - 1)
76
+ index = np.argmin(abs(C - (x)))
77
+ if index % 2 == 0:
78
+ if x <= C[index]:
79
+ dash_type = +1
80
+ else:
81
+ dash_type = 0
82
+ dash_start, dash_end = C[index], C[index + 1]
83
+ else:
84
+ if x > C[index]:
85
+ dash_type = -1
78
86
  else:
79
- if x > C[index]:
80
- dash_type = -1
81
- else:
82
- dash_type = 0
83
- dash_start, dash_end = C[index - 1], C[index]
84
- Z[i] = C[index], dash_type, dash_start, dash_end
85
- return Z, period
87
+ dash_type = 0
88
+ dash_start, dash_end = C[index - 1], C[index]
89
+ Z[i] = C[index], dash_type, dash_start, dash_end
90
+ return Z, period