mwxlib 1.0.0__py3-none-any.whl → 1.8.0__py3-none-any.whl

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