mwxlib 1.0.0__py3-none-any.whl → 1.8.0__py3-none-any.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.
- mwx/__init__.py +6 -4
- mwx/bookshelf.py +144 -68
- mwx/controls.py +444 -378
- mwx/framework.py +567 -546
- mwx/graphman.py +745 -726
- mwx/images.py +16 -0
- mwx/matplot2.py +244 -235
- mwx/matplot2g.py +812 -751
- mwx/matplot2lg.py +218 -209
- mwx/mgplt.py +20 -22
- mwx/nutshell.py +1119 -1015
- mwx/plugins/ffmpeg_view.py +96 -90
- mwx/plugins/fft_view.py +13 -15
- mwx/plugins/frame_listview.py +68 -75
- mwx/plugins/line_profile.py +1 -1
- mwx/py/filling.py +13 -14
- mwx/testsuite.py +38 -0
- mwx/utilus.py +284 -219
- mwx/wxmon.py +43 -44
- mwx/wxpdb.py +83 -84
- mwx/wxwil.py +19 -18
- mwx/wxwit.py +38 -45
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/METADATA +12 -5
- mwxlib-1.8.0.dist-info/RECORD +28 -0
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/WHEEL +1 -1
- mwxlib-1.0.0.dist-info/LICENSE +0 -21
- mwxlib-1.0.0.dist-info/RECORD +0 -28
- {mwxlib-1.0.0.dist-info → mwxlib-1.8.0.dist-info}/top_level.txt +0 -0
mwx/matplot2g.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#! python3
|
|
2
2
|
"""mwxlib graph plot for images.
|
|
3
3
|
"""
|
|
4
|
-
import traceback
|
|
5
4
|
import wx
|
|
6
5
|
|
|
7
6
|
from matplotlib import cm
|
|
@@ -14,13 +13,18 @@ from scipy import ndimage as ndi
|
|
|
14
13
|
|
|
15
14
|
from . import framework as mwx
|
|
16
15
|
from .framework import Menu
|
|
17
|
-
## from .utilus import warn
|
|
18
16
|
from .utilus import funcall as _F
|
|
19
17
|
from .controls import Clipboard
|
|
20
18
|
from .matplot2 import MatplotPanel
|
|
21
19
|
from .matplot2 import NORMAL, DRAGGING, PAN, ZOOM, MARK, LINE, REGION
|
|
22
20
|
|
|
23
21
|
|
|
22
|
+
def _to_array(x):
|
|
23
|
+
if isinstance(x, (list, tuple)):
|
|
24
|
+
x = np.array(x)
|
|
25
|
+
return x
|
|
26
|
+
|
|
27
|
+
|
|
24
28
|
def _to_cvtype(src):
|
|
25
29
|
"""Convert the image to a type that can be applied to the cv2 function.
|
|
26
30
|
Note:
|
|
@@ -31,14 +35,31 @@ def _to_cvtype(src):
|
|
|
31
35
|
return src
|
|
32
36
|
|
|
33
37
|
|
|
34
|
-
def
|
|
35
|
-
if isinstance(
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
def _to_buffer(img):
|
|
39
|
+
if isinstance(img, Image.Image):
|
|
40
|
+
# return np.asarray(img) # ref
|
|
41
|
+
return np.array(img) # copy
|
|
42
|
+
|
|
43
|
+
if isinstance(img, wx.Bitmap): # bitmap to image
|
|
44
|
+
img = img.ConvertToImage()
|
|
45
|
+
|
|
46
|
+
if isinstance(img, wx.Image): # image to RGB array; RGB to grayscale
|
|
47
|
+
w, h = img.GetSize()
|
|
48
|
+
img = np.frombuffer(img.GetDataBuffer(), dtype='uint8').reshape(h, w, 3)
|
|
49
|
+
|
|
50
|
+
if not isinstance(img, np.ndarray):
|
|
51
|
+
raise ValueError("targets must be arrays or images.")
|
|
52
|
+
|
|
53
|
+
if img.ndim < 2:
|
|
54
|
+
raise ValueError("targets must be 2d arrays.")
|
|
55
|
+
|
|
56
|
+
if img.ndim > 2:
|
|
57
|
+
return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
|
|
58
|
+
return img
|
|
38
59
|
|
|
39
60
|
|
|
40
61
|
def _to_image(src, cutoff=0, threshold=None, binning=1):
|
|
41
|
-
"""Convert buffer to image <uint8
|
|
62
|
+
"""Convert buffer to image <uint8>.
|
|
42
63
|
|
|
43
64
|
>>> dst = (src-a) * 255 / (b-a)
|
|
44
65
|
|
|
@@ -49,29 +70,27 @@ def _to_image(src, cutoff=0, threshold=None, binning=1):
|
|
|
49
70
|
beta = -a * alpha
|
|
50
71
|
|
|
51
72
|
Args:
|
|
52
|
-
cutoff
|
|
53
|
-
threshold
|
|
54
|
-
binning
|
|
73
|
+
cutoff: cutoff score [%] to cut the lo/hi limits
|
|
74
|
+
threshold: limit bytes of image (to make matplotlib light)
|
|
75
|
+
binning: minimum binning number of src array
|
|
55
76
|
"""
|
|
56
|
-
if src.dtype in (np.complex64, np.complex128):
|
|
77
|
+
if src.dtype in (np.complex64, np.complex128): # maybe fft pattern
|
|
57
78
|
src = np.log(1 + abs(src))
|
|
58
79
|
|
|
59
|
-
bins = binning
|
|
60
80
|
if threshold:
|
|
61
|
-
##
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if
|
|
67
|
-
## src = src[::bins,::bins]
|
|
81
|
+
## Reduce the binning by itemsize before finally converting to <uint8>.
|
|
82
|
+
## Select the larger value between binning and threshold.
|
|
83
|
+
n = max(binning, int(np.sqrt(src.nbytes / threshold / src.itemsize)) + 1)
|
|
84
|
+
else:
|
|
85
|
+
n = binning
|
|
86
|
+
if n > 1:
|
|
68
87
|
src = _to_cvtype(src)
|
|
69
|
-
src = cv2.resize(src, None, fx=1/
|
|
88
|
+
src = cv2.resize(src, None, fx=1/n, fy=1/n, interpolation=cv2.INTER_AREA)
|
|
70
89
|
|
|
71
|
-
if src.dtype == np.uint8:
|
|
72
|
-
return
|
|
90
|
+
if src.dtype == np.uint8: # RGB or gray image <uint8>
|
|
91
|
+
return n, (0, 255), src
|
|
73
92
|
|
|
74
|
-
if hasattr(cutoff, '__iter__'):
|
|
93
|
+
if hasattr(cutoff, '__iter__'): # cutoff vlim: (vmin, vmax) is specified.
|
|
75
94
|
a, b = cutoff
|
|
76
95
|
elif cutoff > 0:
|
|
77
96
|
a = np.percentile(src, cutoff)
|
|
@@ -81,238 +100,238 @@ def _to_image(src, cutoff=0, threshold=None, binning=1):
|
|
|
81
100
|
b = src.max()
|
|
82
101
|
|
|
83
102
|
r = (255 / (b - a)) if a < b else 1
|
|
84
|
-
## img = cv2.convertScaleAbs(src, alpha=r, beta=-r*a)
|
|
85
|
-
img = np.uint8((src - a) * r)
|
|
103
|
+
## img = cv2.convertScaleAbs(src, alpha=r, beta=-r*a) # 負数は絶対値になるので以下に変更.
|
|
104
|
+
img = np.uint8((src - a) * r)
|
|
86
105
|
img[src < a] = 0
|
|
87
106
|
img[src > b] = 255
|
|
88
|
-
return
|
|
107
|
+
return n, (a, b), img
|
|
89
108
|
|
|
90
109
|
|
|
91
110
|
def _Property(name):
|
|
92
111
|
return property(
|
|
93
|
-
lambda self:
|
|
94
|
-
lambda self,v: setattr(self.parent, name, v),
|
|
95
|
-
lambda self:
|
|
112
|
+
lambda self: getattr(self.parent, name),
|
|
113
|
+
lambda self, v: setattr(self.parent, name, v),
|
|
114
|
+
lambda self: delattr(self.parent, name))
|
|
96
115
|
|
|
97
116
|
|
|
98
117
|
class AxesImagePhantom:
|
|
99
|
-
"""Phantom of frame facade
|
|
118
|
+
"""Phantom of frame facade.
|
|
100
119
|
|
|
101
120
|
Args:
|
|
102
|
-
buf
|
|
103
|
-
name
|
|
104
|
-
show
|
|
105
|
-
|
|
106
|
-
localunit : initial localunit
|
|
107
|
-
attributes : additional info:dict
|
|
121
|
+
buf: buffer
|
|
122
|
+
name: buffer name
|
|
123
|
+
show: show immediately when loaded
|
|
124
|
+
**kwargs: frame attributes
|
|
108
125
|
|
|
109
126
|
Note:
|
|
110
|
-
Due to the problem of performance,
|
|
111
|
-
the image pixel size could be reduced by binning.
|
|
127
|
+
Due to the problem of performance, the image pixel size could be reduced by binning.
|
|
112
128
|
"""
|
|
113
|
-
def __init__(self, parent, buf, name, show=True,
|
|
114
|
-
|
|
115
|
-
|
|
129
|
+
def __init__(self, parent, buf, name, show=True, **kwargs):
|
|
130
|
+
self.parent = parent
|
|
131
|
+
|
|
132
|
+
## Properties of the frame/image.
|
|
116
133
|
self.__name = name
|
|
117
|
-
self.
|
|
118
|
-
self.
|
|
119
|
-
self.
|
|
120
|
-
self.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if isinstance(buf, wx.Image): # image to RGB array
|
|
126
|
-
w, h = buf.GetSize()
|
|
127
|
-
buf = np.frombuffer(buf.GetDataBuffer(), dtype='uint8').reshape(h, w, 3)
|
|
128
|
-
if buf.ndim > 2:
|
|
129
|
-
buf = cv2.cvtColor(buf, cv2.COLOR_RGB2GRAY) # RGB to grayscale
|
|
130
|
-
self.__buf = buf
|
|
134
|
+
self.__attributes = kwargs
|
|
135
|
+
self.__pathname = kwargs.get('pathname')
|
|
136
|
+
self.__annotation = kwargs.get('annotation', '')
|
|
137
|
+
self.__localunit = kwargs.get('localunit')
|
|
138
|
+
self.__center = kwargs.get('center', [0, 0])
|
|
139
|
+
|
|
140
|
+
## Conditions for image loading.
|
|
141
|
+
self.__buf = _to_buffer(buf)
|
|
131
142
|
bins, vlim, img = _to_image(self.__buf,
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
cutoff=self.parent.score_percentile,
|
|
144
|
+
threshold=self.parent.nbytes_threshold,
|
|
145
|
+
)
|
|
135
146
|
self.__bins = bins
|
|
136
|
-
self.
|
|
147
|
+
self.__cuts = vlim
|
|
137
148
|
self.__art = parent.axes.imshow(img,
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
self.
|
|
145
|
-
|
|
149
|
+
cmap=cm.gray,
|
|
150
|
+
aspect='equal', # cf. aspect_ratio => xy_unit
|
|
151
|
+
interpolation='nearest',
|
|
152
|
+
visible=show,
|
|
153
|
+
picker=True,
|
|
154
|
+
)
|
|
155
|
+
self.aspect_ratio = 1
|
|
156
|
+
self.update_extent()
|
|
157
|
+
|
|
146
158
|
def __getattr__(self, attr):
|
|
147
159
|
return getattr(self.__art, attr)
|
|
148
|
-
|
|
160
|
+
|
|
149
161
|
def __eq__(self, x):
|
|
162
|
+
## Called in `on_pick` and `__contains__` to check objects in.
|
|
150
163
|
return x is self.__art
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
binning = property(lambda self: self.__bins)
|
|
157
|
-
|
|
158
|
-
image = property(
|
|
159
|
-
lambda self: self.__art.get_array(),
|
|
160
|
-
doc="A displayed image array<uint8>.")
|
|
161
|
-
|
|
162
|
-
vlim = property(
|
|
163
|
-
lambda self: self.__vlim,
|
|
164
|
-
doc="Image lower/upper values.")
|
|
165
|
-
|
|
166
|
-
clim = property(
|
|
167
|
-
lambda self: self.__art.get_clim(),
|
|
168
|
-
lambda self,v: self.__art.set_clim(v),
|
|
169
|
-
doc="Buffer lower/upper values.")
|
|
170
|
-
|
|
171
|
-
attributes = property(
|
|
172
|
-
lambda self: self.__attributes,
|
|
173
|
-
doc="Miscellaneous info about the frame/buffer.")
|
|
174
|
-
|
|
175
|
-
pathname = property(
|
|
176
|
-
lambda self: self.__attributes.get('pathname'),
|
|
177
|
-
lambda self,v: self.update_attributes({'pathname': v}),
|
|
178
|
-
doc="A fullpath of buffer, when bounds to file.")
|
|
179
|
-
|
|
180
|
-
annotation = property(
|
|
181
|
-
lambda self: self.__attributes.get('annotation', ''),
|
|
182
|
-
lambda self,v: self.update_attributes({'annotation': v}),
|
|
183
|
-
doc="Annotation of the buffer.")
|
|
184
|
-
|
|
185
|
-
def update_attributes(self, attr=None, **kwargs):
|
|
186
|
-
"""Update frame-specifc attributes.
|
|
187
|
-
The frame holds any attributes with dictionary
|
|
188
|
-
There are some keys which acts as the value setter when given,
|
|
189
|
-
`annotation` also shows the message with infobar
|
|
190
|
-
`localunit` also updates the frame.unit
|
|
191
|
-
"""
|
|
192
|
-
attr = attr or {}
|
|
193
|
-
attr.update(kwargs)
|
|
194
|
-
self.__attributes.update(attr)
|
|
195
|
-
|
|
196
|
-
if 'localunit' in attr:
|
|
197
|
-
self.unit = attr['localunit']
|
|
164
|
+
|
|
165
|
+
def update_attr(self, attr):
|
|
166
|
+
"""Update frame-specifc attributes."""
|
|
167
|
+
if not attr:
|
|
168
|
+
return
|
|
198
169
|
|
|
199
|
-
|
|
200
|
-
|
|
170
|
+
FLAG_ANNOTATION = 1
|
|
171
|
+
FLAG_UPDATE_EXTENT = 2
|
|
172
|
+
flag = 0
|
|
173
|
+
if 'pathname' in attr:
|
|
174
|
+
self.__pathname = attr['pathname']
|
|
175
|
+
flag |= FLAG_ANNOTATION
|
|
201
176
|
|
|
202
177
|
if 'annotation' in attr:
|
|
203
|
-
|
|
178
|
+
self.__annotation = attr['annotation']
|
|
204
179
|
if self.parent.frame is self:
|
|
205
|
-
self.parent.infobar.ShowMessage(
|
|
180
|
+
self.parent.infobar.ShowMessage(attr['annotation'])
|
|
181
|
+
flag |= FLAG_ANNOTATION
|
|
182
|
+
|
|
183
|
+
if 'center' in attr:
|
|
184
|
+
v = list(attr['center']) # for json format
|
|
185
|
+
if v != self.__center:
|
|
186
|
+
self.__center = v
|
|
187
|
+
flag |= FLAG_UPDATE_EXTENT
|
|
206
188
|
|
|
207
|
-
if
|
|
189
|
+
if 'localunit' in attr:
|
|
190
|
+
v = attr['localunit']
|
|
191
|
+
if v is None or np.isnan(v): # nan => None: undefined.
|
|
192
|
+
v = None
|
|
193
|
+
elif np.isinf(v):
|
|
194
|
+
raise ValueError("The unit value must not be inf")
|
|
195
|
+
elif v <= 0:
|
|
196
|
+
raise ValueError("The unit value must be greater than zero")
|
|
197
|
+
if v != self.__localunit:
|
|
198
|
+
self.__localunit = v
|
|
199
|
+
flag |= FLAG_UPDATE_EXTENT
|
|
200
|
+
|
|
201
|
+
self.__attributes.update(attr)
|
|
202
|
+
|
|
203
|
+
if flag & FLAG_UPDATE_EXTENT:
|
|
204
|
+
self.update_extent()
|
|
205
|
+
self.parent.canvas.draw_idle()
|
|
206
|
+
if flag:
|
|
208
207
|
self.parent.handler('frame_updated', self)
|
|
209
|
-
|
|
210
|
-
selector = _Property('Selector')
|
|
211
|
-
markers = _Property('Markers')
|
|
212
|
-
region = _Property('Region')
|
|
213
|
-
|
|
214
|
-
@name.setter
|
|
215
|
-
def name(self, v):
|
|
216
|
-
self.__name = v
|
|
217
|
-
self.parent.handler('frame_updated', self)
|
|
218
|
-
|
|
219
|
-
@property
|
|
220
|
-
def localunit(self):
|
|
221
|
-
return self.__localunit
|
|
222
|
-
|
|
223
|
-
@property
|
|
224
|
-
def unit(self):
|
|
225
|
-
"""Logical length per pixel arb.unit [u/pixel]."""
|
|
226
|
-
return self.__localunit or self.parent.unit
|
|
227
|
-
|
|
228
|
-
@unit.setter
|
|
229
|
-
def unit(self, v):
|
|
230
|
-
if v is None:
|
|
231
|
-
v = self.parent.unit
|
|
232
|
-
self.__localunit = None
|
|
233
|
-
elif np.isnan(v) or np.isinf(v):
|
|
234
|
-
raise ValueError("The unit value cannot be NaN or Inf")
|
|
235
|
-
elif v <= 0:
|
|
236
|
-
raise ValueError("The unit value must be greater than zero")
|
|
237
|
-
else:
|
|
238
|
-
if v == self.__localunit: # no effect when v is localunit
|
|
239
|
-
return
|
|
240
|
-
self.__localunit = v
|
|
241
|
-
self.__attributes['localunit'] = self.__localunit
|
|
242
|
-
self.update_extent()
|
|
243
|
-
self.parent.handler('frame_updated', self)
|
|
244
|
-
self.parent.canvas.draw_idle()
|
|
245
|
-
|
|
246
|
-
@unit.deleter
|
|
247
|
-
def unit(self):
|
|
248
|
-
self.unit = None
|
|
249
|
-
|
|
250
|
-
@property
|
|
251
|
-
def xy_unit(self):
|
|
252
|
-
u = self.__localunit or self.parent.unit
|
|
253
|
-
return (u, u * self.__aspect_ratio)
|
|
254
|
-
|
|
255
|
-
@property
|
|
256
|
-
def aspect_ratio(self):
|
|
257
|
-
"""Aspect ratio of logical unit."""
|
|
258
|
-
return self.__aspect_ratio
|
|
259
|
-
|
|
260
|
-
@aspect_ratio.setter
|
|
261
|
-
def aspect_ratio(self, v):
|
|
262
|
-
if v == self.__aspect_ratio:
|
|
263
|
-
return
|
|
264
|
-
self.__aspect_ratio = v or 1.0
|
|
265
|
-
self.update_extent()
|
|
266
|
-
self.parent.handler('frame_updated', self)
|
|
267
|
-
|
|
268
|
-
@property
|
|
269
|
-
def index(self):
|
|
270
|
-
"""Page number in the parent book."""
|
|
271
|
-
return self.parent.index(self)
|
|
272
|
-
|
|
208
|
+
|
|
273
209
|
def update_buffer(self, buf=None):
|
|
274
210
|
"""Update buffer and the image (internal use only)."""
|
|
275
211
|
if buf is not None:
|
|
276
|
-
self.__buf = buf
|
|
212
|
+
self.__buf = _to_buffer(buf)
|
|
213
|
+
|
|
277
214
|
bins, vlim, img = _to_image(self.__buf,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
215
|
+
cutoff = self.parent.score_percentile,
|
|
216
|
+
threshold = self.parent.nbytes_threshold,
|
|
217
|
+
)
|
|
281
218
|
self.__bins = bins
|
|
282
|
-
self.
|
|
219
|
+
self.__cuts = vlim
|
|
283
220
|
self.__art.set_array(img)
|
|
284
221
|
self.parent.handler('frame_modified', self)
|
|
285
|
-
|
|
222
|
+
|
|
286
223
|
def update_extent(self):
|
|
287
224
|
"""Update logical extent of the image (internal use only)."""
|
|
288
225
|
h, w = self.__buf.shape[:2]
|
|
289
226
|
ux, uy = self.xy_unit
|
|
290
227
|
w *= ux/2
|
|
291
228
|
h *= uy/2
|
|
292
|
-
self.
|
|
293
|
-
|
|
229
|
+
cx, cy = self.center
|
|
230
|
+
self.__art.set_extent((cx-w, cx+w, cy-h, cy+h))
|
|
231
|
+
|
|
232
|
+
artist = property(
|
|
233
|
+
lambda self: self.__art)
|
|
234
|
+
|
|
235
|
+
binning = property(
|
|
236
|
+
lambda self: self.__bins,
|
|
237
|
+
doc="Binning value resulting from the score_percentile.")
|
|
238
|
+
|
|
239
|
+
cuts = property(
|
|
240
|
+
lambda self: self.__cuts,
|
|
241
|
+
doc="Lower/Upper cutoff values of the buffer.")
|
|
242
|
+
|
|
243
|
+
image = property(
|
|
244
|
+
lambda self: self.__art.get_array(),
|
|
245
|
+
doc="Displayed image array<uint8>.")
|
|
246
|
+
|
|
247
|
+
clim = property(
|
|
248
|
+
lambda self: self.__art.get_clim(),
|
|
249
|
+
lambda self, v: self.__art.set_clim(v),
|
|
250
|
+
doc="Lower/Upper color limit values of the buffer.")
|
|
251
|
+
|
|
252
|
+
attributes = property(
|
|
253
|
+
lambda self: self.__attributes,
|
|
254
|
+
doc="Auxiliary info about the frame.")
|
|
255
|
+
|
|
256
|
+
pathname = property(
|
|
257
|
+
lambda self: self.__pathname,
|
|
258
|
+
lambda self, v: self.update_attr({'pathname': v}),
|
|
259
|
+
doc="Fullpath of the buffer, if bound to a file.")
|
|
260
|
+
|
|
261
|
+
annotation = property(
|
|
262
|
+
lambda self: self.__annotation,
|
|
263
|
+
lambda self, v: self.update_attr({'annotation': v}),
|
|
264
|
+
doc="Annotation of the buffer.")
|
|
265
|
+
|
|
266
|
+
center = property(
|
|
267
|
+
lambda self: self.__center,
|
|
268
|
+
lambda self, v: self.update_attr({'center': v}),
|
|
269
|
+
doc="Center coordinates of the frame in logical units.")
|
|
270
|
+
|
|
271
|
+
localunit = property(
|
|
272
|
+
lambda self: self.__localunit,
|
|
273
|
+
lambda self, v: self.update_attr({'localunit': v}),
|
|
274
|
+
doc="Logical length per pixel in arbitrary units [u/pix], or None if not assigned.")
|
|
275
|
+
|
|
276
|
+
unit = property(
|
|
277
|
+
lambda self: self.__localunit or self.parent.unit,
|
|
278
|
+
lambda self, v: self.update_attr({'localunit': v}),
|
|
279
|
+
doc="Logical length per pixel in arbitrary units [u/pix].")
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def xy_unit(self):
|
|
283
|
+
"""Logical length per pixel in arbitrary units [u/pix] for (X, Y) directions."""
|
|
284
|
+
u = self.unit
|
|
285
|
+
r = self.aspect_ratio
|
|
286
|
+
return (u, u) if r == 1 else (u, u * r)
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def name(self):
|
|
290
|
+
return self.__name
|
|
291
|
+
|
|
292
|
+
@name.setter
|
|
293
|
+
def name(self, v):
|
|
294
|
+
self.__name = v
|
|
295
|
+
self.parent.handler('frame_updated', self)
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def index(self):
|
|
299
|
+
"""Page number in the parent view."""
|
|
300
|
+
return self.parent.index(self)
|
|
301
|
+
|
|
294
302
|
@property
|
|
295
303
|
def roi(self):
|
|
296
304
|
"""Current buffer ROI (region of interest)."""
|
|
297
|
-
if self.parent.
|
|
298
|
-
nx, ny = self.xytopixel(
|
|
299
|
-
sx = slice(max(0,nx[0]), nx[1])
|
|
300
|
-
sy = slice(max(0,ny[1]), ny[0])
|
|
301
|
-
return self.__buf[sy,sx]
|
|
302
|
-
return
|
|
303
|
-
|
|
305
|
+
if self.parent.region.size:
|
|
306
|
+
nx, ny = self.xytopixel(self.region)
|
|
307
|
+
sx = slice(max(0, nx[0]), nx[1]) # nx slice
|
|
308
|
+
sy = slice(max(0, ny[1]), ny[0]) # ny slice 反転 (降順)
|
|
309
|
+
return self.__buf[sy, sx]
|
|
310
|
+
return None
|
|
311
|
+
|
|
304
312
|
@roi.setter
|
|
305
313
|
def roi(self, v):
|
|
306
|
-
self.
|
|
314
|
+
if not self.parent.region.size:
|
|
315
|
+
raise ValueError("region is not selected.")
|
|
316
|
+
self.roi[:] = v # cannot broadcast input array into different shape
|
|
307
317
|
self.update_buffer()
|
|
308
|
-
|
|
318
|
+
|
|
319
|
+
@property
|
|
320
|
+
def roi_or_buffer(self):
|
|
321
|
+
return self.roi if self.parent.region.size else self.buffer
|
|
322
|
+
|
|
323
|
+
@property
|
|
324
|
+
def buffer(self):
|
|
325
|
+
return self.__buf
|
|
326
|
+
|
|
309
327
|
@buffer.setter
|
|
310
328
|
def buffer(self, v):
|
|
311
329
|
self.update_buffer(v)
|
|
312
|
-
|
|
330
|
+
self.update_extent()
|
|
331
|
+
|
|
313
332
|
def xytoc(self, x, y=None, nearest=True):
|
|
314
333
|
"""Convert xydata (x,y) -> data[(x,y)] value of neaerst pixel.
|
|
315
|
-
|
|
334
|
+
If `nearest` is False, the return value is interpolated with spline.
|
|
316
335
|
"""
|
|
317
336
|
h, w = self.__buf.shape[:2]
|
|
318
337
|
nx, ny = self.xytopixel(x, y, cast=nearest)
|
|
@@ -320,42 +339,74 @@ class AxesImagePhantom:
|
|
|
320
339
|
if np.any(nx<0) or np.any(nx>=w) or np.any(ny<0) or np.any(ny>=h):
|
|
321
340
|
return
|
|
322
341
|
if nearest:
|
|
323
|
-
return self.__buf[ny, nx]
|
|
324
|
-
return ndi.map_coordinates(self.__buf, np.vstack((ny, nx)))
|
|
325
|
-
|
|
342
|
+
return self.__buf[ny, nx] # nearest value
|
|
343
|
+
return ndi.map_coordinates(self.__buf, np.vstack((ny, nx))) # spline value
|
|
344
|
+
|
|
326
345
|
def xytopixel(self, x, y=None, cast=True):
|
|
327
346
|
"""Convert xydata (x,y) -> [nx,ny] pixel.
|
|
328
|
-
If cast,
|
|
347
|
+
If `cast` is True, the return value will be integer pixel values.
|
|
329
348
|
"""
|
|
330
349
|
def _cast(n):
|
|
331
350
|
return np.int32(np.floor(np.round(n, 1)))
|
|
332
351
|
if y is None:
|
|
333
|
-
|
|
352
|
+
# warn("Setting xy data with single tuple.", DeprecationWarning)
|
|
334
353
|
x, y = x
|
|
335
354
|
x, y = _to_array(x), _to_array(y)
|
|
336
355
|
l,r,b,t = self.__art.get_extent()
|
|
337
356
|
ux, uy = self.xy_unit
|
|
338
357
|
nx = (x - l) / ux
|
|
339
|
-
ny = (t - y) / uy
|
|
358
|
+
ny = (t - y) / uy # Y ピクセルインデクスは座標と逆
|
|
340
359
|
if cast:
|
|
341
|
-
return (_cast(nx), _cast(ny))
|
|
342
|
-
return (nx-0.5, ny-0.5)
|
|
343
|
-
|
|
360
|
+
return np.array((_cast(nx), _cast(ny)))
|
|
361
|
+
return np.array((nx-0.5, ny-0.5))
|
|
362
|
+
|
|
344
363
|
def xyfrompixel(self, nx, ny=None):
|
|
345
|
-
"""Convert pixel [nx,ny] -> (x,y) xydata (float number).
|
|
364
|
+
"""Convert pixel [nx,ny] -> (x,y) xydata (float number).
|
|
365
|
+
"""
|
|
346
366
|
if ny is None:
|
|
347
|
-
|
|
367
|
+
# warn("Setting xy data with single tuple.", DeprecationWarning)
|
|
348
368
|
nx, ny = nx
|
|
349
369
|
nx, ny = _to_array(nx), _to_array(ny)
|
|
350
370
|
l,r,b,t = self.__art.get_extent()
|
|
351
371
|
ux, uy = self.xy_unit
|
|
352
372
|
x = l + (nx + 0.5) * ux
|
|
353
|
-
y = t - (ny + 0.5) * uy
|
|
354
|
-
return (x, y)
|
|
373
|
+
y = t - (ny + 0.5) * uy # Y ピクセルインデクスは座標と逆
|
|
374
|
+
return np.array((x, y))
|
|
375
|
+
|
|
376
|
+
selector = _Property('selector')
|
|
377
|
+
markers = _Property('markers')
|
|
378
|
+
region = _Property('region')
|
|
379
|
+
|
|
380
|
+
@property
|
|
381
|
+
def selector_pix(self):
|
|
382
|
+
"""Selected points array [[x], [y]] in pixels."""
|
|
383
|
+
return self.xytopixel(self.selector)
|
|
384
|
+
|
|
385
|
+
@selector_pix.setter
|
|
386
|
+
def selector_pix(self, v):
|
|
387
|
+
self.selector = self.xyfrompixel(v)
|
|
388
|
+
|
|
389
|
+
@property
|
|
390
|
+
def markers_pix(self):
|
|
391
|
+
"""Marked points data array [[x], [y]] in pixels."""
|
|
392
|
+
return self.xytopixel(self.markers)
|
|
393
|
+
|
|
394
|
+
@markers_pix.setter
|
|
395
|
+
def markers_pix(self, v):
|
|
396
|
+
self.markers = self.xyfrompixel(v)
|
|
397
|
+
|
|
398
|
+
@property
|
|
399
|
+
def region_pix(self):
|
|
400
|
+
"""Cropped points data array [[l,r], [b,t]] in pixels."""
|
|
401
|
+
return self.xytopixel(self.region)
|
|
402
|
+
|
|
403
|
+
@region_pix.setter
|
|
404
|
+
def region_pix(self, v):
|
|
405
|
+
self.region = self.xyfrompixel(v)
|
|
355
406
|
|
|
356
407
|
|
|
357
408
|
class GraphPlot(MatplotPanel):
|
|
358
|
-
"""Graph panel for 2D graph
|
|
409
|
+
"""Graph panel for 2D graph.
|
|
359
410
|
"""
|
|
360
411
|
def __init__(self, *args, **kwargs):
|
|
361
412
|
MatplotPanel.__init__(self, *args, **kwargs)
|
|
@@ -363,39 +414,41 @@ class GraphPlot(MatplotPanel):
|
|
|
363
414
|
def _draw(evt):
|
|
364
415
|
self.canvas.draw_idle()
|
|
365
416
|
|
|
366
|
-
self.handler.update({
|
|
417
|
+
self.handler.update({ # DNA<GraphPlot>
|
|
367
418
|
None : {
|
|
368
|
-
'frame_shown' : [
|
|
369
|
-
'frame_hidden' : [
|
|
370
|
-
'frame_loaded' : [
|
|
371
|
-
'frame_removed' : [
|
|
372
|
-
'frame_selected' : [
|
|
373
|
-
'frame_deselected' : [
|
|
374
|
-
'frame_modified' : [
|
|
375
|
-
'frame_updated' : [
|
|
376
|
-
'frame_cmapped' : [
|
|
377
|
-
'line_draw' : [
|
|
378
|
-
'line_drawn' : [
|
|
379
|
-
'line_move' : [
|
|
380
|
-
'line_moved' : [
|
|
381
|
-
'line_removed' : [
|
|
382
|
-
'mark_draw' : [
|
|
383
|
-
'mark_drawn' : [
|
|
384
|
-
'mark_removed' : [
|
|
385
|
-
'region_draw' : [
|
|
386
|
-
'region_drawn' : [
|
|
387
|
-
'region_removed' : [
|
|
388
|
-
'M-up pressed' : [
|
|
389
|
-
'M-down pressed' : [
|
|
390
|
-
'pageup pressed' : [
|
|
391
|
-
'pagedown pressed' : [
|
|
392
|
-
|
|
393
|
-
'
|
|
394
|
-
'
|
|
395
|
-
'C-
|
|
396
|
-
|
|
397
|
-
'C-
|
|
398
|
-
|
|
419
|
+
'frame_shown' : [None, ], # show
|
|
420
|
+
'frame_hidden' : [None, ], # show
|
|
421
|
+
'frame_loaded' : [None, ], # load
|
|
422
|
+
'frame_removed' : [None, ], # del[] ! event arg is indices, not frames.
|
|
423
|
+
'frame_selected' : [None, ], # = focus_set
|
|
424
|
+
'frame_deselected' : [None, ], # = focus_kill
|
|
425
|
+
'frame_modified' : [None, _F(self.writeln)], # set[],load,roi => update_buffer
|
|
426
|
+
'frame_updated' : [None, _F(self.writeln)], # unit,name,ratio => update_extent
|
|
427
|
+
'frame_cmapped' : [None, _F(self.writeln)], # cmap
|
|
428
|
+
'line_draw' : [None, ],
|
|
429
|
+
'line_drawn' : [None, _draw],
|
|
430
|
+
'line_move' : [None, ],
|
|
431
|
+
'line_moved' : [None, _draw],
|
|
432
|
+
'line_removed' : [None, _draw],
|
|
433
|
+
'mark_draw' : [None, ],
|
|
434
|
+
'mark_drawn' : [None, _draw],
|
|
435
|
+
'mark_removed' : [None, _draw],
|
|
436
|
+
'region_draw' : [None, ],
|
|
437
|
+
'region_drawn' : [None, _draw],
|
|
438
|
+
'region_removed' : [None, _draw],
|
|
439
|
+
'M-up pressed' : [None, self.OnPageUp],
|
|
440
|
+
'M-down pressed' : [None, self.OnPageDown],
|
|
441
|
+
'pageup pressed' : [None, self.OnPageUp],
|
|
442
|
+
'pagedown pressed' : [None, self.OnPageDown],
|
|
443
|
+
'home pressed' : [None, _F(self.select, index=0)],
|
|
444
|
+
'end pressed' : [None, _F(self.select, index=-1)],
|
|
445
|
+
'M-a pressed' : [None, _F(self.fit_to_canvas)],
|
|
446
|
+
'C-a pressed' : [None, _F(self.fit_to_axes)],
|
|
447
|
+
'C-i pressed' : [None, _F(self.invert_cmap)],
|
|
448
|
+
'C-k pressed' : [None, _F(self.kill_buffer)],
|
|
449
|
+
'C-S-k pressed' : [None, _F(self.kill_all_buffers)],
|
|
450
|
+
'C-c pressed' : [None, _F(self.write_buffer_to_clipboard)],
|
|
451
|
+
'C-v pressed' : [None, _F(self.read_buffer_from_clipboard)],
|
|
399
452
|
},
|
|
400
453
|
NORMAL : {
|
|
401
454
|
'image_picked' : (NORMAL, self.OnImagePicked),
|
|
@@ -502,6 +555,7 @@ class GraphPlot(MatplotPanel):
|
|
|
502
555
|
'delete pressed' : (NORMAL, self.OnRegionRemove),
|
|
503
556
|
'space pressed' : (PAN, self.OnPanBegin),
|
|
504
557
|
'ctrl pressed' : (PAN, self.OnPanBegin),
|
|
558
|
+
'c pressed' : (REGION, self.OnRegionCenter),
|
|
505
559
|
'z pressed' : (ZOOM, self.OnZoomBegin),
|
|
506
560
|
'*Ldrag begin' : (REGION+DRAGGING, self.OnRegionDragBegin),
|
|
507
561
|
'Rbutton pressed' : (REGION, self.on_menu_lock),
|
|
@@ -532,49 +586,48 @@ class GraphPlot(MatplotPanel):
|
|
|
532
586
|
(),
|
|
533
587
|
(mwx.ID_(500), "&Invert Color", "Invert colormap", wx.ITEM_CHECK,
|
|
534
588
|
lambda v: self.invert_cmap(),
|
|
535
|
-
lambda v: v.Check(self.
|
|
589
|
+
lambda v: v.Check(self.get_cmapstr()[-2:] == "_r")),
|
|
536
590
|
(),
|
|
537
591
|
(wx.ID_CLOSE, "&Kill buffer\t(C-k)", "Kill buffer", _Icon(wx.ART_DELETE),
|
|
538
592
|
lambda v: self.kill_buffer(),
|
|
539
593
|
lambda v: v.Enable(self.frame is not None)),
|
|
540
594
|
|
|
541
595
|
(wx.ID_CLOSE_ALL, "&Kill all buffer\t(C-S-k)", "Kill buffers", _Icon(wx.ART_DELETE),
|
|
542
|
-
lambda v: self.
|
|
596
|
+
lambda v: self.kill_all_buffers(),
|
|
543
597
|
lambda v: v.Enable(self.frame is not None)),
|
|
544
598
|
]
|
|
545
599
|
|
|
546
|
-
## modeline menu:
|
|
600
|
+
## modeline menu: バッファリストメニューを追加する.
|
|
547
601
|
def _menu(j, s):
|
|
548
602
|
return (j, s, s, wx.ITEM_CHECK,
|
|
549
603
|
lambda v: self.select(s),
|
|
550
604
|
lambda v: v.Check(self.frame is not None and self.frame.name == s))
|
|
551
605
|
|
|
552
|
-
self.modeline.Bind(wx.EVT_CONTEXT_MENU,
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
self.modeline.Show(1)
|
|
557
|
-
self.Layout()
|
|
606
|
+
self.modeline.Bind(wx.EVT_CONTEXT_MENU,
|
|
607
|
+
lambda v: Menu.Popup(self, (_menu(j, art.name)
|
|
608
|
+
for j, art in enumerate(self.__Arts))))
|
|
558
609
|
|
|
610
|
+
self.modeline.Show()
|
|
559
611
|
self.writeln()
|
|
560
|
-
|
|
612
|
+
self.Layout()
|
|
613
|
+
|
|
561
614
|
def clear(self):
|
|
562
615
|
MatplotPanel.clear(self)
|
|
563
616
|
|
|
564
617
|
self.__Arts = []
|
|
565
618
|
self.__index = None
|
|
566
619
|
|
|
567
|
-
## cf. self.figure.dpi =
|
|
620
|
+
## cf. self.figure.dpi = 80 dpi (0.3175 mm/pix)
|
|
568
621
|
self.__unit = 1.0
|
|
569
622
|
|
|
570
|
-
|
|
623
|
+
# <matplotlib.lines.Line2D>
|
|
571
624
|
(self.marked,) = self.axes.plot([], [], "r+", ms=8, mew=1,
|
|
572
625
|
picker=8)
|
|
573
626
|
self.__marksel = []
|
|
574
627
|
self.__markarts = []
|
|
575
628
|
self.marked.set_clip_on(False)
|
|
576
629
|
|
|
577
|
-
|
|
630
|
+
# <matplotlib.lines.Line2D>
|
|
578
631
|
(self.rected,) = self.axes.plot([], [], "r+--", ms=4, lw=3/4,
|
|
579
632
|
picker=4, alpha=0.8)
|
|
580
633
|
self.__rectsel = []
|
|
@@ -584,7 +637,7 @@ class GraphPlot(MatplotPanel):
|
|
|
584
637
|
self.__isPicked = None
|
|
585
638
|
self.selected.set_picker(8)
|
|
586
639
|
self.selected.set_clip_on(False)
|
|
587
|
-
|
|
640
|
+
|
|
588
641
|
def get_uniqname(self, name):
|
|
589
642
|
base = name = name or "*temp*"
|
|
590
643
|
i = 1
|
|
@@ -593,41 +646,35 @@ class GraphPlot(MatplotPanel):
|
|
|
593
646
|
i += 1
|
|
594
647
|
name = "{}<{:d}>".format(base, i)
|
|
595
648
|
return name
|
|
596
|
-
|
|
649
|
+
|
|
597
650
|
def load(self, buf, name=None, pos=None, show=True, **kwargs):
|
|
598
651
|
"""Load a buffer with a name.
|
|
599
652
|
|
|
600
653
|
Args:
|
|
601
|
-
buf
|
|
602
|
-
name
|
|
603
|
-
pos
|
|
604
|
-
show
|
|
605
|
-
|
|
654
|
+
buf: buffer array.
|
|
655
|
+
name: buffer name (default to *temp*).
|
|
656
|
+
pos: Insertion position in the frame list.
|
|
657
|
+
show: Show immediately when loaded.
|
|
606
658
|
**kwargs: frame attributes.
|
|
607
|
-
|
|
608
|
-
- localunit : localunit
|
|
609
|
-
- aspect : aspect ratio
|
|
610
|
-
- pathname : full path of the buffer file
|
|
611
659
|
"""
|
|
612
|
-
|
|
613
|
-
return
|
|
660
|
+
assert buf is not None, "Load buffer must be an array or path:str (not None)"
|
|
614
661
|
|
|
615
662
|
if isinstance(buf, str):
|
|
616
663
|
buf = Image.open(buf)
|
|
617
664
|
|
|
618
|
-
|
|
665
|
+
path = kwargs.get('pathname')
|
|
619
666
|
paths = [art.pathname for art in self.__Arts]
|
|
620
667
|
names = [art.name for art in self.__Arts]
|
|
621
668
|
j = -1
|
|
622
|
-
if
|
|
623
|
-
if
|
|
624
|
-
j = paths.index(
|
|
669
|
+
if path:
|
|
670
|
+
if path in paths:
|
|
671
|
+
j = paths.index(path) # existing path
|
|
625
672
|
elif name in names:
|
|
626
|
-
j = names.index(name)
|
|
673
|
+
j = names.index(name) # existing frame
|
|
627
674
|
if j != -1:
|
|
628
675
|
art = self.__Arts[j]
|
|
629
|
-
art.update_buffer(buf)
|
|
630
|
-
art.
|
|
676
|
+
art.update_buffer(buf) # => [frame_modified]
|
|
677
|
+
art.update_attr(kwargs) # => [frame_updated] localunit => [canvas_draw]
|
|
631
678
|
art.update_extent()
|
|
632
679
|
if show:
|
|
633
680
|
self.select(j)
|
|
@@ -635,31 +682,33 @@ class GraphPlot(MatplotPanel):
|
|
|
635
682
|
|
|
636
683
|
name = self.get_uniqname(name)
|
|
637
684
|
|
|
638
|
-
## The first load of axes.imshow (=> self.axes.axis 表示を更新する)
|
|
685
|
+
## The first load of axes.imshow (=> self.axes.axis 表示を更新する).
|
|
639
686
|
art = AxesImagePhantom(self, buf, name, show, **kwargs)
|
|
640
687
|
|
|
641
688
|
j = len(self) if pos is None else pos
|
|
642
689
|
self.__Arts.insert(j, art)
|
|
643
690
|
self.handler('frame_loaded', art)
|
|
644
691
|
if show:
|
|
645
|
-
u = self.frame and self.frame.unit
|
|
692
|
+
u = self.frame and self.frame.unit # current frame unit
|
|
646
693
|
self.select(j)
|
|
647
|
-
## Update view if the unit length is different from before selection
|
|
694
|
+
## Update view if the unit length is different from before selection.
|
|
648
695
|
if u != art.unit:
|
|
649
696
|
self.axes.axis(art.get_extent())
|
|
650
697
|
return art
|
|
651
|
-
|
|
652
|
-
def select(self,
|
|
653
|
-
if isinstance(
|
|
654
|
-
j = self.index(
|
|
698
|
+
|
|
699
|
+
def select(self, index):
|
|
700
|
+
if isinstance(index, (str, AxesImagePhantom)):
|
|
701
|
+
j = self.index(index)
|
|
702
|
+
else:
|
|
703
|
+
j = index
|
|
655
704
|
|
|
656
|
-
for art in self.__Arts:
|
|
705
|
+
for art in self.__Arts: # Hide all frames
|
|
657
706
|
art.set_visible(0)
|
|
658
707
|
|
|
659
708
|
if j != self.__index and self.__index is not None:
|
|
660
709
|
self.handler('frame_hidden', self.frame)
|
|
661
710
|
|
|
662
|
-
if j is not None:
|
|
711
|
+
if j is not None and self.__Arts:
|
|
663
712
|
art = self.__Arts[j]
|
|
664
713
|
art.set_visible(1)
|
|
665
714
|
self.__index = j % len(self)
|
|
@@ -669,10 +718,13 @@ class GraphPlot(MatplotPanel):
|
|
|
669
718
|
|
|
670
719
|
self.draw()
|
|
671
720
|
self.writeln()
|
|
672
|
-
self.trace_point(*self.
|
|
673
|
-
|
|
721
|
+
self.trace_point(*self.selector)
|
|
674
722
|
return self.frame
|
|
675
|
-
|
|
723
|
+
|
|
724
|
+
def __iter__(self):
|
|
725
|
+
for art in self.__Arts:
|
|
726
|
+
yield art.buffer
|
|
727
|
+
|
|
676
728
|
def __getitem__(self, j):
|
|
677
729
|
if isinstance(j, str):
|
|
678
730
|
j = self.index(j)
|
|
@@ -680,27 +732,23 @@ class GraphPlot(MatplotPanel):
|
|
|
680
732
|
buffers = [art.buffer for art in self.__Arts]
|
|
681
733
|
if hasattr(j, '__iter__'):
|
|
682
734
|
return [buffers[i] for i in j]
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
735
|
+
return buffers[j] # j can also be slicing
|
|
736
|
+
|
|
686
737
|
def __setitem__(self, j, v):
|
|
738
|
+
if v is None:
|
|
739
|
+
raise ValueError("values must be buffers, not NoneType")
|
|
740
|
+
|
|
687
741
|
if isinstance(j, str):
|
|
688
|
-
|
|
689
|
-
j = self.index(j) # overwrite buffer
|
|
690
|
-
except ValueError:
|
|
691
|
-
return self.load(v, name=j) # new buffer
|
|
742
|
+
return self.load(v, name=j) # update buffer or new buffer
|
|
692
743
|
|
|
693
|
-
if
|
|
694
|
-
raise ValueError("attempt to assign buffers
|
|
744
|
+
if isinstance(j, slice) or hasattr(j, '__iter__'):
|
|
745
|
+
raise ValueError("attempt to assign buffers via slicing or iterator")
|
|
695
746
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
art.update_extent()
|
|
702
|
-
self.select(j)
|
|
703
|
-
|
|
747
|
+
art = self.__Arts[j]
|
|
748
|
+
art.update_buffer(v) # update buffer
|
|
749
|
+
art.update_extent()
|
|
750
|
+
self.select(j)
|
|
751
|
+
|
|
704
752
|
def __delitem__(self, j):
|
|
705
753
|
if isinstance(j, str):
|
|
706
754
|
j = self.index(j)
|
|
@@ -713,7 +761,7 @@ class GraphPlot(MatplotPanel):
|
|
|
713
761
|
arts = [self.__Arts[j]]
|
|
714
762
|
|
|
715
763
|
if arts:
|
|
716
|
-
indices = [art.index for art in arts]
|
|
764
|
+
indices = [art.index for art in arts] # frames to be removed
|
|
717
765
|
for art in arts:
|
|
718
766
|
art.remove()
|
|
719
767
|
self.__Arts.remove(art)
|
|
@@ -724,105 +772,121 @@ class GraphPlot(MatplotPanel):
|
|
|
724
772
|
n = len(self)
|
|
725
773
|
self.__index = None if n==0 else j if j<n else n-1
|
|
726
774
|
self.select(self.__index)
|
|
727
|
-
|
|
728
|
-
## __len__ は bool() でも呼び出されるため,オブジェクト判定で偽を返すことがある (PY2)
|
|
729
|
-
## __nonzero__ : bool() を追加しておく必要がある (PY2)
|
|
730
|
-
|
|
775
|
+
|
|
776
|
+
## __len__ は bool() でも呼び出されるため,オブジェクト判定で偽を返すことがある (PY2).
|
|
777
|
+
## __nonzero__ : bool() を追加しておく必要がある (PY2).
|
|
778
|
+
|
|
731
779
|
def __len__(self):
|
|
732
780
|
return len(self.__Arts)
|
|
733
|
-
|
|
781
|
+
|
|
734
782
|
def __nonzero__(self):
|
|
735
783
|
return True
|
|
736
|
-
|
|
784
|
+
|
|
737
785
|
def __bool__(self):
|
|
738
786
|
return True
|
|
739
|
-
|
|
787
|
+
|
|
740
788
|
def __contains__(self, j):
|
|
741
789
|
if isinstance(j, str):
|
|
742
790
|
return j in (art.name for art in self.__Arts)
|
|
791
|
+
elif isinstance(j, np.ndarray):
|
|
792
|
+
return any(j is art.buffer for art in self.__Arts)
|
|
743
793
|
else:
|
|
744
794
|
return j in self.__Arts
|
|
745
|
-
|
|
795
|
+
|
|
746
796
|
def index(self, j):
|
|
747
797
|
if isinstance(j, str):
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
798
|
+
return next(i for i, art in enumerate(self.__Arts) if j == art.name)
|
|
799
|
+
elif isinstance(j, np.ndarray):
|
|
800
|
+
return next(i for i, art in enumerate(self.__Arts) if j is art.buffer)
|
|
801
|
+
else:
|
|
802
|
+
return self.__Arts.index(j) # j:frame -> int
|
|
803
|
+
|
|
752
804
|
def find_frame(self, j):
|
|
753
805
|
if isinstance(j, str):
|
|
754
|
-
return next((art for art in self.__Arts if art.name
|
|
755
|
-
|
|
756
|
-
|
|
806
|
+
return next((art for art in self.__Arts if j == art.name), None)
|
|
807
|
+
elif isinstance(j, np.ndarray):
|
|
808
|
+
return next((art for art in self.__Arts if j is art.buffer), None)
|
|
809
|
+
else:
|
|
810
|
+
return self.__Arts[j] # j:int -> frame
|
|
811
|
+
|
|
812
|
+
def get_all_frames(self, j=None):
|
|
813
|
+
"""List of arts <matplotlib.image.AxesImage>."""
|
|
814
|
+
if j is None:
|
|
815
|
+
yield from self.__Arts
|
|
816
|
+
elif isinstance(j, str):
|
|
817
|
+
yield from (art for art in self.__Arts if j in art.name)
|
|
818
|
+
elif isinstance(j, np.ndarray):
|
|
819
|
+
yield from (art for art in self.__Arts if j is art.buffer)
|
|
820
|
+
|
|
757
821
|
## --------------------------------
|
|
758
|
-
## Property of frame / drawer
|
|
822
|
+
## Property of frame / drawer.
|
|
759
823
|
## --------------------------------
|
|
760
|
-
|
|
761
|
-
|
|
824
|
+
|
|
825
|
+
## Image bytes max for loading matplotlib (with wxAgg backend).
|
|
762
826
|
nbytes_threshold = 24e6
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
score_percentile = 0.
|
|
766
|
-
|
|
827
|
+
|
|
828
|
+
## Image cutoff score percentiles.
|
|
829
|
+
score_percentile = 0.005
|
|
830
|
+
|
|
767
831
|
@property
|
|
768
832
|
def all_frames(self):
|
|
769
833
|
"""List of arts <matplotlib.image.AxesImage>."""
|
|
770
834
|
return self.__Arts
|
|
771
|
-
|
|
835
|
+
|
|
772
836
|
@property
|
|
773
837
|
def frame(self):
|
|
774
838
|
"""Current art <matplotlib.image.AxesImage>."""
|
|
775
839
|
if self.__Arts and self.__index is not None:
|
|
776
840
|
return self.__Arts[self.__index]
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
841
|
+
|
|
842
|
+
@property
|
|
843
|
+
def buffer(self):
|
|
844
|
+
"""Current buffer array."""
|
|
845
|
+
if self.frame:
|
|
846
|
+
return self.frame.buffer
|
|
847
|
+
|
|
848
|
+
@buffer.setter
|
|
849
|
+
def buffer(self, v):
|
|
850
|
+
if self.frame:
|
|
851
|
+
self.__setitem__(self.__index, v)
|
|
852
|
+
else:
|
|
853
|
+
self.load(v)
|
|
854
|
+
|
|
789
855
|
@property
|
|
790
856
|
def unit(self):
|
|
791
|
-
"""Logical length per pixel
|
|
857
|
+
"""Logical length per pixel in arbitrary units [u/pix]."""
|
|
792
858
|
return self.__unit
|
|
793
|
-
|
|
859
|
+
|
|
794
860
|
@unit.setter
|
|
795
861
|
def unit(self, v):
|
|
796
|
-
if v
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
raise ValueError("
|
|
862
|
+
if v == self.__unit: # no effect
|
|
863
|
+
return
|
|
864
|
+
if v is None or np.isnan(v) or np.isinf(v):
|
|
865
|
+
raise ValueError("The unit value must not be nan or inf")
|
|
800
866
|
elif v <= 0:
|
|
801
867
|
raise ValueError("The unit value must be greater than zero")
|
|
802
868
|
else:
|
|
803
|
-
if v == self.__unit: # no effect unless unit changes
|
|
804
|
-
return
|
|
805
869
|
self.__unit = v
|
|
806
870
|
for art in self.__Arts:
|
|
807
871
|
art.update_extent()
|
|
808
872
|
self.handler('frame_updated', art)
|
|
809
873
|
self.canvas.draw_idle()
|
|
810
|
-
|
|
874
|
+
|
|
811
875
|
def kill_buffer(self):
|
|
812
|
-
if self.
|
|
813
|
-
del self.
|
|
814
|
-
|
|
815
|
-
def
|
|
876
|
+
if self.frame:
|
|
877
|
+
del self[self.__index]
|
|
878
|
+
|
|
879
|
+
def kill_all_buffers(self):
|
|
816
880
|
del self[:]
|
|
817
|
-
|
|
881
|
+
|
|
818
882
|
def fit_to_axes(self):
|
|
819
883
|
"""Reset the view limits to the current frame extent."""
|
|
820
884
|
if self.frame:
|
|
821
|
-
self.axes.axis(self.frame.get_extent())
|
|
885
|
+
self.axes.axis(self.frame.get_extent()) # reset xlim and ylim
|
|
822
886
|
self.toolbar.update()
|
|
823
887
|
self.toolbar.push_current()
|
|
824
888
|
self.draw()
|
|
825
|
-
|
|
889
|
+
|
|
826
890
|
def fit_to_canvas(self):
|
|
827
891
|
"""Reset the view limits to the canvas range."""
|
|
828
892
|
x, y = self.xlim, self.ylim
|
|
@@ -838,55 +902,56 @@ class GraphPlot(MatplotPanel):
|
|
|
838
902
|
dy = (x[1] - x[0]) / 2 * r
|
|
839
903
|
self.ylim = cy-dy, cy+dy
|
|
840
904
|
self.draw()
|
|
841
|
-
|
|
905
|
+
|
|
842
906
|
def on_focus_set(self, evt):
|
|
843
907
|
"""Called when focus is set (override)."""
|
|
844
908
|
MatplotPanel.on_focus_set(self, evt)
|
|
845
909
|
if self.frame:
|
|
846
910
|
self.handler('frame_selected', self.frame)
|
|
847
911
|
self.on_picker_unlock(evt)
|
|
848
|
-
self.trace_point(*self.
|
|
849
|
-
|
|
912
|
+
self.trace_point(*self.selector)
|
|
913
|
+
|
|
850
914
|
def on_focus_kill(self, evt):
|
|
851
915
|
"""Called when focus is killed (override)."""
|
|
852
916
|
MatplotPanel.on_focus_kill(self, evt)
|
|
853
917
|
if self.frame:
|
|
854
918
|
self.handler('frame_deselected', self.frame)
|
|
855
919
|
self.on_picker_lock(evt)
|
|
856
|
-
|
|
857
|
-
def
|
|
920
|
+
|
|
921
|
+
def get_cmapstr(self):
|
|
858
922
|
if self.frame:
|
|
859
923
|
return self.frame.get_cmap().name
|
|
860
924
|
return ''
|
|
861
|
-
|
|
862
|
-
def
|
|
925
|
+
|
|
926
|
+
def set_cmapstr(self, name):
|
|
863
927
|
if self.frame:
|
|
864
928
|
self.frame.set_cmap(name)
|
|
865
929
|
self.handler('frame_cmapped', self.frame)
|
|
866
930
|
self.draw()
|
|
867
|
-
|
|
931
|
+
|
|
868
932
|
def invert_cmap(self):
|
|
869
933
|
if self.frame:
|
|
870
934
|
name = self.frame.get_cmap().name
|
|
871
|
-
self.
|
|
872
|
-
|
|
935
|
+
self.set_cmapstr(name + "_r" if name[-2:] != "_r" else name[:-2])
|
|
936
|
+
|
|
873
937
|
def trace_point(self, x, y, type=NORMAL):
|
|
874
938
|
"""Puts (override) a message of points x and y."""
|
|
939
|
+
if not hasattr(x, '__iter__'): # called from OnMotion
|
|
940
|
+
return self.trace_point([x], [y], type)
|
|
941
|
+
|
|
875
942
|
frame = self.frame
|
|
876
943
|
if frame:
|
|
877
|
-
if
|
|
878
|
-
nx, ny = frame.xytopixel(x, y)
|
|
879
|
-
z = frame.xytoc(x, y)
|
|
880
|
-
self.message(f"[{nx:-4d},{ny:-4d}] ({x:-8.3f},{y:-8.3f}) value: {z}")
|
|
944
|
+
if len(x) == 0: # no selection
|
|
881
945
|
return
|
|
882
946
|
|
|
883
|
-
if len(x) ==
|
|
947
|
+
if len(x) == 1: # 1-selector trace point (called from markers.setter)
|
|
948
|
+
x, y = x[0], y[0]
|
|
949
|
+
z = frame.xytoc(x, y)
|
|
950
|
+
nx, ny = frame.xytopixel(x, y)
|
|
951
|
+
self.message(f"[{nx:-4d},{ny:-4d}] ({x:-8.3f},{y:-8.3f}) value: {z}")
|
|
884
952
|
return
|
|
885
953
|
|
|
886
|
-
if len(x) ==
|
|
887
|
-
return self.trace_point(x[0], y[0], type)
|
|
888
|
-
|
|
889
|
-
if len(x) == 2: # 2-Selector trace line (called from Selector:setter)
|
|
954
|
+
if len(x) == 2: # 2-selector trace line (called from selector.setter)
|
|
890
955
|
nx, ny = frame.xytopixel(x, y)
|
|
891
956
|
dx = x[1] - x[0]
|
|
892
957
|
dy = y[1] - y[0]
|
|
@@ -895,12 +960,12 @@ class GraphPlot(MatplotPanel):
|
|
|
895
960
|
li = np.hypot(nx[1]-nx[0], ny[1]-ny[0])
|
|
896
961
|
self.message(f"[Line] Length: {li:.1f} pixel ({lu:g}u) Angle: {a:.1f} deg")
|
|
897
962
|
|
|
898
|
-
elif type == REGION:
|
|
963
|
+
elif type == REGION: # N-selector trace polygon (called from region.setter)
|
|
899
964
|
nx, ny = frame.xytopixel(x, y)
|
|
900
|
-
xo,
|
|
901
|
-
|
|
902
|
-
self.message(f"[Region] crop={
|
|
903
|
-
|
|
965
|
+
xo, xp = min(nx), max(nx)
|
|
966
|
+
yo, yp = min(ny), max(ny)
|
|
967
|
+
self.message(f"[Region] crop={xp-xo}:{yp-yo}:{xo}:{yo}") # (W:H:left:top)
|
|
968
|
+
|
|
904
969
|
def writeln(self):
|
|
905
970
|
"""Puts (override) attributes of current frame to the modeline."""
|
|
906
971
|
if not self.modeline.IsShown():
|
|
@@ -909,7 +974,7 @@ class GraphPlot(MatplotPanel):
|
|
|
909
974
|
if frame:
|
|
910
975
|
self.modeline.SetLabel(
|
|
911
976
|
"[{page}/{maxpage}] -{a}- {name} ({data.dtype}:{cmap}{bins}) "
|
|
912
|
-
"[{data.shape[1]}:{data.shape[0]}] {x} [{unit:g}/
|
|
977
|
+
"[{data.shape[1]}:{data.shape[0]}] {x} [{unit:g}/pix]".format(
|
|
913
978
|
page = self.__index,
|
|
914
979
|
maxpage = len(self),
|
|
915
980
|
name = frame.name,
|
|
@@ -921,53 +986,47 @@ class GraphPlot(MatplotPanel):
|
|
|
921
986
|
a = '%%' if not frame.buffer.flags.writeable else '--'))
|
|
922
987
|
else:
|
|
923
988
|
self.modeline.SetLabel(
|
|
924
|
-
"[{page}/{maxpage}] ---- No buffer (-:-) [-:-] -- [{unit:g}/
|
|
989
|
+
"[{page}/{maxpage}] ---- No buffer (-:-) [-:-] -- [{unit:g}/pix]".format(
|
|
925
990
|
page = '-',
|
|
926
991
|
maxpage = len(self),
|
|
927
992
|
unit = self.__unit))
|
|
928
|
-
|
|
993
|
+
|
|
929
994
|
## --------------------------------
|
|
930
|
-
##
|
|
995
|
+
## 外部入出力/複合インターフェース.
|
|
931
996
|
## --------------------------------
|
|
932
997
|
## GraphPlot 間共有のグローバル変数
|
|
933
998
|
clipboard_name = None
|
|
934
999
|
clipboard_data = None
|
|
935
|
-
|
|
1000
|
+
|
|
936
1001
|
def write_buffer_to_clipboard(self):
|
|
937
1002
|
"""Write buffer data to clipboard."""
|
|
938
1003
|
frame = self.frame
|
|
939
1004
|
if not frame:
|
|
940
1005
|
self.message("No frame")
|
|
941
1006
|
return
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
traceback.print_exc()
|
|
952
|
-
self.message("- Failed to write to clipboard.", e)
|
|
953
|
-
|
|
1007
|
+
|
|
1008
|
+
name = frame.name
|
|
1009
|
+
data = frame.roi_or_buffer
|
|
1010
|
+
GraphPlot.clipboard_name = name
|
|
1011
|
+
GraphPlot.clipboard_data = data
|
|
1012
|
+
bins, vlim, img = _to_image(data, frame.cuts)
|
|
1013
|
+
Clipboard.imwrite(img)
|
|
1014
|
+
self.message("Write buffer to clipboard.")
|
|
1015
|
+
|
|
954
1016
|
def read_buffer_from_clipboard(self):
|
|
955
1017
|
"""Read buffer data from clipboard."""
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1018
|
+
name = GraphPlot.clipboard_name
|
|
1019
|
+
data = GraphPlot.clipboard_data
|
|
1020
|
+
if name:
|
|
1021
|
+
self.message("Read buffer from clipboard.")
|
|
1022
|
+
GraphPlot.clipboard_name = None
|
|
1023
|
+
GraphPlot.clipboard_data = None
|
|
1024
|
+
else:
|
|
1025
|
+
self.message("Read image from clipboard.")
|
|
1026
|
+
data = Clipboard.imread()
|
|
1027
|
+
if data is not None:
|
|
966
1028
|
self.load(data)
|
|
967
|
-
|
|
968
|
-
traceback.print_exc()
|
|
969
|
-
self.message("- No data in clipboard.", e)
|
|
970
|
-
|
|
1029
|
+
|
|
971
1030
|
def destroy_colorbar(self):
|
|
972
1031
|
if self.cbar:
|
|
973
1032
|
self.cbar = None
|
|
@@ -976,13 +1035,13 @@ class GraphPlot(MatplotPanel):
|
|
|
976
1035
|
self.canvas.draw_idle()
|
|
977
1036
|
self.handler.unbind('frame_cmapped', self.update_colorbar)
|
|
978
1037
|
self.handler.unbind('frame_shown', self.update_colorbar)
|
|
979
|
-
|
|
1038
|
+
|
|
980
1039
|
def update_colorbar(self, frame):
|
|
981
1040
|
if self.cbar:
|
|
982
1041
|
self.cbar.update_normal(frame)
|
|
983
1042
|
self.canvas.draw_idle()
|
|
984
1043
|
self.figure.draw_without_rendering()
|
|
985
|
-
|
|
1044
|
+
|
|
986
1045
|
def create_colorbar(self):
|
|
987
1046
|
"""Make a colorbar.
|
|
988
1047
|
The colorbar is plotted in self.figure.axes[1] (second axes)
|
|
@@ -997,12 +1056,12 @@ class GraphPlot(MatplotPanel):
|
|
|
997
1056
|
self.handler.bind('frame_shown', self.update_colorbar)
|
|
998
1057
|
else:
|
|
999
1058
|
self.message("- A frame must exist to create a colorbar.")
|
|
1000
|
-
|
|
1059
|
+
|
|
1001
1060
|
## --------------------------------
|
|
1002
|
-
## matplotlib
|
|
1061
|
+
## matplotlib interface.
|
|
1003
1062
|
## --------------------------------
|
|
1004
|
-
|
|
1005
|
-
def on_pick(self, evt):
|
|
1063
|
+
|
|
1064
|
+
def on_pick(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1006
1065
|
"""Pickup image and other arts.
|
|
1007
1066
|
Called (maybe) after mouse buttons are pressed.
|
|
1008
1067
|
"""
|
|
@@ -1016,70 +1075,69 @@ class GraphPlot(MatplotPanel):
|
|
|
1016
1075
|
if not evt.mouseevent.inaxes:
|
|
1017
1076
|
return
|
|
1018
1077
|
|
|
1019
|
-
##
|
|
1078
|
+
## 画像が選択された場合.
|
|
1020
1079
|
if evt.artist in self.__Arts:
|
|
1021
1080
|
if self.__isPicked:
|
|
1022
|
-
self.__isPicked = None
|
|
1081
|
+
self.__isPicked = None # release pick guard
|
|
1023
1082
|
else:
|
|
1024
1083
|
self.handler('image_picked', evt)
|
|
1025
1084
|
|
|
1026
|
-
##
|
|
1085
|
+
## その他のプロットが選択された場合.
|
|
1027
1086
|
else:
|
|
1028
1087
|
if evt.artist is self.marked:
|
|
1029
|
-
self.__isPicked = 'mark'
|
|
1088
|
+
self.__isPicked = 'mark' # image pick gurad
|
|
1030
1089
|
self.handler('mark_picked', evt)
|
|
1031
1090
|
|
|
1032
1091
|
elif evt.artist is self.rected:
|
|
1033
|
-
self.__isPicked = 'region'
|
|
1092
|
+
self.__isPicked = 'region' # image pick gurad
|
|
1034
1093
|
self.handler('region_picked', evt)
|
|
1035
1094
|
|
|
1036
1095
|
elif evt.artist is self.selected:
|
|
1037
|
-
if (self.
|
|
1038
|
-
or wx.GetKeyState(wx.WXK_SHIFT)):
|
|
1096
|
+
if (self.selector.shape[1] < 2 # single selector
|
|
1097
|
+
or wx.GetKeyState(wx.WXK_SHIFT)): # or polygon mode
|
|
1039
1098
|
return
|
|
1040
|
-
self.__isPicked = 'line'
|
|
1099
|
+
self.__isPicked = 'line' # image pick gurad
|
|
1041
1100
|
self.handler('line_picked', evt)
|
|
1042
1101
|
else:
|
|
1043
1102
|
self.__isPicked = 'art'
|
|
1044
|
-
MatplotPanel.on_pick(self, evt)
|
|
1103
|
+
MatplotPanel.on_pick(self, evt) # [art_picked]
|
|
1045
1104
|
|
|
1046
1105
|
self.canvas.draw_idle()
|
|
1047
|
-
|
|
1106
|
+
|
|
1048
1107
|
def on_picker_lock(self, evt):
|
|
1049
1108
|
self.__isPicked = True
|
|
1050
|
-
|
|
1109
|
+
|
|
1051
1110
|
def on_picker_unlock(self, evt):
|
|
1052
1111
|
self.__isPicked = False
|
|
1053
|
-
|
|
1054
|
-
def OnImagePicked(self, evt):
|
|
1112
|
+
|
|
1113
|
+
def OnImagePicked(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1055
1114
|
x = evt.mouseevent.xdata
|
|
1056
1115
|
y = evt.mouseevent.ydata
|
|
1057
1116
|
nx, ny = self.frame.xytopixel(x, y)
|
|
1058
|
-
x, y = self.frame.xyfrompixel(nx, ny)
|
|
1059
1117
|
evt.ind = (ny, nx)
|
|
1060
|
-
self.
|
|
1061
|
-
|
|
1118
|
+
self.selector = self.frame.xyfrompixel(nx, ny)
|
|
1119
|
+
|
|
1062
1120
|
def _inaxes(self, evt):
|
|
1063
1121
|
try:
|
|
1064
|
-
return evt.inaxes is not self.axes
|
|
1122
|
+
return evt.inaxes is not self.axes # <matplotlib.backend_bases.MouseEvent>
|
|
1065
1123
|
except AttributeError:
|
|
1066
|
-
return None
|
|
1067
|
-
|
|
1124
|
+
return None # <wx._core.KeyEvent>
|
|
1125
|
+
|
|
1068
1126
|
## --------------------------------
|
|
1069
|
-
## Pan/Zoom actions (override)
|
|
1127
|
+
## Pan/Zoom actions (override).
|
|
1070
1128
|
## --------------------------------
|
|
1071
1129
|
## antialiased, nearest, bilinear, bicubic, spline16,
|
|
1072
1130
|
## spline36, hanning, hamming, hermite, kaiser, quadric,
|
|
1073
|
-
## catrom, gaussian, bessel, mitchell, sinc, lanczos, or none
|
|
1131
|
+
## catrom, gaussian, bessel, mitchell, sinc, lanczos, or none.
|
|
1074
1132
|
interpolation_mode = 'bilinear'
|
|
1075
|
-
|
|
1133
|
+
|
|
1076
1134
|
def OnDraw(self, evt):
|
|
1077
1135
|
"""Called before canvas.draw (overridden)."""
|
|
1078
1136
|
if not self.interpolation_mode:
|
|
1079
1137
|
return
|
|
1080
1138
|
frame = self.frame
|
|
1081
1139
|
if frame:
|
|
1082
|
-
## [dots/
|
|
1140
|
+
## [dots/pix] = [dots/u] * [u/pix]
|
|
1083
1141
|
dots = self.ddpu[0] * frame.unit * frame.binning
|
|
1084
1142
|
|
|
1085
1143
|
if frame.get_interpolation() == 'nearest' and dots < 1:
|
|
@@ -1087,33 +1145,33 @@ class GraphPlot(MatplotPanel):
|
|
|
1087
1145
|
|
|
1088
1146
|
elif frame.get_interpolation() != 'nearest' and dots > 1:
|
|
1089
1147
|
frame.set_interpolation('nearest')
|
|
1090
|
-
|
|
1148
|
+
|
|
1091
1149
|
def OnMotion(self, evt):
|
|
1092
1150
|
"""Called when mouse moves in axes (overridden)."""
|
|
1093
|
-
if self.
|
|
1151
|
+
if self.selector.shape[1] < 2:
|
|
1094
1152
|
self.trace_point(evt.xdata, evt.ydata)
|
|
1095
|
-
|
|
1153
|
+
|
|
1096
1154
|
def OnPageDown(self, evt):
|
|
1097
1155
|
"""Next page."""
|
|
1098
1156
|
i = self.__index
|
|
1099
1157
|
if i is not None and i < len(self)-1:
|
|
1100
1158
|
self.select(i + 1)
|
|
1101
|
-
|
|
1159
|
+
|
|
1102
1160
|
def OnPageUp(self, evt):
|
|
1103
1161
|
"""Previous page."""
|
|
1104
1162
|
i = self.__index
|
|
1105
1163
|
if i is not None and i > 0:
|
|
1106
1164
|
self.select(i - 1)
|
|
1107
|
-
|
|
1165
|
+
|
|
1108
1166
|
def OnHomePosition(self, evt):
|
|
1109
1167
|
self.fit_to_axes()
|
|
1110
|
-
|
|
1168
|
+
|
|
1111
1169
|
def OnEscapeSelection(self, evt):
|
|
1112
|
-
xs, ys = self.
|
|
1113
|
-
del self.
|
|
1170
|
+
xs, ys = self.selector
|
|
1171
|
+
del self.selector
|
|
1114
1172
|
if len(xs) > 1:
|
|
1115
1173
|
self.handler('line_removed', self.frame)
|
|
1116
|
-
|
|
1174
|
+
|
|
1117
1175
|
def OnXAxisPanZoom(self, evt, c=None):
|
|
1118
1176
|
org = self.p_event
|
|
1119
1177
|
M = np.exp(-(evt.x - org.x)/100)
|
|
@@ -1123,7 +1181,7 @@ class GraphPlot(MatplotPanel):
|
|
|
1123
1181
|
self.ylim = self.zoomlim(self.ylim, M)
|
|
1124
1182
|
org.x, org.y = evt.x, evt.y
|
|
1125
1183
|
self.draw()
|
|
1126
|
-
|
|
1184
|
+
|
|
1127
1185
|
def OnYAxisPanZoom(self, evt, c=None):
|
|
1128
1186
|
org = self.p_event
|
|
1129
1187
|
M = np.exp(-(evt.y - org.y)/100)
|
|
@@ -1133,11 +1191,11 @@ class GraphPlot(MatplotPanel):
|
|
|
1133
1191
|
self.ylim = self.zoomlim(self.ylim, M, c)
|
|
1134
1192
|
org.x, org.y = evt.x, evt.y
|
|
1135
1193
|
self.draw()
|
|
1136
|
-
|
|
1194
|
+
|
|
1137
1195
|
## --------------------------------
|
|
1138
|
-
## Selector interface
|
|
1196
|
+
## Selector interface.
|
|
1139
1197
|
## --------------------------------
|
|
1140
|
-
|
|
1198
|
+
|
|
1141
1199
|
def calc_point(self, x, y, centred=True, inaxes=False):
|
|
1142
1200
|
"""Computes the nearest pixelated point from a point (x, y).
|
|
1143
1201
|
If centred, correct the points to the center of the nearest pixel.
|
|
@@ -1165,63 +1223,63 @@ class GraphPlot(MatplotPanel):
|
|
|
1165
1223
|
x = l + nx * ux
|
|
1166
1224
|
y = t - ny * uy
|
|
1167
1225
|
return (x, y)
|
|
1168
|
-
|
|
1226
|
+
|
|
1169
1227
|
def calc_shiftpoint(self, xo, yo, x, y, centred=True):
|
|
1170
1228
|
"""Restrict point (x, y) from (xo, yo) in pi/8 step angles.
|
|
1171
1229
|
If centred, correct the point to the center of the nearest pixel.
|
|
1172
1230
|
"""
|
|
1173
1231
|
dx, dy = x-xo, y-yo
|
|
1174
|
-
L = np.hypot(dy,dx)
|
|
1175
|
-
a = np.arctan2(dy,dx)
|
|
1176
|
-
aa = np.linspace(-pi,pi,9) + pi/8
|
|
1232
|
+
L = np.hypot(dy, dx)
|
|
1233
|
+
a = np.arctan2(dy, dx)
|
|
1234
|
+
aa = np.linspace(-pi, pi, 9) + pi/8 # 角度の検索範囲
|
|
1177
1235
|
k = np.searchsorted(aa, a)
|
|
1178
1236
|
x = xo + L * np.cos(aa[k] - pi/8)
|
|
1179
1237
|
y = yo + L * np.sin(aa[k] - pi/8)
|
|
1180
1238
|
return self.calc_point(x, y, centred)
|
|
1181
|
-
|
|
1239
|
+
|
|
1182
1240
|
def OnSelectorAppend(self, evt):
|
|
1183
|
-
xs, ys = self.
|
|
1241
|
+
xs, ys = self.selector
|
|
1184
1242
|
x, y = self.calc_point(evt.xdata, evt.ydata)
|
|
1185
|
-
self.
|
|
1243
|
+
self.selector = np.append(xs, x), np.append(ys, y)
|
|
1186
1244
|
self.handler('line_drawn', self.frame)
|
|
1187
|
-
|
|
1245
|
+
|
|
1188
1246
|
def OnDragLock(self, evt):
|
|
1189
1247
|
pass
|
|
1190
|
-
|
|
1248
|
+
|
|
1191
1249
|
def OnDragBegin(self, evt):
|
|
1192
1250
|
if not self.frame or self._inaxes(evt):
|
|
1193
1251
|
self.handler('quit', evt)
|
|
1194
1252
|
return
|
|
1195
|
-
org = self.p_event
|
|
1253
|
+
org = self.p_event # the last pressed
|
|
1196
1254
|
self.__lastpoint = self.calc_point(org.xdata, org.ydata)
|
|
1197
|
-
self.__orgpoints = self.
|
|
1198
|
-
|
|
1255
|
+
self.__orgpoints = self.selector
|
|
1256
|
+
|
|
1199
1257
|
def OnDragMove(self, evt, shift=False):
|
|
1200
1258
|
x, y = self.calc_point(evt.xdata, evt.ydata)
|
|
1201
1259
|
xo, yo = self.__lastpoint
|
|
1202
1260
|
if shift:
|
|
1203
1261
|
x, y = self.calc_shiftpoint(xo, yo, x, y)
|
|
1204
|
-
self.
|
|
1262
|
+
self.selector = np.append(xo, x), np.append(yo, y)
|
|
1205
1263
|
self.handler('line_draw', self.frame)
|
|
1206
|
-
|
|
1264
|
+
|
|
1207
1265
|
def OnDragShiftMove(self, evt):
|
|
1208
1266
|
self.OnDragMove(evt, shift=True)
|
|
1209
|
-
|
|
1267
|
+
|
|
1210
1268
|
def OnDragEscape(self, evt):
|
|
1211
|
-
self.
|
|
1269
|
+
self.selector = self.__orgpoints
|
|
1212
1270
|
self.handler('line_draw', self.frame)
|
|
1213
1271
|
|
|
1214
1272
|
def OnDragEnd(self, evt):
|
|
1215
1273
|
x, y = self.calc_point(evt.xdata, evt.ydata)
|
|
1216
1274
|
xo, yo = self.__lastpoint
|
|
1217
1275
|
if x == xo and y == yo:
|
|
1218
|
-
self.
|
|
1276
|
+
self.selector = ([x], [y])
|
|
1219
1277
|
self.handler('line_drawn', self.frame)
|
|
1220
|
-
|
|
1278
|
+
|
|
1221
1279
|
## --------------------------------
|
|
1222
|
-
## Selector +Line interface
|
|
1280
|
+
## Selector + Line interface.
|
|
1223
1281
|
## --------------------------------
|
|
1224
|
-
|
|
1282
|
+
|
|
1225
1283
|
def OnLineSelected(self, evt):
|
|
1226
1284
|
k = evt.ind[0]
|
|
1227
1285
|
x = evt.mouseevent.xdata
|
|
@@ -1229,18 +1287,18 @@ class GraphPlot(MatplotPanel):
|
|
|
1229
1287
|
xs, ys = evt.artist.get_data(orig=0)
|
|
1230
1288
|
dots = np.hypot(x-xs[k], y-ys[k]) * self.ddpu[0]
|
|
1231
1289
|
self.__linesel = k if dots < 8 else None
|
|
1232
|
-
|
|
1233
|
-
def OnLineDeselected(self, evt):
|
|
1290
|
+
|
|
1291
|
+
def OnLineDeselected(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1234
1292
|
self.__linesel = None
|
|
1235
|
-
|
|
1293
|
+
|
|
1236
1294
|
def OnLineDragBegin(self, evt):
|
|
1237
1295
|
if not self.frame or self._inaxes(evt):
|
|
1238
1296
|
self.handler('quit', evt)
|
|
1239
1297
|
return
|
|
1240
|
-
org = self.p_event
|
|
1298
|
+
org = self.p_event # the last pressed
|
|
1241
1299
|
self.__lastpoint = self.calc_point(org.xdata, org.ydata)
|
|
1242
|
-
self.__orgpoints = self.
|
|
1243
|
-
|
|
1300
|
+
self.__orgpoints = self.selector
|
|
1301
|
+
|
|
1244
1302
|
def OnLineDragMove(self, evt, shift=False):
|
|
1245
1303
|
x, y = self.calc_point(evt.xdata, evt.ydata)
|
|
1246
1304
|
xc, yc = self.__lastpoint
|
|
@@ -1249,82 +1307,262 @@ class GraphPlot(MatplotPanel):
|
|
|
1249
1307
|
if j is not None:
|
|
1250
1308
|
if shift:
|
|
1251
1309
|
i = j-1 if j else 1
|
|
1252
|
-
xo, yo = xo[i], yo[i]
|
|
1310
|
+
xo, yo = xo[i], yo[i] # となりの点を基準とする
|
|
1253
1311
|
x, y = self.calc_shiftpoint(xo, yo, x, y)
|
|
1254
|
-
xs, ys = self.
|
|
1312
|
+
xs, ys = self.selector
|
|
1255
1313
|
xs[j], ys[j] = x, y
|
|
1256
|
-
self.
|
|
1314
|
+
self.selector = (xs, ys)
|
|
1257
1315
|
self.handler('line_draw', self.frame)
|
|
1258
1316
|
else:
|
|
1259
1317
|
xs = xo + (x - xc)
|
|
1260
1318
|
ys = yo + (y - yc)
|
|
1261
|
-
self.
|
|
1319
|
+
self.selector = (xs, ys)
|
|
1262
1320
|
self.handler('line_move', self.frame)
|
|
1263
|
-
|
|
1321
|
+
|
|
1264
1322
|
def OnLineDragShiftMove(self, evt):
|
|
1265
1323
|
self.OnLineDragMove(evt, shift=True)
|
|
1266
|
-
|
|
1324
|
+
|
|
1267
1325
|
def OnLineDragEscape(self, evt):
|
|
1268
|
-
self.
|
|
1326
|
+
self.selector = self.__orgpoints
|
|
1269
1327
|
if self.__linesel:
|
|
1270
1328
|
self.handler('line_drawn', self.frame)
|
|
1271
1329
|
else:
|
|
1272
1330
|
self.handler('line_moved', self.frame)
|
|
1273
|
-
|
|
1331
|
+
|
|
1274
1332
|
def OnLineDragEnd(self, evt):
|
|
1275
1333
|
if self.__linesel:
|
|
1276
1334
|
self.handler('line_drawn', self.frame)
|
|
1277
1335
|
else:
|
|
1278
1336
|
self.handler('line_moved', self.frame)
|
|
1279
|
-
|
|
1337
|
+
|
|
1280
1338
|
def OnLineShift(self, evt):
|
|
1281
|
-
if self.
|
|
1339
|
+
if self.selector.size and self.frame:
|
|
1282
1340
|
ux, uy = self.frame.xy_unit
|
|
1283
1341
|
du = {
|
|
1284
|
-
'up' : (
|
|
1285
|
-
'down' : (
|
|
1286
|
-
'left' : (-ux, 0
|
|
1287
|
-
'right' : (
|
|
1342
|
+
'up' : (0, +uy),
|
|
1343
|
+
'down' : (0, -uy),
|
|
1344
|
+
'left' : (-ux, 0),
|
|
1345
|
+
'right' : (+ux, 0),
|
|
1288
1346
|
}
|
|
1289
|
-
self.
|
|
1347
|
+
self.selector += np.resize(du[evt.key], (2,1))
|
|
1290
1348
|
self.handler('line_move', self.frame)
|
|
1291
|
-
|
|
1349
|
+
|
|
1292
1350
|
def OnLineShiftEnd(self, evt):
|
|
1293
1351
|
self.handler('line_moved', self.frame)
|
|
1294
|
-
|
|
1352
|
+
|
|
1295
1353
|
## --------------------------------
|
|
1296
|
-
##
|
|
1354
|
+
## Marker interface.
|
|
1297
1355
|
## --------------------------------
|
|
1298
|
-
|
|
1356
|
+
|
|
1357
|
+
## Limit number of markers to display 最大(表示)数を制限する.
|
|
1358
|
+
maxnum_markers = 1000
|
|
1359
|
+
|
|
1360
|
+
@property
|
|
1361
|
+
def markers(self):
|
|
1362
|
+
"""Marked points data array [[x],[y]]."""
|
|
1363
|
+
xm, ym = self.marked.get_data(orig=0)
|
|
1364
|
+
return np.array((xm, ym))
|
|
1365
|
+
|
|
1366
|
+
@markers.setter
|
|
1367
|
+
def markers(self, v):
|
|
1368
|
+
x, y = v
|
|
1369
|
+
if not hasattr(x, '__iter__'):
|
|
1370
|
+
x, y = [x], [y]
|
|
1371
|
+
elif len(x) > self.maxnum_markers:
|
|
1372
|
+
self.message("- Got too many markers ({}) to plot".format(len(x)))
|
|
1373
|
+
return
|
|
1374
|
+
self.marked.set_data(x, y)
|
|
1375
|
+
self.__marksel = []
|
|
1376
|
+
self.update_mark_art()
|
|
1377
|
+
self.handler('mark_drawn', self.frame)
|
|
1378
|
+
|
|
1379
|
+
@markers.deleter
|
|
1380
|
+
def markers(self):
|
|
1381
|
+
if self.markers.size:
|
|
1382
|
+
self.marked.set_data([], [])
|
|
1383
|
+
self.__marksel = []
|
|
1384
|
+
self.update_mark_art()
|
|
1385
|
+
self.handler('mark_removed', self.frame)
|
|
1386
|
+
|
|
1387
|
+
def get_current_mark(self):
|
|
1388
|
+
"""Currently selected mark."""
|
|
1389
|
+
xm, ym = self.marked.get_data(orig=0)
|
|
1390
|
+
return np.take((xm, ym), self.__marksel, axis=1)
|
|
1391
|
+
|
|
1392
|
+
def set_current_mark(self, x, y):
|
|
1393
|
+
xm, ym = self.marked.get_data(orig=0)
|
|
1394
|
+
j = self.__marksel
|
|
1395
|
+
if j:
|
|
1396
|
+
xm[j], ym[j] = x, y
|
|
1397
|
+
self.marked.set_data(xm, ym)
|
|
1398
|
+
self.update_mark_art(j, xm[j], ym[j])
|
|
1399
|
+
else:
|
|
1400
|
+
n = len(xm)
|
|
1401
|
+
k = len(x) if hasattr(x, '__iter__') else 1
|
|
1402
|
+
self.__marksel = list(range(n, n+k))
|
|
1403
|
+
xm, ym = np.append(xm, x), np.append(ym, y)
|
|
1404
|
+
self.marked.set_data(xm, ym)
|
|
1405
|
+
self.marked.set_visible(1)
|
|
1406
|
+
self.update_mark_art()
|
|
1407
|
+
self.selector = (x, y)
|
|
1408
|
+
|
|
1409
|
+
def del_current_mark(self):
|
|
1410
|
+
j = self.__marksel
|
|
1411
|
+
if j:
|
|
1412
|
+
xm, ym = self.marked.get_data(orig=0)
|
|
1413
|
+
xm, ym = np.delete(xm, j), np.delete(ym, j)
|
|
1414
|
+
self.__marksel = []
|
|
1415
|
+
self.marked.set_data(xm, ym)
|
|
1416
|
+
n = len(xm)
|
|
1417
|
+
self.__marksel = [j[-1] % n] if n > 0 else []
|
|
1418
|
+
self.update_mark_art()
|
|
1419
|
+
|
|
1420
|
+
def update_mark_art(self, *args):
|
|
1421
|
+
if args:
|
|
1422
|
+
for k, x, y in zip(*args):
|
|
1423
|
+
art = self.__markarts[k] # art の再描画処理をして終了
|
|
1424
|
+
art.xy = x, y
|
|
1425
|
+
self.draw(self.marked)
|
|
1426
|
+
return
|
|
1427
|
+
for art in self.__markarts: # or reset all arts
|
|
1428
|
+
art.remove()
|
|
1429
|
+
self.__markarts = []
|
|
1430
|
+
if self.marked.get_visible() and self.handler.current_state in (MARK, MARK+DRAGGING):
|
|
1431
|
+
N = self.maxnum_markers
|
|
1432
|
+
xm, ym = self.marked.get_data(orig=0)
|
|
1433
|
+
for k, (x, y) in enumerate(zip(xm[:N], ym[:N])):
|
|
1434
|
+
self.__markarts.append(
|
|
1435
|
+
self.axes.annotate(k, # <matplotlib.text.Annotation>
|
|
1436
|
+
xy=(x,y), xycoords='data',
|
|
1437
|
+
xytext=(6,6), textcoords='offset points',
|
|
1438
|
+
bbox=dict(boxstyle="round", fc=(1,1,1,), ec=(1,0,0,)),
|
|
1439
|
+
color='red', size=7, # fontsize=8,
|
|
1440
|
+
)
|
|
1441
|
+
)
|
|
1442
|
+
self.trace_point(*self.get_current_mark(), type=MARK)
|
|
1443
|
+
self.draw(self.marked)
|
|
1444
|
+
|
|
1445
|
+
def OnMarkAppend(self, evt):
|
|
1446
|
+
xs, ys = self.selector
|
|
1447
|
+
if not self.__marksel and len(xs) > 0:
|
|
1448
|
+
self.set_current_mark(xs, ys)
|
|
1449
|
+
self.handler('mark_drawn', self.frame)
|
|
1450
|
+
self.update_mark_art()
|
|
1451
|
+
|
|
1452
|
+
def OnMarkRemove(self, evt):
|
|
1453
|
+
if self.__marksel:
|
|
1454
|
+
self.del_current_mark()
|
|
1455
|
+
self.handler('mark_removed', self.frame)
|
|
1456
|
+
|
|
1457
|
+
def OnMarkSelected(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1458
|
+
k = evt.ind[0]
|
|
1459
|
+
if evt.mouseevent.key == 'shift': # 多重マーカー選択
|
|
1460
|
+
if k not in self.__marksel:
|
|
1461
|
+
self.__marksel += [k]
|
|
1462
|
+
else:
|
|
1463
|
+
self.__marksel = [k]
|
|
1464
|
+
self.update_mark_art()
|
|
1465
|
+
self.selector = self.get_current_mark()
|
|
1466
|
+
if self.selector.shape[1] > 1:
|
|
1467
|
+
self.handler('line_drawn', self.frame) # 多重マーカー選択時
|
|
1468
|
+
|
|
1469
|
+
def OnMarkDeselected(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1470
|
+
self.__marksel = []
|
|
1471
|
+
self.update_mark_art()
|
|
1472
|
+
|
|
1473
|
+
def OnMarkDragBegin(self, evt):
|
|
1474
|
+
if not self.frame or self._inaxes(evt):
|
|
1475
|
+
self.handler('quit', evt)
|
|
1476
|
+
return
|
|
1477
|
+
self.__orgpoints = self.get_current_mark()
|
|
1478
|
+
|
|
1479
|
+
def OnMarkDragMove(self, evt):
|
|
1480
|
+
x, y = self.calc_point(evt.xdata, evt.ydata)
|
|
1481
|
+
self.set_current_mark(x, y)
|
|
1482
|
+
self.handler('mark_draw', self.frame)
|
|
1483
|
+
|
|
1484
|
+
def OnMarkDragEscape(self, evt):
|
|
1485
|
+
self.set_current_mark(*self.__orgpoints)
|
|
1486
|
+
self.handler('mark_drawn', self.frame)
|
|
1487
|
+
|
|
1488
|
+
def OnMarkDragEnd(self, evt):
|
|
1489
|
+
self.handler('mark_drawn', self.frame)
|
|
1490
|
+
|
|
1491
|
+
def OnMarkShift(self, evt):
|
|
1492
|
+
j = self.__marksel
|
|
1493
|
+
if j and self.frame:
|
|
1494
|
+
ux, uy = self.frame.xy_unit
|
|
1495
|
+
du = {
|
|
1496
|
+
'up' : (0, uy),
|
|
1497
|
+
'down' : (0, -uy),
|
|
1498
|
+
'left' : (-ux, 0),
|
|
1499
|
+
'right' : ( ux, 0),
|
|
1500
|
+
}
|
|
1501
|
+
p = self.get_current_mark() + np.resize(du[evt.key], (2,1))
|
|
1502
|
+
self.set_current_mark(*p)
|
|
1503
|
+
self.handler('mark_draw', self.frame)
|
|
1504
|
+
|
|
1505
|
+
def OnMarkShiftEnd(self, evt):
|
|
1506
|
+
self.handler('mark_drawn', self.frame)
|
|
1507
|
+
|
|
1508
|
+
def next_mark(self, j):
|
|
1509
|
+
self.__marksel = [j]
|
|
1510
|
+
xs, ys = self.get_current_mark()
|
|
1511
|
+
self.xlim += xs[-1] - (self.xlim[1] + self.xlim[0]) / 2
|
|
1512
|
+
self.ylim += ys[-1] - (self.ylim[1] + self.ylim[0]) / 2
|
|
1513
|
+
self.selector = (xs, ys)
|
|
1514
|
+
self.trace_point(xs, ys, type=MARK)
|
|
1515
|
+
self.draw()
|
|
1516
|
+
|
|
1517
|
+
def OnMarkSkipNext(self, evt):
|
|
1518
|
+
n = self.markers.shape[1]
|
|
1519
|
+
j = self.__marksel
|
|
1520
|
+
if j:
|
|
1521
|
+
self.next_mark((j[-1]+1) % n)
|
|
1522
|
+
elif n:
|
|
1523
|
+
self.next_mark(0)
|
|
1524
|
+
|
|
1525
|
+
def OnMarkSkipPrevious(self, evt):
|
|
1526
|
+
n = self.markers.shape[1]
|
|
1527
|
+
j = self.__marksel
|
|
1528
|
+
if j:
|
|
1529
|
+
self.next_mark((j[-1]-1) % n)
|
|
1530
|
+
elif n:
|
|
1531
|
+
self.next_mark(-1)
|
|
1532
|
+
|
|
1533
|
+
## --------------------------------
|
|
1534
|
+
## Region interface.
|
|
1535
|
+
## --------------------------------
|
|
1536
|
+
|
|
1299
1537
|
@property
|
|
1300
|
-
def
|
|
1301
|
-
"""
|
|
1538
|
+
def region(self):
|
|
1539
|
+
"""Cropped rectangle points data array [[l,r], [b,t]]."""
|
|
1302
1540
|
x, y = self.rected.get_data(orig=0)
|
|
1303
1541
|
if len(x) and len(y):
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
return np.array(((
|
|
1307
|
-
return np.resize(0., (2,0))
|
|
1308
|
-
|
|
1309
|
-
@
|
|
1310
|
-
def
|
|
1542
|
+
l, r = min(x), max(x)
|
|
1543
|
+
b, t = min(y), max(y)
|
|
1544
|
+
return np.array(((l,r), (b,t)))
|
|
1545
|
+
return np.resize(0., (2, 0))
|
|
1546
|
+
|
|
1547
|
+
@region.setter
|
|
1548
|
+
def region(self, v):
|
|
1311
1549
|
x, y = v
|
|
1312
1550
|
if len(x) > 1:
|
|
1313
1551
|
self.set_current_rect(x, y)
|
|
1314
1552
|
self.handler('region_drawn', self.frame)
|
|
1315
|
-
|
|
1316
|
-
@
|
|
1317
|
-
def
|
|
1318
|
-
if self.
|
|
1553
|
+
|
|
1554
|
+
@region.deleter
|
|
1555
|
+
def region(self):
|
|
1556
|
+
if self.region.size:
|
|
1319
1557
|
self.del_current_rect()
|
|
1320
1558
|
self.handler('region_removed', self.frame)
|
|
1321
|
-
|
|
1559
|
+
|
|
1322
1560
|
def get_current_rect(self):
|
|
1323
1561
|
"""Currently selected region."""
|
|
1324
1562
|
if self.__rectsel:
|
|
1325
1563
|
x, y = self.rected.get_data(orig=0)
|
|
1326
1564
|
return np.array((x, y))
|
|
1327
|
-
|
|
1565
|
+
|
|
1328
1566
|
def set_current_rect(self, x, y):
|
|
1329
1567
|
if len(x) == 2:
|
|
1330
1568
|
(xa,xb), (ya,yb) = x, y
|
|
@@ -1333,9 +1571,7 @@ class GraphPlot(MatplotPanel):
|
|
|
1333
1571
|
l,r,b,t = self.frame.get_extent()
|
|
1334
1572
|
xa, xb = min(x), max(x)
|
|
1335
1573
|
ya, yb = min(y), max(y)
|
|
1336
|
-
##
|
|
1337
|
-
## return
|
|
1338
|
-
## Modify range so that it does not exceed the extent
|
|
1574
|
+
## Modify range so that it does not exceed the extent.
|
|
1339
1575
|
w, h = xb-xa, yb-ya
|
|
1340
1576
|
if xa < l: xa, xb = l, l+w
|
|
1341
1577
|
if xb > r: xa, xb = r-w, r
|
|
@@ -1345,17 +1581,17 @@ class GraphPlot(MatplotPanel):
|
|
|
1345
1581
|
y = [ya, ya, yb, yb, ya]
|
|
1346
1582
|
self.rected.set_data(x, y)
|
|
1347
1583
|
self.rected.set_visible(1)
|
|
1348
|
-
self.
|
|
1349
|
-
|
|
1584
|
+
self.update_rect_art()
|
|
1585
|
+
|
|
1350
1586
|
def del_current_rect(self):
|
|
1351
1587
|
self.__rectsel = []
|
|
1352
1588
|
self.rected.set_data([], [])
|
|
1353
1589
|
self.rected.set_visible(0)
|
|
1354
|
-
self.
|
|
1355
|
-
|
|
1356
|
-
def
|
|
1590
|
+
self.update_rect_art()
|
|
1591
|
+
|
|
1592
|
+
def update_rect_art(self, *args):
|
|
1357
1593
|
if args:
|
|
1358
|
-
art = self.__rectarts
|
|
1594
|
+
art = self.__rectarts # art の再描画処理をして終了
|
|
1359
1595
|
art.xy = args
|
|
1360
1596
|
self.draw(self.rected)
|
|
1361
1597
|
return
|
|
@@ -1367,60 +1603,66 @@ class GraphPlot(MatplotPanel):
|
|
|
1367
1603
|
if x.size:
|
|
1368
1604
|
self.__rectarts.append(
|
|
1369
1605
|
self.axes.add_patch(
|
|
1370
|
-
patches.Polygon(list(zip(x,y)),
|
|
1606
|
+
patches.Polygon(list(zip(x, y)),
|
|
1371
1607
|
color='red', ls='solid', lw=1/2, ec='white', alpha=0.2)
|
|
1372
1608
|
)
|
|
1373
1609
|
)
|
|
1374
1610
|
self.trace_point(x, y, type=REGION)
|
|
1375
1611
|
self.draw(self.rected)
|
|
1376
|
-
|
|
1612
|
+
|
|
1613
|
+
def OnRegionCenter(self, evt):
|
|
1614
|
+
if self.region.size and self.frame:
|
|
1615
|
+
(l,r), (b,t) = self.region
|
|
1616
|
+
c = np.array(((l+r)/2, (b+t)/2))
|
|
1617
|
+
self.region += self.frame.center - c[:,None]
|
|
1618
|
+
|
|
1377
1619
|
def OnRegionAppend(self, evt):
|
|
1378
|
-
xs, ys = self.
|
|
1620
|
+
xs, ys = self.selector
|
|
1379
1621
|
if len(xs) > 0 and self.frame:
|
|
1380
1622
|
ux, uy = self.frame.xy_unit
|
|
1381
1623
|
xs = (xs.min()-ux/2, xs.max()+ux/2)
|
|
1382
1624
|
ys = (ys.max()+uy/2, ys.min()-uy/2)
|
|
1383
1625
|
self.set_current_rect(xs, ys)
|
|
1384
|
-
self.
|
|
1626
|
+
self.update_rect_art()
|
|
1385
1627
|
self.handler('region_drawn', self.frame)
|
|
1386
|
-
|
|
1628
|
+
|
|
1387
1629
|
def OnRegionRemove(self, evt):
|
|
1388
1630
|
if self.__rectsel:
|
|
1389
1631
|
self.del_current_rect()
|
|
1390
1632
|
self.handler('region_removed', self.frame)
|
|
1391
1633
|
self.set_wxcursor(wx.CURSOR_ARROW)
|
|
1392
|
-
|
|
1393
|
-
def OnRegionSelected(self, evt):
|
|
1634
|
+
|
|
1635
|
+
def OnRegionSelected(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1394
1636
|
k = evt.ind[0]
|
|
1395
1637
|
x = evt.mouseevent.xdata
|
|
1396
1638
|
y = evt.mouseevent.ydata
|
|
1397
1639
|
xs, ys = evt.artist.get_data(orig=0)
|
|
1398
1640
|
dots = np.hypot(x-xs[k], y-ys[k]) * self.ddpu[0]
|
|
1399
|
-
self.__rectsel = [k] if dots < 8 else [0,1,2,3,4]
|
|
1400
|
-
self.
|
|
1401
|
-
|
|
1402
|
-
def OnRegionDeselected(self, evt):
|
|
1641
|
+
self.__rectsel = [k] if dots < 8 else [0,1,2,3,4] # リージョンの全選択
|
|
1642
|
+
self.update_rect_art()
|
|
1643
|
+
|
|
1644
|
+
def OnRegionDeselected(self, evt): # <matplotlib.backend_bases.PickEvent>
|
|
1403
1645
|
self.__rectsel = []
|
|
1404
|
-
self.
|
|
1646
|
+
self.update_rect_art()
|
|
1405
1647
|
self.set_wxcursor(wx.CURSOR_ARROW)
|
|
1406
|
-
|
|
1648
|
+
|
|
1407
1649
|
def OnRegionDragBegin(self, evt):
|
|
1408
1650
|
if not self.frame or self._inaxes(evt):
|
|
1409
1651
|
self.handler('quit', evt)
|
|
1410
1652
|
return
|
|
1411
|
-
org = self.p_event
|
|
1653
|
+
org = self.p_event # the last pressed
|
|
1412
1654
|
self.__lastpoint = self.calc_point(org.xdata, org.ydata, centred=False)
|
|
1413
1655
|
if not self.__rectsel:
|
|
1414
1656
|
x, y = self.__lastpoint
|
|
1415
|
-
self.set_current_rect((x,x), (y,y))
|
|
1657
|
+
self.set_current_rect((x, x), (y, y)) # start new region
|
|
1416
1658
|
self.__orgpoints = self.get_current_rect()
|
|
1417
|
-
|
|
1659
|
+
|
|
1418
1660
|
def OnRegionDragMove(self, evt, shift=False, meta=False):
|
|
1419
1661
|
x, y = self.calc_point(evt.xdata, evt.ydata, centred=False)
|
|
1420
1662
|
xs, ys = self.get_current_rect()
|
|
1421
|
-
j = self.__rectsel
|
|
1663
|
+
j = self.__rectsel # corner-drag[1] or region-drag[4]
|
|
1422
1664
|
if len(j) == 1:
|
|
1423
|
-
k = (j[0] + 2) % 4
|
|
1665
|
+
k = (j[0] + 2) % 4 # 選択された一点の対角点
|
|
1424
1666
|
xo, yo = xs[k], ys[k]
|
|
1425
1667
|
if shift:
|
|
1426
1668
|
x, y = self.calc_shiftpoint(xo, yo, x, y, centred=False)
|
|
@@ -1431,7 +1673,7 @@ class GraphPlot(MatplotPanel):
|
|
|
1431
1673
|
i = np.searchsorted(nn, n)
|
|
1432
1674
|
x = xo + nn[i] * np.sign(x-xo) * ux
|
|
1433
1675
|
y = yo + nn[i] * np.sign(y-yo) * uy
|
|
1434
|
-
self.set_current_rect((xo,x), (yo,y))
|
|
1676
|
+
self.set_current_rect((xo, x), (yo, y))
|
|
1435
1677
|
else:
|
|
1436
1678
|
xc, yc = self.__lastpoint
|
|
1437
1679
|
xo, yo = self.__orgpoints
|
|
@@ -1439,50 +1681,50 @@ class GraphPlot(MatplotPanel):
|
|
|
1439
1681
|
ys = yo + (y - yc)
|
|
1440
1682
|
self.set_current_rect(xs, ys)
|
|
1441
1683
|
self.handler('region_draw', self.frame)
|
|
1442
|
-
|
|
1684
|
+
|
|
1443
1685
|
def OnRegionDragShiftMove(self, evt):
|
|
1444
1686
|
self.OnRegionDragMove(evt, shift=True)
|
|
1445
|
-
|
|
1687
|
+
|
|
1446
1688
|
def OnRegionDragMetaMove(self, evt):
|
|
1447
1689
|
self.OnRegionDragMove(evt, meta=True)
|
|
1448
|
-
|
|
1690
|
+
|
|
1449
1691
|
def OnRegionDragEscape(self, evt):
|
|
1450
1692
|
self.set_current_rect(*self.__orgpoints)
|
|
1451
1693
|
self.handler('region_drawn', self.frame)
|
|
1452
|
-
|
|
1694
|
+
|
|
1453
1695
|
def OnRegionDragEnd(self, evt):
|
|
1454
|
-
|
|
1696
|
+
# self.__rectsel = [0,1,2,3,4] # リージョンの全選択
|
|
1455
1697
|
self.handler('region_drawn', self.frame)
|
|
1456
|
-
|
|
1698
|
+
|
|
1457
1699
|
def OnRegionShift(self, evt):
|
|
1458
1700
|
j = self.__rectsel
|
|
1459
1701
|
if j and self.frame:
|
|
1460
1702
|
ux, uy = self.frame.xy_unit
|
|
1461
1703
|
du = {
|
|
1462
|
-
'up' : (
|
|
1463
|
-
'down' : (
|
|
1464
|
-
'left' : (-ux, 0
|
|
1465
|
-
'right' : ( ux, 0
|
|
1704
|
+
'up' : (0, uy),
|
|
1705
|
+
'down' : (0, -uy),
|
|
1706
|
+
'left' : (-ux, 0),
|
|
1707
|
+
'right' : ( ux, 0),
|
|
1466
1708
|
}
|
|
1467
1709
|
dp = du[evt.key]
|
|
1468
1710
|
p = self.get_current_rect().T
|
|
1469
1711
|
if len(j) == 1:
|
|
1470
|
-
i = j[0]
|
|
1471
|
-
k = (i + 2) % 4
|
|
1712
|
+
i = j[0] # 選択されている点
|
|
1713
|
+
k = (i + 2) % 4 # 選択された一点の対角点
|
|
1472
1714
|
p[i] += dp
|
|
1473
1715
|
self.set_current_rect(*p[[k,i]].T)
|
|
1474
1716
|
else:
|
|
1475
1717
|
p += dp
|
|
1476
1718
|
self.set_current_rect(*p.T)
|
|
1477
1719
|
self.handler('region_draw', self.frame)
|
|
1478
|
-
|
|
1720
|
+
|
|
1479
1721
|
def OnRegionShiftEnd(self, evt):
|
|
1480
1722
|
self.handler('region_drawn', self.frame)
|
|
1481
|
-
|
|
1723
|
+
|
|
1482
1724
|
def OnRegionMotion(self, evt):
|
|
1483
1725
|
x, y = evt.xdata, evt.ydata
|
|
1484
|
-
if self.
|
|
1485
|
-
(l,r), (b,t) = self.
|
|
1726
|
+
if self.region.size:
|
|
1727
|
+
(l,r), (b,t) = self.region
|
|
1486
1728
|
d = self.rected.pickradius / self.ddpu[0]
|
|
1487
1729
|
x0 = l+d < x < r-d
|
|
1488
1730
|
y0 = b+d < y < t-d
|
|
@@ -1491,198 +1733,17 @@ class GraphPlot(MatplotPanel):
|
|
|
1491
1733
|
y1 = b-d < y < b+d
|
|
1492
1734
|
y2 = t-d < y < t+d
|
|
1493
1735
|
if x0 and y0:
|
|
1494
|
-
|
|
1736
|
+
# self.set_wxcursor(wx.CURSOR_HAND) # insdie
|
|
1495
1737
|
self.set_wxcursor(wx.CURSOR_ARROW)
|
|
1496
1738
|
elif (x1 or x2) and y0:
|
|
1497
|
-
|
|
1739
|
+
# self.set_wxcursor(wx.CURSOR_SIZEWE) # on-x-edge
|
|
1498
1740
|
self.set_wxcursor(wx.CURSOR_SIZING)
|
|
1499
1741
|
elif x0 and (y1 or y2):
|
|
1500
|
-
|
|
1742
|
+
# self.set_wxcursor(wx.CURSOR_SIZENS) # on-y-edge
|
|
1501
1743
|
self.set_wxcursor(wx.CURSOR_SIZING)
|
|
1502
1744
|
elif x1 and y1 or x2 and y2:
|
|
1503
|
-
self.set_wxcursor(wx.CURSOR_SIZENESW)
|
|
1745
|
+
self.set_wxcursor(wx.CURSOR_SIZENESW) # on-NE/SW-corner
|
|
1504
1746
|
elif x1 and y2 or x2 and y1:
|
|
1505
|
-
self.set_wxcursor(wx.CURSOR_SIZENWSE)
|
|
1747
|
+
self.set_wxcursor(wx.CURSOR_SIZENWSE) # on-NW/SE-corner
|
|
1506
1748
|
else:
|
|
1507
|
-
self.set_wxcursor(wx.CURSOR_ARROW)
|
|
1508
|
-
|
|
1509
|
-
## --------------------------------
|
|
1510
|
-
## Markers interface
|
|
1511
|
-
## --------------------------------
|
|
1512
|
-
|
|
1513
|
-
#: Limit number of markers to display 最大(表示)数を制限する
|
|
1514
|
-
maxnum_markers = 1000
|
|
1515
|
-
|
|
1516
|
-
@property
|
|
1517
|
-
def Markers(self):
|
|
1518
|
-
"""Marked points data array [[x],[y]]."""
|
|
1519
|
-
xm, ym = self.marked.get_data(orig=0)
|
|
1520
|
-
return np.array((xm, ym))
|
|
1521
|
-
|
|
1522
|
-
@Markers.setter
|
|
1523
|
-
def Markers(self, v):
|
|
1524
|
-
x, y = v
|
|
1525
|
-
if not hasattr(x, '__iter__'):
|
|
1526
|
-
x, y = [x], [y]
|
|
1527
|
-
elif len(x) > self.maxnum_markers:
|
|
1528
|
-
self.message("- Got too many markers ({}) to plot".format(len(x)))
|
|
1529
|
-
return
|
|
1530
|
-
self.marked.set_data(x, y)
|
|
1531
|
-
self.__marksel = []
|
|
1532
|
-
self.update_art_of_mark()
|
|
1533
|
-
self.handler('mark_drawn', self.frame)
|
|
1534
|
-
|
|
1535
|
-
@Markers.deleter
|
|
1536
|
-
def Markers(self):
|
|
1537
|
-
if self.Markers.size:
|
|
1538
|
-
self.marked.set_data([], [])
|
|
1539
|
-
self.__marksel = []
|
|
1540
|
-
self.update_art_of_mark()
|
|
1541
|
-
self.handler('mark_removed', self.frame)
|
|
1542
|
-
|
|
1543
|
-
def get_current_mark(self):
|
|
1544
|
-
"""Currently selected mark."""
|
|
1545
|
-
xm, ym = self.marked.get_data(orig=0)
|
|
1546
|
-
return np.take((xm, ym), self.__marksel, axis=1)
|
|
1547
|
-
|
|
1548
|
-
def set_current_mark(self, x, y):
|
|
1549
|
-
xm, ym = self.marked.get_data(orig=0)
|
|
1550
|
-
j = self.__marksel
|
|
1551
|
-
if j:
|
|
1552
|
-
xm[j], ym[j] = x, y
|
|
1553
|
-
self.marked.set_data(xm, ym)
|
|
1554
|
-
self.update_art_of_mark(j, xm[j], ym[j])
|
|
1555
|
-
else:
|
|
1556
|
-
n = len(xm)
|
|
1557
|
-
k = len(x) if hasattr(x, '__iter__') else 1
|
|
1558
|
-
self.__marksel = list(range(n, n+k))
|
|
1559
|
-
xm, ym = np.append(xm, x), np.append(ym, y)
|
|
1560
|
-
self.marked.set_data(xm, ym)
|
|
1561
|
-
self.marked.set_visible(1)
|
|
1562
|
-
self.update_art_of_mark()
|
|
1563
|
-
self.Selector = (x, y)
|
|
1564
|
-
|
|
1565
|
-
def del_current_mark(self):
|
|
1566
|
-
j = self.__marksel
|
|
1567
|
-
if j:
|
|
1568
|
-
xm, ym = self.marked.get_data(orig=0)
|
|
1569
|
-
xm, ym = np.delete(xm,j), np.delete(ym,j)
|
|
1570
|
-
self.__marksel = []
|
|
1571
|
-
self.marked.set_data(xm, ym)
|
|
1572
|
-
n = len(xm)
|
|
1573
|
-
self.__marksel = [j[-1] % n] if n > 0 else []
|
|
1574
|
-
self.update_art_of_mark()
|
|
1575
|
-
|
|
1576
|
-
def update_art_of_mark(self, *args):
|
|
1577
|
-
if args:
|
|
1578
|
-
for k,x,y in zip(*args):
|
|
1579
|
-
art = self.__markarts[k] # art の再描画処理をして終了
|
|
1580
|
-
art.xy = x, y
|
|
1581
|
-
self.draw(self.marked)
|
|
1582
|
-
return
|
|
1583
|
-
for art in self.__markarts: # or reset all arts
|
|
1584
|
-
art.remove()
|
|
1585
|
-
self.__markarts = []
|
|
1586
|
-
if self.marked.get_visible() and self.handler.current_state in (MARK, MARK+DRAGGING):
|
|
1587
|
-
N = self.maxnum_markers
|
|
1588
|
-
xm, ym = self.marked.get_data(orig=0)
|
|
1589
|
-
for k, (x,y) in enumerate(zip(xm[:N],ym[:N])):
|
|
1590
|
-
self.__markarts.append(
|
|
1591
|
-
self.axes.annotate(k, #<matplotlib.text.Annotation>
|
|
1592
|
-
xy=(x,y), xycoords='data',
|
|
1593
|
-
xytext=(6,6), textcoords='offset points',
|
|
1594
|
-
bbox=dict(boxstyle="round", fc=(1,1,1,), ec=(1,0,0,)),
|
|
1595
|
-
color='red', size=7, #fontsize=8,
|
|
1596
|
-
)
|
|
1597
|
-
)
|
|
1598
|
-
self.Selector = self.get_current_mark()
|
|
1599
|
-
self.trace_point(*self.Selector, type=MARK)
|
|
1600
|
-
self.draw(self.marked)
|
|
1601
|
-
|
|
1602
|
-
def OnMarkAppend(self, evt):
|
|
1603
|
-
xs, ys = self.Selector
|
|
1604
|
-
if not self.__marksel and len(xs) > 0:
|
|
1605
|
-
self.set_current_mark(xs, ys)
|
|
1606
|
-
self.handler('mark_drawn', self.frame)
|
|
1607
|
-
self.update_art_of_mark()
|
|
1608
|
-
|
|
1609
|
-
def OnMarkRemove(self, evt):
|
|
1610
|
-
if self.__marksel:
|
|
1611
|
-
self.del_current_mark()
|
|
1612
|
-
self.handler('mark_removed', self.frame)
|
|
1613
|
-
|
|
1614
|
-
def OnMarkSelected(self, evt): #<matplotlib.backend_bases.PickEvent>
|
|
1615
|
-
k = evt.ind[0]
|
|
1616
|
-
if evt.mouseevent.key == 'shift': # 多重マーカー選択
|
|
1617
|
-
if k not in self.__marksel:
|
|
1618
|
-
self.__marksel += [k]
|
|
1619
|
-
else:
|
|
1620
|
-
self.__marksel = [k]
|
|
1621
|
-
self.update_art_of_mark()
|
|
1622
|
-
|
|
1623
|
-
if self.Selector.shape[1] > 1:
|
|
1624
|
-
self.handler('line_drawn', self.frame) # 多重マーカー選択時
|
|
1625
|
-
|
|
1626
|
-
def OnMarkDeselected(self, evt): #<matplotlib.backend_bases.PickEvent>
|
|
1627
|
-
self.__marksel = []
|
|
1628
|
-
self.update_art_of_mark()
|
|
1629
|
-
|
|
1630
|
-
def OnMarkDragBegin(self, evt):
|
|
1631
|
-
if not self.frame or self._inaxes(evt):
|
|
1632
|
-
self.handler('quit', evt)
|
|
1633
|
-
return
|
|
1634
|
-
self.__orgpoints = self.get_current_mark()
|
|
1635
|
-
|
|
1636
|
-
def OnMarkDragMove(self, evt):
|
|
1637
|
-
x, y = self.calc_point(evt.xdata, evt.ydata)
|
|
1638
|
-
self.set_current_mark(x, y)
|
|
1639
|
-
self.handler('mark_draw', self.frame)
|
|
1640
|
-
|
|
1641
|
-
def OnMarkDragEscape(self, evt):
|
|
1642
|
-
self.set_current_mark(*self.__orgpoints)
|
|
1643
|
-
self.handler('mark_drawn', self.frame)
|
|
1644
|
-
|
|
1645
|
-
def OnMarkDragEnd(self, evt):
|
|
1646
|
-
self.handler('mark_drawn', self.frame)
|
|
1647
|
-
|
|
1648
|
-
def OnMarkShift(self, evt):
|
|
1649
|
-
j = self.__marksel
|
|
1650
|
-
if j and self.frame:
|
|
1651
|
-
ux, uy = self.frame.xy_unit
|
|
1652
|
-
du = {
|
|
1653
|
-
'up' : ( 0., uy),
|
|
1654
|
-
'down' : ( 0.,-uy),
|
|
1655
|
-
'left' : (-ux, 0.),
|
|
1656
|
-
'right' : ( ux, 0.),
|
|
1657
|
-
}
|
|
1658
|
-
p = self.get_current_mark() + np.resize(du[evt.key], (2,1))
|
|
1659
|
-
self.set_current_mark(*p)
|
|
1660
|
-
self.handler('mark_draw', self.frame)
|
|
1661
|
-
|
|
1662
|
-
def OnMarkShiftEnd(self, evt):
|
|
1663
|
-
self.handler('mark_drawn', self.frame)
|
|
1664
|
-
|
|
1665
|
-
def next_mark(self, j):
|
|
1666
|
-
self.__marksel = [j]
|
|
1667
|
-
xs, ys = self.get_current_mark()
|
|
1668
|
-
self.xlim += xs[-1] - (self.xlim[1] + self.xlim[0]) / 2
|
|
1669
|
-
self.ylim += ys[-1] - (self.ylim[1] + self.ylim[0]) / 2
|
|
1670
|
-
self.Selector = (xs, ys)
|
|
1671
|
-
self.trace_point(xs, ys, type=MARK)
|
|
1672
|
-
self.draw()
|
|
1673
|
-
|
|
1674
|
-
def OnMarkSkipNext(self, evt):
|
|
1675
|
-
n = self.Markers.shape[1]
|
|
1676
|
-
j = self.__marksel
|
|
1677
|
-
if j:
|
|
1678
|
-
self.next_mark((j[-1]+1) % n)
|
|
1679
|
-
elif n:
|
|
1680
|
-
self.next_mark(0)
|
|
1681
|
-
|
|
1682
|
-
def OnMarkSkipPrevious(self, evt):
|
|
1683
|
-
n = self.Markers.shape[1]
|
|
1684
|
-
j = self.__marksel
|
|
1685
|
-
if j:
|
|
1686
|
-
self.next_mark((j[-1]-1) % n)
|
|
1687
|
-
elif n:
|
|
1688
|
-
self.next_mark(-1)
|
|
1749
|
+
self.set_wxcursor(wx.CURSOR_ARROW) # outside
|