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