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/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
- ## from .utilus import warn
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 _imcv(src):
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
- ## return np.asarray(img) # ref
37
- return np.array(img) # copy
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
- buf = np.frombuffer(img.GetDataBuffer(), dtype='uint8')
45
- return buf.reshape(h, w, 3)
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 _to_array(x):
50
- if isinstance(x, (list, tuple)):
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 : cutoff score [%] to cut the lo/hi limits
68
- threshold : limit bytes of image (to make matplotlib light)
69
- binning : minimum binning number of src array
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): # maybe fft pattern
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
- ## Converted to <uint8(=1byte)> finally, binning should be reduced by itemsize.
77
- n = int(np.sqrt(src.nbytes / threshold / src.itemsize)) + 1
78
- if bins < n:
79
- bins = n # binning or threshold; Select the larger one.
80
-
81
- if bins > 1:
82
- ## src = src[::bins,::bins]
83
- src = _imcv(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 bins, (0,255), src
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) # copy buffer
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 bins, (a, b), img
108
+ return n, (a, b), img
104
109
 
105
110
 
106
111
  def _Property(name):
107
112
  return property(
108
- lambda self: getattr(self.parent, name),
109
- lambda self,v: setattr(self.parent, name, v),
110
- lambda self: delattr(self.parent, name))
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 : buffer
118
- name : buffer name
119
- show : show immediately when loaded
120
- aspect : initial aspect ratio
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
- localunit=None, aspect=1.0, **attributes):
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.__localunit = localunit or None # [+] value, no assertion
133
- self.__aspect_ratio = aspect
134
- self.__attributes = attributes
135
- self.__attributes['localunit'] = self.__localunit
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 = imconvert(self.__buf,
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.__vlim = vlim
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() # this determines the aspect ratio
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
- parent = property(lambda self: self.__owner)
159
- artist = property(lambda self: self.__art)
160
- name = property(lambda self: self.__name)
161
- buffer = property(lambda self: self.__buf) # buffer.setter is defined below.
162
- binning = property(lambda self: self.__bins)
163
-
164
- image = property(
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
- attr = attr or {}
199
- attr.update(kwargs)
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 'aspect' in attr:
206
- self.aspect_ratio = attr['aspect']
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
- selector = _Property('Selector')
217
- markers = _Property('Markers')
218
- region = _Property('Region')
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/pixel]."""
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 is None:
237
- v = self.parent.unit
238
- self.__localunit = None
239
- elif np.isnan(v) or np.isinf(v):
240
- raise ValueError("The unit value cannot be NaN or Inf")
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
- else:
244
- if v == self.__localunit: # no effect when v is localunit
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
- if v == self.__aspect_ratio:
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 book."""
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.Region.size:
305
- nx, ny = self.xytopixel(*self.parent.Region)
306
- sx = slice(max(0,nx[0]), nx[1]) # nx slice
307
- sy = slice(max(0,ny[1]), ny[0]) # ny slice 反転 (降順)
308
- return self.__buf[sy,sx]
309
- return self.__buf
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.roi[:] = v # cannot broadcast input array into different shape
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
- if nearest is False, retval is interpolated with spline
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] # nearest value
331
- return ndi.map_coordinates(self.__buf, np.vstack((ny, nx))) # spline value
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, convert pixel-based lengths to pixel numbers.
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
- ## warn("Setting xy data with single tuple.", DeprecationWarning)
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 # Y ピクセルインデクスは座標と逆
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
- ## warn("Setting xy data with single tuple.", DeprecationWarning)
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 # Y ピクセルインデクスは座標と逆
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 ], # show
376
- 'frame_hidden' : [ None ], # show
377
- 'frame_loaded' : [ None ], # load
378
- 'frame_removed' : [ None ], # del[] ! event arg is indices, not frames.
379
- 'frame_selected' : [ None ], # = focus_set
380
- 'frame_deselected' : [ None ], # = focus_kill
381
- 'frame_modified' : [ None, _F(self.writeln) ], # set[],load,roi => update_buffer
382
- 'frame_updated' : [ None, _F(self.writeln) ], # unit,name,ratio => update_extent
383
- 'frame_cmapped' : [ None, _F(self.writeln) ], # cmap
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.get_cmap()[-2:] == "_r")),
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 = 80dpi (0.3175mm/pixel)
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 : buffer array.
609
- name : buffer name (default to *temp*).
610
- pos : Insertion position in the frame list.
611
- show : Show immediately when loaded.
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
- if buf is None:
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) # identical path
697
+ j = paths.index(pathname) # identical path
632
698
  elif name in names:
633
- j = names.index(name) # existing frame
699
+ j = names.index(name) # existing frame
634
700
  if j != -1:
635
701
  art = self.__Arts[j]
636
- art.update_buffer(buf) # => [frame_modified]
637
- art.update_attributes(kwargs) # => [frame_updated] localunit => [canvas_draw]
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 # current 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, j):
660
- if isinstance(j, (str, AxesImagePhantom)):
661
- j = self.index(j)
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: # Hide all frames
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.Selector)
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
- return buffers[j] # j can also be slicing
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
- try:
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 hasattr(j, '__iter__') or isinstance(j, slice):
701
- raise ValueError("attempt to assign buffers into slice")
770
+ if isinstance(j, slice) or hasattr(j, '__iter__'):
771
+ raise ValueError("attempt to assign buffers via slicing or iterator")
702
772
 
703
- if v is None:
704
- self.__delitem__(j)
705
- else:
706
- art = self.__Arts[j]
707
- art.update_buffer(v)
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] # frames to be removed
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
- names = [art.name for art in self.__Arts]
756
- return names.index(j)
757
- return self.__Arts.index(j)
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 == j), None)
762
- return self.__Arts[j]
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
- #: image bytes max for loading matplotlib (with wxAgg backend)
850
+
851
+ ## Image bytes max for loading matplotlib (with wxAgg backend).
769
852
  nbytes_threshold = 24e6
770
-
771
- #: image cutoff score percentiles
772
- score_percentile = 0.01
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
- buffer = property(
786
- lambda self: self.frame and self.frame.buffer,
787
- lambda self,v: self.__setitem__(self.__index, v),
788
- lambda self: self.__delitem__(self.__index),
789
- doc="current buffer array")
790
-
791
- newbuffer = property(
792
- lambda self: None,
793
- lambda self,v: self.load(v),
794
- doc="new buffer loader")
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/pixel]."""
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 is None:
804
- raise ValueError("The globalunit must be non-nil value")
805
- elif np.isnan(v) or np.isinf(v):
806
- raise ValueError("Axis limits cannot be NaN or Inf")
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.buffer is not None:
820
- del self.buffer
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()) # reset xlim and ylim
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.Selector)
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 get_cmap(self):
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 set_cmap(self, name):
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.set_cmap(name + "_r" if name[-2:] != "_r" else name[:-2])
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__'): # called from OnMotion
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: # no selection
973
+ if len(x) == 0: # no selection
891
974
  return
892
975
 
893
- if len(x) == 1: # 1-Selector trace point (called from Marker:setter)
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: # 2-Selector trace line (called from Selector:setter)
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: # N-Selector trace polygon (called from Region:setter)
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) # top-left
908
- xr, yr = max(nx), max(ny) # bottom-right
909
- self.message(f"[Region] crop={xr-xo}:{yr-yo}:{xo}:{yo}") # (W:H:left:top)
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}/pixel]".format(
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}/pixel]".format(
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
- try:
950
- name = frame.name
951
- data = frame.roi
952
- GraphPlot.clipboard_name = name
953
- GraphPlot.clipboard_data = data
954
- bins, vlim, img = imconvert(data, frame.vlim)
955
- Clipboard.imwrite(img)
956
- self.message("Write buffer to clipboard.")
957
- except Exception as e:
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
- try:
964
- name = GraphPlot.clipboard_name
965
- data = GraphPlot.clipboard_data
966
- if name:
967
- self.message("Read buffer from clipboard.")
968
- self.load(data)
969
- GraphPlot.clipboard_name = None
970
- GraphPlot.clipboard_data = None
971
- else:
972
- self.message("Read image from clipboard.")
973
- self.load(Clipboard.imread())
974
- except Exception as e:
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 interfaces
1086
+ ## matplotlib interface.
1010
1087
  ## --------------------------------
1011
-
1012
- def on_pick(self, evt): #<matplotlib.backend_bases.PickEvent>
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 # release pick guard
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' # image pick gurad
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' # image pick gurad
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.Selector.shape[1] < 2 # single selector
1045
- or wx.GetKeyState(wx.WXK_SHIFT)): # or polygon mode
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' # image pick gurad
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) # [art_picked]
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): #<matplotlib.backend_bases.PickEvent>
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.Selector = (x, y)
1068
-
1144
+ self.selector = (x, y)
1145
+
1069
1146
  def _inaxes(self, evt):
1070
1147
  try:
1071
- return evt.inaxes is not self.axes #<matplotlib.backend_bases.MouseEvent>
1148
+ return evt.inaxes is not self.axes #<matplotlib.backend_bases.MouseEvent>
1072
1149
  except AttributeError:
1073
- return None #<wx._core.KeyEvent>
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/pixel] = [dots/u] * [u/pixel]
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.Selector.shape[1] < 2:
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.Selector
1120
- del self.Selector
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.Selector
1267
+ xs, ys = self.selector
1191
1268
  x, y = self.calc_point(evt.xdata, evt.ydata)
1192
- self.Selector = np.append(xs, x), np.append(ys, y)
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 # the last pressed
1279
+ org = self.p_event # the last pressed
1203
1280
  self.__lastpoint = self.calc_point(org.xdata, org.ydata)
1204
- self.__orgpoints = self.Selector
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.Selector = np.append(xo, x), np.append(yo, y)
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.Selector = self.__orgpoints
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.Selector = ([x], [y])
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): #<matplotlib.backend_bases.PickEvent>
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 # the last pressed
1324
+ org = self.p_event # the last pressed
1248
1325
  self.__lastpoint = self.calc_point(org.xdata, org.ydata)
1249
- self.__orgpoints = self.Selector
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.Selector
1338
+ xs, ys = self.selector
1262
1339
  xs[j], ys[j] = x, y
1263
- self.Selector = (xs, ys)
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.Selector = (xs, ys)
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.Selector = self.__orgpoints
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.Selector.size and self.frame:
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.Selector += np.resize(du[evt.key], (2,1))
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
- ## Region interface
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 Region(self):
1308
- """Rectangle points data array [l,r],[b,t]."""
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) #= x[[0, 2]]
1312
- yo, y = min(y), max(y) #= y[[0, 2]]
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
- @Region.setter
1317
- def Region(self, v):
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
- @Region.deleter
1324
- def Region(self):
1325
- if self.Region.size:
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
- ## if (xa < l or xb > r) or (ya < b or yb > t):
1344
- ## return
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 # art の再描画処理をして終了
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.Selector
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): #<matplotlib.backend_bases.PickEvent>
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): #<matplotlib.backend_bases.PickEvent>
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 # the last pressed
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)) # start new region
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 # corner-drag[1] or region-drag[4]
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
- ## self.__rectsel = [0,1,2,3,4] # リージョンの全選択
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.Region.size:
1492
- (l,r), (b,t) = self.Region
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
- ## self.set_wxcursor(wx.CURSOR_HAND) # insdie
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
- ## self.set_wxcursor(wx.CURSOR_SIZEWE) # on-x-edge
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
- ## self.set_wxcursor(wx.CURSOR_SIZENS) # on-y-edge
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) # on-NE/SW-corner
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) # on-NW/SE-corner
1769
+ self.set_wxcursor(wx.CURSOR_SIZENWSE) # on-NW/SE-corner
1513
1770
  else:
1514
- self.set_wxcursor(wx.CURSOR_ARROW) # outside
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