mwxlib 0.93.1__py3-none-any.whl → 0.93.7__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.

Potentially problematic release.


This version of mwxlib might be problematic. Click here for more details.

mwx/matplot2lg.py CHANGED
@@ -31,7 +31,7 @@ class LinePlot(MatplotPanel):
31
31
  'delete pressed' : (NORMAL, self.OnEscapeSelection),
32
32
  'M-a pressed' : (NORMAL, self.OnHomePosition),
33
33
  'C-a pressed' : (NORMAL, self.OnHomePosition),
34
- 'Lbutton dblclick' : (NORMAL, self.OnEscapeSelection),
34
+ 'Lbutton dblclick' : (NORMAL, self.OnEscapeSelection, self.OnDragLock),
35
35
  '*Lbutton pressed' : (NORMAL, self.OnDragLock),
36
36
  '*Ldrag begin' : (REGION, self.OnDragBegin),
37
37
  },
@@ -166,7 +166,7 @@ class LinePlot(MatplotPanel):
166
166
  if x < l: x = l
167
167
  elif x > r: x = r
168
168
  self.region = (self.__lastpoint, x)
169
- else:
169
+ elif self.region is not None:
170
170
  a, b = self.region
171
171
  d = x - self.__lastpoint
172
172
  if self.boundary is not None:
@@ -181,6 +181,8 @@ class LinePlot(MatplotPanel):
181
181
  else:
182
182
  self.region = (a+d, b+d)
183
183
  self.__lastpoint = x
184
+ else:
185
+ self.message("- No region.") #<FSM logic-error>
184
186
  self.draw()
185
187
 
186
188
  def OnDragEnd(self, evt):
@@ -282,7 +284,8 @@ class Histogram(LinePlot):
282
284
  self.xlim = x.min(), x.max()
283
285
  self.ylim = 0, y.max()
284
286
  self.region = None
285
- self.update_position()
287
+ self.toolbar.update()
288
+ self.toolbar.push_current()
286
289
  self.draw()
287
290
 
288
291
  def hreplot(self, frame):
@@ -308,7 +311,8 @@ class Histogram(LinePlot):
308
311
  self.__plot.set_data([], [])
309
312
  self.region = None
310
313
 
311
- self.update_position()
314
+ self.toolbar.update()
315
+ self.toolbar.push_current()
312
316
  self.draw()
313
317
 
314
318
  def writeln(self):
@@ -392,9 +396,9 @@ class LineProfile(LinePlot):
392
396
  '*Ldrag begin' : (REGION, self.OnDragBegin),
393
397
  },
394
398
  REGION : {
395
- 'S-Ldrag move' : (REGION+LINE, self.OnRegionLock),
399
+ 'S-Ldrag move' : (REGION+LINE, self.OnRegionLock, self.OnDragLineBegin),
396
400
  'M-Ldrag move' : (REGION+MARK, self.OnMarkPeaks, self.OnMarkSelectionBegin),
397
- '*Ldrag move' : (REGION, self.OnDragMove),
401
+ '*Ldrag move' : (REGION, self.OnDragMove, self.OnDragTrace),
398
402
  '*Ldrag end' : (NORMAL, self.OnDragEnd),
399
403
  },
400
404
  LINE: {
@@ -431,7 +435,7 @@ class LineProfile(LinePlot):
431
435
 
432
436
  self.menu += [
433
437
  (mwx.ID_(510), "&Copy data", "Copy data to clipboard",
434
- lambda v: self.copy_data_to_clipboard()),
438
+ lambda v: self.write_data_to_clipboard()),
435
439
  (),
436
440
  (mwx.ID_(511), "Logic length", "Set axis-unit in logic base", wx.ITEM_RADIO,
437
441
  lambda v: self.set_logic(1),
@@ -516,6 +520,14 @@ class LineProfile(LinePlot):
516
520
  """Plotted (xdata, ydata) in single plot."""
517
521
  return self.__plot.get_data(orig=0)
518
522
 
523
+ def calc_average(self):
524
+ X, Y = self.plotdata
525
+ if self.region is not None:
526
+ a, b = self.region
527
+ Y = Y[(a <= X) & (X <= b)]
528
+ if Y.size:
529
+ return Y.mean()
530
+
519
531
  def linplot(self, frame, fit=True, force=True):
520
532
  if not force:
521
533
  if frame is self.__frame:
@@ -572,7 +584,8 @@ class LineProfile(LinePlot):
572
584
  self.xlim = ls[0], ls[-1]
573
585
  self.ylim = ly[0], max(ly[1], max(zs))
574
586
 
575
- self.update_position()
587
+ self.toolbar.update()
588
+ self.toolbar.push_current()
576
589
  self.draw()
577
590
 
578
591
  def writeln(self):
@@ -594,14 +607,14 @@ class LineProfile(LinePlot):
594
607
  else:
595
608
  self.modeline.write("")
596
609
 
597
- def copy_data_to_clipboard(self):
598
- """Copy plotdata to clipboard."""
599
- self.message("Copy data to clipboard.")
610
+ def write_data_to_clipboard(self):
611
+ """Write plot data to clipboard."""
600
612
  X, Y = self.plotdata
601
613
  with io.StringIO() as o:
602
614
  for x, y in zip(X, Y):
603
615
  o.write("{:g}\t{:g}\n".format(x, y))
604
616
  Clipboard.write(o.getvalue(), verbose=0)
617
+ self.message("Write data to clipboard.")
605
618
 
606
619
  ## --------------------------------
607
620
  ## Motion/Drag actions (override)
@@ -629,7 +642,7 @@ class LineProfile(LinePlot):
629
642
  if x.size:
630
643
  self.__fil.set_xy(list(chain([(x[0],0)], zip(x,y), [(x[-1],0)])))
631
644
  self.writeln()
632
-
645
+
633
646
  def OnLineWidth(self, evt):
634
647
  n = -2 if evt.key[-1] == '-' else 2
635
648
  self.set_linewidth(self.__linewidth + n)
@@ -648,7 +661,17 @@ class LineProfile(LinePlot):
648
661
  def OnDragLineBegin(self, evt):
649
662
  self.set_wxcursor(wx.CURSOR_SIZENS)
650
663
 
664
+ def OnDragTrace(self, evt):
665
+ """Show average value."""
666
+ y = self.calc_average()
667
+ if y is not None:
668
+ ## self.__hline.set_ydata([y])
669
+ ## self.__hline.set_visible(1)
670
+ ## self.canvas.draw_idle()
671
+ self.message(f"ya = {y:g}")
672
+
651
673
  def OnRegionLock(self, evt):
674
+ """Show FWHM region."""
652
675
  x, y = self.plotdata
653
676
  if x.size:
654
677
  xc, yc = evt.xdata, evt.ydata
@@ -676,12 +699,13 @@ class LineProfile(LinePlot):
676
699
  else:
677
700
  self.region = x[[0,-1]] # all y > yc
678
701
 
679
- self.__hline.set_ydata(yc)
702
+ self.__hline.set_ydata([yc])
680
703
  self.__hline.set_visible(1)
681
- self.message("y = {:g}, xr = {}".format(yc, self.region))
682
704
  self.draw()
705
+ self.message(f"yc = {yc:g}")
683
706
 
684
707
  def OnMarkPeaks(self, evt):
708
+ """Set markers on peaks."""
685
709
  x, y = self.plotdata
686
710
  if x.size:
687
711
  lw = 5
@@ -699,7 +723,9 @@ class LineProfile(LinePlot):
699
723
  self.Selector = x[peaks], y[peaks]
700
724
 
701
725
  def OnMarkErase(self, evt):
702
- del self.Selector
726
+ """Erase markers on peaks."""
727
+ ## del self.Selector
728
+ self.OnEscapeSelection(evt)
703
729
 
704
730
  def OnMarkSelectionBegin(self, evt):
705
731
  org = self.p_event
mwx/nutshell.py CHANGED
@@ -1682,7 +1682,7 @@ class Buffer(EditWindow, EditorInterface):
1682
1682
  self.EnsureCaretVisible()
1683
1683
  self.AnnotationSetStyle(lx, stc.STC_STYLE_ANNOTATION)
1684
1684
  self.AnnotationSetText(lx, msg)
1685
- self.message("- {}".format(e))
1685
+ self.message(e)
1686
1686
  ## print(msg, file=sys.__stderr__)
1687
1687
  else:
1688
1688
  self.code = code
@@ -2299,7 +2299,7 @@ class Nautilus(Shell, EditorInterface):
2299
2299
  obj.this = inspect.getmodule(obj)
2300
2300
  obj.shell = self # overwrite the facade <wx.py.shell.ShellFacade>
2301
2301
  except AttributeError:
2302
- ## print("- cannot overwrite target vars: {}".format(e))
2302
+ ## print("- cannot overwrite target vars:", e)
2303
2303
  pass
2304
2304
  self.parent.handler('title_window', obj)
2305
2305
 
@@ -2354,7 +2354,7 @@ class Nautilus(Shell, EditorInterface):
2354
2354
  wx.py.shell.USE_MAGIC = True
2355
2355
  wx.py.shell.magic = self.magic # called when USE_MAGIC
2356
2356
 
2357
- ## cf. sys.modules (shell.modules
2357
+ ## cf. sys.modules
2358
2358
  if not self.modules:
2359
2359
  force = wx.GetKeyState(wx.WXK_CONTROL)\
2360
2360
  & wx.GetKeyState(wx.WXK_SHIFT)
@@ -3292,7 +3292,7 @@ class Nautilus(Shell, EditorInterface):
3292
3292
  if lines:
3293
3293
  region = self.get_region(self.cline)
3294
3294
  self.pointer = region[0] + lines[-1] - 1
3295
- self.message("- {}".format(e))
3295
+ self.message(e)
3296
3296
  ## print(msg, file=sys.__stderr__)
3297
3297
  else:
3298
3298
  del self.pointer
@@ -3461,7 +3461,7 @@ class Nautilus(Shell, EditorInterface):
3461
3461
  try:
3462
3462
  modules = set(dir(import_module(text)))
3463
3463
  except ImportError as e:
3464
- self.message("\b failed: {}".format(e))
3464
+ self.message("\b failed:", e)
3465
3465
  return
3466
3466
  ## Add unimported module names.
3467
3467
  p = "{}.{}".format(text, hint)
mwx/py/__init__.py CHANGED
@@ -0,0 +1,2 @@
1
+ #! python3
2
+ """Plug-ins / Add-ins."""
mwx/py/ffmpeg_view.py ADDED
@@ -0,0 +1,257 @@
1
+ #! python3
2
+ """FFmpeg wrapper.
3
+ """
4
+ from subprocess import Popen, PIPE
5
+ import numpy as np
6
+ import os
7
+ import wx
8
+ import wx.media
9
+
10
+ from mwx.graphman import Layer, Frame
11
+ from mwx.controls import LParam, Icon, Button, TextCtrl
12
+
13
+
14
+ def read_info(path):
15
+ command = ['ffprobe',
16
+ '-i', path,
17
+ '-loglevel', 'quiet', # no verbose
18
+ '-print_format', 'json', # -format json
19
+ '-show_streams', # -streams info
20
+ ]
21
+ with Popen(command, stdout=PIPE, stderr=PIPE) as fp:
22
+ ret, err = fp.communicate()
23
+ if not err:
24
+ return eval(ret)
25
+
26
+
27
+ def capture_video(path, ss=0):
28
+ command = ['ffmpeg',
29
+ '-ss', f"{ss}", # Note: placing -ss before -i will be faster,
30
+ '-i', path, # but maybe not accurate.
31
+ '-frames:v', '1', # -frame one shot
32
+ '-f', 'rawvideo', # -format raw
33
+ '-pix_fmt', 'rgb24', # rgb24, gray, etc.
34
+ 'pipe:' # pipe to stdout: '-'
35
+ ]
36
+ bufsize = 4096 # w * h * 3
37
+ buf = b"" # bytearray()
38
+ with Popen(command, stdout=PIPE) as fp:
39
+ while 1:
40
+ s = fp.stdout.read(bufsize)
41
+ buf += s
42
+ if len(s) < bufsize:
43
+ break
44
+ return np.frombuffer(buf, np.uint8)
45
+
46
+
47
+ def export_video(path, crop, ss, to, filename):
48
+ command = ['ffmpeg',
49
+ '-i', path,
50
+ '-vf', f"{crop=}",
51
+ '-ss', f"{ss}",
52
+ '-to', f"{to}",
53
+ '-y', filename,
54
+ ]
55
+ print('>', ' '.join(command))
56
+ with Popen(command) as fp:
57
+ ret, err = fp.communicate()
58
+
59
+
60
+ class MyFileDropLoader(wx.FileDropTarget):
61
+ def __init__(self, target):
62
+ wx.FileDropTarget.__init__(self)
63
+ self.target = target
64
+
65
+ def OnDropFiles(self, x, y, filenames):
66
+ path = filenames[-1] # Only the last one will be loaded.
67
+ if len(filenames) > 1:
68
+ print("- Drop only one file please."
69
+ "Loading {!r} ...".format(path))
70
+ self.target.load_media(path)
71
+ return True
72
+
73
+
74
+ class Plugin(Layer):
75
+ """Media loader using FFMpeg (installation required).
76
+ """
77
+ menukey = "Plugins/Extensions/FFMpeg viewer"
78
+ ## menukey = "FFMpeg/"
79
+ dockable = False
80
+
81
+ def Init(self):
82
+ self.mc = wx.media.MediaCtrl()
83
+ self.mc.Create(self, size=(300,300),
84
+ style=wx.SIMPLE_BORDER,
85
+ szBackend=wx.media.MEDIABACKEND_WMP10
86
+ ## szBackend=wx.media.MEDIABACKEND_DIRECTSHOW
87
+ )
88
+ self.mc.ShowPlayerControls()
89
+ self.mc.Bind(wx.media.EVT_MEDIA_LOADED, self.OnMediaLoaded)
90
+ self.mc.Bind(wx.media.EVT_MEDIA_PAUSE, self.OnMediaPause)
91
+
92
+ self.mc.SetDropTarget(MyFileDropLoader(self))
93
+
94
+ self._path = None
95
+
96
+ self.ss = LParam("ss:", # range/value will be set when loaded later.
97
+ handler=self.set_offset,
98
+ updater=self.get_offset,
99
+ )
100
+ self.to = LParam("to:", # range/value will be set when loaded later.
101
+ handler=self.set_offset,
102
+ updater=self.get_offset,
103
+ )
104
+ self.crop = TextCtrl(self, icon="cut", size=(130,-1),
105
+ handler=self.set_crop,
106
+ updater=self.get_crop,
107
+ )
108
+
109
+ self.snp = Button(self, handler=self.snapshot, tip='Snapshot', icon='clock')
110
+ self.exp = Button(self, handler=self.export, tip="Export", icon='save')
111
+
112
+ self.rw = Button(self, handler=lambda v: self.seekdelta(-100), icon='|<-')
113
+ self.fw = Button(self, handler=lambda v: self.seekdelta(+100), icon='->|')
114
+
115
+ self.layout((self.mc,), expand=2)
116
+ self.layout((self.ss, self.to, self.rw, self.fw,
117
+ self.snp, self.crop, self.exp),
118
+ expand=0, row=7, style='button', lw=12, cw=0, tw=64)
119
+
120
+ self.menu[0:5] = [
121
+ (1, "&Load file", Icon('open'),
122
+ lambda v: self.load_media()),
123
+
124
+ (2, "&Snapshot", Icon('clock'),
125
+ lambda v: self.snapshot(),
126
+ lambda v: v.Enable(self._path is not None)),
127
+ (),
128
+ ]
129
+
130
+ self.parent.handler.bind("unknown_format", self.load_media)
131
+
132
+ def Destroy(self):
133
+ try:
134
+ self.parent.handler.unbind("unknown_format", self.load_media)
135
+ self.mc.Stop()
136
+ finally:
137
+ return Layer.Destroy(self)
138
+
139
+ def OnMediaLoaded(self, evt):
140
+ self.ss.range = (0, self.video_dur, 0.01)
141
+ self.to.range = (0, self.video_dur, 0.01)
142
+ self.Show()
143
+ evt.Skip()
144
+
145
+ def OnMediaPause(self, evt):
146
+ evt.Skip()
147
+
148
+ def load_media(self, path=None):
149
+ if path is None:
150
+ with wx.FileDialog(self, "Choose a media file",
151
+ style=wx.FD_OPEN|wx.FD_CHANGE_DIR|wx.FD_FILE_MUST_EXIST) as dlg:
152
+ if dlg.ShowModal() != wx.ID_OK:
153
+ return None
154
+ path = dlg.Path
155
+ self.mc.Load(path) # -> True (always)
156
+ self.info = read_info(path)
157
+ if self.info:
158
+ v = next(x for x in self.info['streams'] if x['codec_type'] == 'video')
159
+ ## self.video_fps = eval(v['r_frame_rate']) # real base frame rate
160
+ self.video_fps = eval(v['avg_frame_rate']) # averaged frame rate
161
+ self.video_dur = eval(v['duration']) # duration [s]
162
+ w, h = v['width'], v['height']
163
+ if v['tags'].get('rotate') in ('90', '270'):
164
+ w, h = h, w # transpose
165
+ self.video_size = w, h
166
+ self._path = path
167
+ self.message(f"Loaded {path!r} successfully.")
168
+ return True
169
+ else:
170
+ self.message(f"Failed to load file {path!r}.")
171
+ return False
172
+
173
+ DELTA = 1000 # correction ▲理由は不明 (WMP10 backend only?)
174
+
175
+ def set_offset(self, tc):
176
+ """Set offset value by referring to ss/to value."""
177
+ try:
178
+ self.mc.Seek(self.DELTA + int(tc.value * 1000))
179
+ except Exception:
180
+ pass
181
+
182
+ def get_offset(self, tc):
183
+ """Get offset value and assigns it to ss/to value."""
184
+ try:
185
+ tc.value = round(self.mc.Tell()) / 1000
186
+ except Exception:
187
+ pass
188
+
189
+ def set_crop(self):
190
+ """Set crop area (W:H:Left:Top) to roi."""
191
+ frame = self.graph.frame
192
+ if frame:
193
+ try:
194
+ w, h, xo, yo = eval(self.crop.Value.replace(':', ','))
195
+ xo -= 0.5 # Correction with half-pixel
196
+ yo -= 0.5 # to select left-top (not center) position
197
+ nx = xo, xo+w
198
+ ny = yo, yo+h
199
+ frame.region = frame.xyfrompixel(nx, ny)
200
+ except Exception:
201
+ self.message("Failed to evaluate crop text.")
202
+
203
+ def get_crop(self):
204
+ """Get crop area (W:H:Left:Top) from roi."""
205
+ crop = ''
206
+ frame = self.graph.frame
207
+ if frame:
208
+ nx, ny = frame.xytopixel(*frame.region)
209
+ if nx.size:
210
+ xo, yo = nx[0], ny[1]
211
+ xp, yp = nx[1], ny[0]
212
+ crop = "{}:{}:{}:{}".format(xp-xo, yp-yo, xo, yo)
213
+ if self._path and not crop:
214
+ crop = "{}:{}:0:0".format(*self.video_size)
215
+ self.crop.Value = crop
216
+
217
+ def seekdelta(self, offset):
218
+ """Seek relative position [ms]."""
219
+ if wx.GetKeyState(wx.WXK_SHIFT):
220
+ offset /= 10
221
+ try:
222
+ t = self.to.value + offset/1000
223
+ except Exception as e:
224
+ print(e)
225
+ else:
226
+ if self._path and 0 <= t < self.video_dur:
227
+ self.to.value = round(t, 3)
228
+ self.set_offset(self.to) # => seek
229
+
230
+ def snapshot(self):
231
+ """Create a snapshot of the current frame.
232
+ Load the snapshot image into the graph window.
233
+ """
234
+ if not self._path:
235
+ return
236
+ t = self.mc.Tell()
237
+ w, h = self.video_size
238
+ buf = capture_video(self._path, t/1000).reshape((h,w,3))
239
+ name = "{}-ss{}".format(os.path.basename(self._path), int(t))
240
+ self.graph.load(buf, name)
241
+
242
+ def export(self):
243
+ """Export the cropped / clipped data to a media file."""
244
+ if not self._path:
245
+ return
246
+ fout = "{}_clip".format(os.path.splitext(self._path)[0])
247
+ with wx.FileDialog(self, "Save as",
248
+ defaultFile=os.path.basename(fout),
249
+ wildcard="Media file (*.mp4)|*.mp4|"
250
+ "Animiation (*.gif)|*.gif",
251
+ style=wx.FD_SAVE|wx.FD_OVERWRITE_PROMPT) as dlg:
252
+ if dlg.ShowModal() != wx.ID_OK:
253
+ return
254
+ fout = dlg.Path
255
+ export_video(self._path,
256
+ self.crop.Value or "{}:{}:0:0".format(*self.video_size),
257
+ self.ss.value, self.to.value, fout)
mwx/py/fft_view.py ADDED
@@ -0,0 +1,79 @@
1
+ #! python3
2
+ """View of FFT/iFFT.
3
+ """
4
+ import wx
5
+ import numpy as np
6
+ from numpy.fft import fft2,ifft2,fftshift,ifftshift
7
+ ## from scipy.fftpack import fft,ifft,fft2,ifft2 Memory Leak? <scipy 0.16.1>
8
+ ## import cv2
9
+
10
+ from jgdk import Layer, Param
11
+ import editor as edi
12
+
13
+
14
+ class Plugin(Layer):
15
+ """FFT view.
16
+
17
+ FFT src (graph.buffer) to dst (output.buffer).
18
+ Note:
19
+ Rectangular regions will result in distorted patterns.
20
+ 長方形のリージョンは歪んだパターンになるので要注意
21
+ """
22
+ menukey = "Plugins/Extensions/&FFT view"
23
+ caption = "FFT view"
24
+
25
+ def Init(self):
26
+ self.pchk = wx.CheckBox(self, label="logical unit")
27
+ self.pchk.Value = True
28
+
29
+ self.ftor = Param("mask", (2,4,8,16,32,64)) # masking area factor of 1/2
30
+
31
+ self.layout((self.pchk,), title="normal FFT")
32
+ self.layout((self.ftor,), title="inverse FFT", style='chkbox', tw=32)
33
+
34
+ self.parent.define_key('C-f', self.newfft)
35
+ self.parent.define_key('C-S-f', self.newifft)
36
+
37
+ def Destroy(self):
38
+ self.parent.define_key('C-f', None)
39
+ self.parent.define_key('C-S-f', None)
40
+ return Layer.Destroy(self)
41
+
42
+ def newfft(self, evt):
43
+ """New FFT of graph to output."""
44
+ frame = self.graph.frame
45
+ if frame:
46
+ self.message("FFT execution...")
47
+ src = edi.fftcrop(frame.roi)
48
+ h, w = src.shape
49
+
50
+ dst = fftshift(fft2(src))
51
+
52
+ self.message("\b Loading image...")
53
+ u = 1 / w
54
+ if self.pchk.Value:
55
+ u /= frame.unit
56
+ self.output.load(dst, "*fft of {}*".format(frame.name),
57
+ localunit=u)
58
+ self.message("\b done")
59
+
60
+ def newifft(self, evt):
61
+ """New inverse FFT of output to graph."""
62
+ frame = self.output.frame
63
+ if frame:
64
+ self.message("iFFT execution...")
65
+ src = frame.roi
66
+ h, w = src.shape
67
+
68
+ if self.ftor.check:
69
+ y, x = np.ogrid[-h/2:h/2, -w/2:w/2]
70
+ mask = np.hypot(y,x) > w / self.ftor.value
71
+ src = src.copy() # apply mask to the copy
72
+ src[mask] = 0
73
+
74
+ dst = ifft2(ifftshift(src))
75
+
76
+ self.message("\b Loading image...")
77
+ self.graph.load(dst.real, "*ifft of {}*".format(frame.name),
78
+ localunit=1/w/frame.unit)
79
+ self.message("\b done")