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