mwxlib 0.93.5__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/framework.py +1 -1
- mwx/graphman.py +16 -17
- mwx/matplot2.py +10 -14
- mwx/matplot2g.py +7 -6
- mwx/matplot2lg.py +40 -14
- mwx/py/__init__.py +2 -0
- mwx/py/ffmpeg_view.py +257 -0
- mwx/py/fft_view.py +79 -0
- mwx/py/frame_listview.py +283 -0
- mwx/py/line_profile.py +27 -0
- mwx/utilus.py +9 -9
- {mwxlib-0.93.5.dist-info → mwxlib-0.93.7.dist-info}/METADATA +1 -1
- mwxlib-0.93.7.dist-info/RECORD +27 -0
- mwxlib-0.93.5.dist-info/RECORD +0 -23
- {mwxlib-0.93.5.dist-info → mwxlib-0.93.7.dist-info}/LICENSE +0 -0
- {mwxlib-0.93.5.dist-info → mwxlib-0.93.7.dist-info}/WHEEL +0 -0
- {mwxlib-0.93.5.dist-info → mwxlib-0.93.7.dist-info}/top_level.txt +0 -0
mwx/framework.py
CHANGED
mwx/graphman.py
CHANGED
|
@@ -33,6 +33,19 @@ from .matplot2g import GraphPlot
|
|
|
33
33
|
from .matplot2lg import Histogram
|
|
34
34
|
|
|
35
35
|
|
|
36
|
+
def split_paths(obj):
|
|
37
|
+
"""Split obj path into dirname and basename.
|
|
38
|
+
The object can be module name:str, module, or class.
|
|
39
|
+
"""
|
|
40
|
+
if hasattr(obj, '__file__'): #<class 'module'>
|
|
41
|
+
obj = obj.__file__
|
|
42
|
+
elif isinstance(obj, type): #<class 'type'>
|
|
43
|
+
obj = inspect.getsourcefile(obj)
|
|
44
|
+
if obj.endswith(".py"):
|
|
45
|
+
obj, _ = os.path.splitext(obj)
|
|
46
|
+
return os.path.split(obj)
|
|
47
|
+
|
|
48
|
+
|
|
36
49
|
class Thread(object):
|
|
37
50
|
"""Thread manager for graphman.Layer
|
|
38
51
|
|
|
@@ -1062,20 +1075,6 @@ class Frame(mwx.Frame):
|
|
|
1062
1075
|
module.Plugin = _Plugin
|
|
1063
1076
|
return _Plugin
|
|
1064
1077
|
|
|
1065
|
-
@staticmethod
|
|
1066
|
-
def _split_paths(root):
|
|
1067
|
-
if hasattr(root, '__file__'): #<class 'module'>
|
|
1068
|
-
name = root.__file__
|
|
1069
|
-
elif isinstance(root, type): #<class 'type'>
|
|
1070
|
-
name = inspect.getsourcefile(root)
|
|
1071
|
-
else:
|
|
1072
|
-
name = root
|
|
1073
|
-
dirname = os.path.dirname(name)
|
|
1074
|
-
name = os.path.basename(name)
|
|
1075
|
-
if name.endswith(".py"):
|
|
1076
|
-
name, _ = os.path.splitext(name)
|
|
1077
|
-
return dirname, name
|
|
1078
|
-
|
|
1079
1078
|
def load_module(self, root):
|
|
1080
1079
|
"""Load module of plugin (internal use only).
|
|
1081
1080
|
|
|
@@ -1083,7 +1082,7 @@ class Frame(mwx.Frame):
|
|
|
1083
1082
|
This is called automatically from load_plug,
|
|
1084
1083
|
and should not be called directly from user.
|
|
1085
1084
|
"""
|
|
1086
|
-
dirname_, name =
|
|
1085
|
+
dirname_, name = split_paths(root)
|
|
1087
1086
|
|
|
1088
1087
|
## Update the include-path to load the module correctly.
|
|
1089
1088
|
if os.path.isdir(dirname_):
|
|
@@ -1147,10 +1146,10 @@ class Frame(mwx.Frame):
|
|
|
1147
1146
|
props = dict(show=show, dock=dock, layer=layer, pos=pos, row=row, prop=prop,
|
|
1148
1147
|
floating_pos=floating_pos, floating_size=floating_size)
|
|
1149
1148
|
|
|
1150
|
-
_dirname, name =
|
|
1149
|
+
_dirname, name = split_paths(root)
|
|
1151
1150
|
|
|
1152
1151
|
plug = self.get_plug(name)
|
|
1153
|
-
if plug and not force:
|
|
1152
|
+
if plug and not force:
|
|
1154
1153
|
self.update_pane(name, **props)
|
|
1155
1154
|
try:
|
|
1156
1155
|
if session:
|
mwx/matplot2.py
CHANGED
|
@@ -297,8 +297,8 @@ class MatplotPanel(wx.Panel):
|
|
|
297
297
|
self.cursor.visible = 1
|
|
298
298
|
|
|
299
299
|
def draw(self, art=None):
|
|
300
|
-
"""Draw
|
|
301
|
-
|
|
300
|
+
"""Draw plots.
|
|
301
|
+
Call each time the drawing should be updated.
|
|
302
302
|
"""
|
|
303
303
|
if isinstance(art, matplotlib.artist.Artist):
|
|
304
304
|
self.axes.draw_artist(art)
|
|
@@ -403,16 +403,16 @@ class MatplotPanel(wx.Panel):
|
|
|
403
403
|
|
|
404
404
|
def copy_to_clipboard(self):
|
|
405
405
|
"""Copy canvas image to clipboard."""
|
|
406
|
-
|
|
406
|
+
## b = self.selected.get_visible()
|
|
407
|
+
c = self.cursor.visible
|
|
407
408
|
try:
|
|
408
|
-
|
|
409
|
-
c = self.cursor.visible
|
|
410
|
-
self.selected.set_visible(0)
|
|
409
|
+
## self.selected.set_visible(0)
|
|
411
410
|
self.cursor.visible = 0
|
|
412
411
|
self.canvas.draw()
|
|
413
412
|
self.canvas.Copy_to_Clipboard()
|
|
413
|
+
self.message("Copy image to clipboard.")
|
|
414
414
|
finally:
|
|
415
|
-
self.selected.set_visible(b)
|
|
415
|
+
## self.selected.set_visible(b)
|
|
416
416
|
self.cursor.visible = c
|
|
417
417
|
self.canvas.draw()
|
|
418
418
|
|
|
@@ -604,10 +604,6 @@ class MatplotPanel(wx.Panel):
|
|
|
604
604
|
|
|
605
605
|
ZOOM_RATIO = 10**0.2
|
|
606
606
|
|
|
607
|
-
def update_position(self):
|
|
608
|
-
self.toolbar.update()
|
|
609
|
-
self.toolbar.push_current()
|
|
610
|
-
|
|
611
607
|
def OnDraw(self, evt):
|
|
612
608
|
"""Called before canvas.draw."""
|
|
613
609
|
pass
|
|
@@ -630,12 +626,14 @@ class MatplotPanel(wx.Panel):
|
|
|
630
626
|
def OnHomePosition(self, evt):
|
|
631
627
|
"""Go back to home position."""
|
|
632
628
|
self.toolbar.home()
|
|
633
|
-
self.
|
|
629
|
+
self.toolbar.update()
|
|
630
|
+
self.toolbar.push_current()
|
|
634
631
|
self.draw()
|
|
635
632
|
|
|
636
633
|
def OnEscapeSelection(self, evt):
|
|
637
634
|
"""Escape from selection."""
|
|
638
635
|
del self.Selector
|
|
636
|
+
self.canvas.draw_idle()
|
|
639
637
|
|
|
640
638
|
## def OnShiftLimit(self, evt, r=0.1):
|
|
641
639
|
## w = self.xlim[1] - self.xlim[0]
|
|
@@ -686,7 +684,6 @@ class MatplotPanel(wx.Panel):
|
|
|
686
684
|
## self.toolbar.set_cursor(1)
|
|
687
685
|
self.set_wxcursor(wx.CURSOR_ARROW)
|
|
688
686
|
self.toolbar.pan()
|
|
689
|
-
## self.draw()
|
|
690
687
|
self.handler.current_state = self.__prev # --> previous state of PAN
|
|
691
688
|
del self.__prev
|
|
692
689
|
|
|
@@ -701,7 +698,6 @@ class MatplotPanel(wx.Panel):
|
|
|
701
698
|
## self.toolbar.set_cursor(1)
|
|
702
699
|
self.set_wxcursor(wx.CURSOR_ARROW)
|
|
703
700
|
self.toolbar.zoom()
|
|
704
|
-
## self.draw()
|
|
705
701
|
self.handler.current_state = self.__prev # --> previous state of ZOOM
|
|
706
702
|
del self.__prev
|
|
707
703
|
|
mwx/matplot2g.py
CHANGED
|
@@ -863,7 +863,8 @@ class GraphPlot(MatplotPanel):
|
|
|
863
863
|
"""Reset display range (xylim's), update home position."""
|
|
864
864
|
if self.frame:
|
|
865
865
|
self.axes.axis(self.frame.get_extent()) # reset xlim and ylim
|
|
866
|
-
self.
|
|
866
|
+
self.toolbar.update()
|
|
867
|
+
self.toolbar.push_current()
|
|
867
868
|
self.draw()
|
|
868
869
|
|
|
869
870
|
def fit_to_canvas(self):
|
|
@@ -980,24 +981,24 @@ class GraphPlot(MatplotPanel):
|
|
|
980
981
|
clipboard_data = None
|
|
981
982
|
|
|
982
983
|
def write_buffer_to_clipboard(self):
|
|
983
|
-
"""
|
|
984
|
+
"""Write buffer data to clipboard."""
|
|
984
985
|
if not self.frame:
|
|
985
986
|
self.message("No frame")
|
|
986
987
|
return
|
|
987
988
|
try:
|
|
988
|
-
self.message("Write buffer to clipboard.")
|
|
989
989
|
name = self.frame.name
|
|
990
990
|
data = self.frame.roi
|
|
991
991
|
GraphPlot.clipboard_name = name
|
|
992
992
|
GraphPlot.clipboard_data = data
|
|
993
993
|
bins, vlim, img = imconvert(data, self.frame.vlim)
|
|
994
994
|
Clipboard.imwrite(img)
|
|
995
|
+
self.message("Write buffer to clipboard.")
|
|
995
996
|
except Exception as e:
|
|
996
|
-
self.message("- Failure in clipboard:", e)
|
|
997
997
|
traceback.print_exc()
|
|
998
|
+
self.message("- Failed to write to clipboard:", e)
|
|
998
999
|
|
|
999
1000
|
def read_buffer_from_clipboard(self):
|
|
1000
|
-
"""
|
|
1001
|
+
"""Read buffer data from clipboard."""
|
|
1001
1002
|
try:
|
|
1002
1003
|
name = GraphPlot.clipboard_name
|
|
1003
1004
|
data = GraphPlot.clipboard_data
|
|
@@ -1010,8 +1011,8 @@ class GraphPlot(MatplotPanel):
|
|
|
1010
1011
|
self.message("Read image from clipboard.")
|
|
1011
1012
|
self.load(Clipboard.imread())
|
|
1012
1013
|
except Exception as e:
|
|
1013
|
-
self.message("- No data in clipboard:", e)
|
|
1014
1014
|
traceback.print_exc()
|
|
1015
|
+
self.message("- No data in clipboard:", e)
|
|
1015
1016
|
|
|
1016
1017
|
def destroy_colorbar(self):
|
|
1017
1018
|
if self.cbar:
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
598
|
-
"""
|
|
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
|
|
@@ -678,10 +701,11 @@ class LineProfile(LinePlot):
|
|
|
678
701
|
|
|
679
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
|
-
|
|
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/py/__init__.py
CHANGED
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")
|
mwx/py/frame_listview.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#! python3
|
|
2
|
+
"""Property list of buffers.
|
|
3
|
+
"""
|
|
4
|
+
from pprint import pformat
|
|
5
|
+
import wx
|
|
6
|
+
from wx import aui
|
|
7
|
+
from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin
|
|
8
|
+
|
|
9
|
+
from mwx.framework import CtrlInterface, Menu, StatusBar
|
|
10
|
+
from mwx.controls import Icon, Icon2, Clipboard
|
|
11
|
+
from mwx.graphman import Layer
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CheckList(wx.ListCtrl, ListCtrlAutoWidthMixin, CtrlInterface):
|
|
15
|
+
"""CheckList of Graph buffers.
|
|
16
|
+
|
|
17
|
+
Note:
|
|
18
|
+
list item order = buffer order.
|
|
19
|
+
(リストアイテムとバッファの並び順 0..n は常に一致します)
|
|
20
|
+
"""
|
|
21
|
+
@property
|
|
22
|
+
def selected_items(self):
|
|
23
|
+
return filter(self.IsSelected, range(self.ItemCount))
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def checked_items(self):
|
|
27
|
+
return filter(self.IsItemChecked, range(self.ItemCount))
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def focused_item(self):
|
|
31
|
+
return self.FocusedItem
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def all_items(self):
|
|
35
|
+
rows = range(self.ItemCount)
|
|
36
|
+
cols = range(self.ColumnCount)
|
|
37
|
+
## return [[self.GetItemText(j, k) for k in cols] for j in rows]
|
|
38
|
+
for j in rows:
|
|
39
|
+
yield [self.GetItemText(j, k) for k in cols]
|
|
40
|
+
|
|
41
|
+
def __init__(self, parent, target, **kwargs):
|
|
42
|
+
wx.ListCtrl.__init__(self, parent, size=(400,130),
|
|
43
|
+
style=wx.LC_REPORT|wx.LC_HRULES, **kwargs)
|
|
44
|
+
ListCtrlAutoWidthMixin.__init__(self)
|
|
45
|
+
CtrlInterface.__init__(self)
|
|
46
|
+
|
|
47
|
+
self.EnableCheckBoxes()
|
|
48
|
+
|
|
49
|
+
self.parent = parent
|
|
50
|
+
self.Target = target
|
|
51
|
+
|
|
52
|
+
self.__dir = True
|
|
53
|
+
|
|
54
|
+
_alist = ( # assoc-list of column names
|
|
55
|
+
("id", 42),
|
|
56
|
+
("name", 160),
|
|
57
|
+
("shape", 90),
|
|
58
|
+
("dtype", 60),
|
|
59
|
+
("Mb", 40),
|
|
60
|
+
("unit", 60),
|
|
61
|
+
("annotation", 240),
|
|
62
|
+
)
|
|
63
|
+
for k, (name, w) in enumerate(_alist):
|
|
64
|
+
self.InsertColumn(k, name, width=w)
|
|
65
|
+
|
|
66
|
+
for j, frame in enumerate(self.Target.all_frames):
|
|
67
|
+
self.InsertItem(j, str(j))
|
|
68
|
+
self.UpdateInfo(frame) # update all --> 計算が入ると時間がかかる
|
|
69
|
+
|
|
70
|
+
self.handler.update({
|
|
71
|
+
0 : {
|
|
72
|
+
'Lbutton dblclick' : (0, self.OnShowItems), # -> frame_shown
|
|
73
|
+
'enter pressed' : (0, self.OnShowItems), # -> frame_shown
|
|
74
|
+
'delete pressed' : (0, self.OnRemoveItems), # -> frame_removed/shown
|
|
75
|
+
'f2 pressed' : (0, self.OnEditAnnotation),
|
|
76
|
+
'C-a pressed' : (0, self.OnSelectAllItems),
|
|
77
|
+
'C-o pressed' : (0, self.OnLoadItems),
|
|
78
|
+
'C-s pressed' : (0, self.OnSaveItems),
|
|
79
|
+
'M-up pressed' : (0, self.Target.OnPageUp),
|
|
80
|
+
'M-down pressed' : (0, self.Target.OnPageDown),
|
|
81
|
+
},
|
|
82
|
+
})
|
|
83
|
+
self.handler.clear(0)
|
|
84
|
+
|
|
85
|
+
self.Bind(wx.EVT_LIST_COL_CLICK, self.OnSortItems)
|
|
86
|
+
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
|
|
87
|
+
|
|
88
|
+
self.context = { # bound to the target
|
|
89
|
+
None: {
|
|
90
|
+
'frame_shown' : [ None, self.on_frame_shown ],
|
|
91
|
+
'frame_hidden' : [ None, self.on_frame_hidden ],
|
|
92
|
+
'frame_loaded' : [ None, self.on_frame_loaded ],
|
|
93
|
+
'frame_removed' : [ None, self.on_frames_removed ],
|
|
94
|
+
'frame_modified' : [ None, self.UpdateInfo ],
|
|
95
|
+
'frame_updated' : [ None, self.UpdateInfo ],
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
self.Target.handler.append(self.context)
|
|
99
|
+
|
|
100
|
+
def copy(all=True):
|
|
101
|
+
frames = self.Target.all_frames
|
|
102
|
+
if frames:
|
|
103
|
+
frame = frames[self.focused_item]
|
|
104
|
+
if all:
|
|
105
|
+
text = pformat(frame.attributes, sort_dicts=0)
|
|
106
|
+
else:
|
|
107
|
+
text = "{}\n{}".format(frame.name, frame.annotation)
|
|
108
|
+
Clipboard.write(text)
|
|
109
|
+
|
|
110
|
+
self.menu = [
|
|
111
|
+
(101, "&Edit annotation", "Edit annotation", Icon('pencil'),
|
|
112
|
+
self.OnEditAnnotation),
|
|
113
|
+
(),
|
|
114
|
+
(102, "Copy info", Icon('copy'),
|
|
115
|
+
lambda v: copy(0),
|
|
116
|
+
lambda v: v.Enable(len(list(self.selected_items)))),
|
|
117
|
+
|
|
118
|
+
(103, "Copy ALL data", Icon2('copy', '+'),
|
|
119
|
+
lambda v: copy(),
|
|
120
|
+
lambda v: v.Enable(len(list(self.selected_items)))),
|
|
121
|
+
]
|
|
122
|
+
self.Bind(wx.EVT_CONTEXT_MENU,
|
|
123
|
+
lambda v: Menu.Popup(self, self.menu))
|
|
124
|
+
|
|
125
|
+
def Destroy(self):
|
|
126
|
+
try:
|
|
127
|
+
self.Target.handler.remove(self.context)
|
|
128
|
+
finally:
|
|
129
|
+
return wx.ListCtrl.Destroy(self)
|
|
130
|
+
|
|
131
|
+
def UpdateInfo(self, frame):
|
|
132
|
+
ls = ("{}".format(frame.index),
|
|
133
|
+
"{}".format(frame.name),
|
|
134
|
+
"{}".format(frame.buffer.shape),
|
|
135
|
+
"{}".format(frame.buffer.dtype),
|
|
136
|
+
"{:.1f}".format(frame.buffer.nbytes/1e6),
|
|
137
|
+
"{:g}{}".format(frame.unit, '*' if frame.localunit else ''),
|
|
138
|
+
"{}".format(frame.annotation),
|
|
139
|
+
)
|
|
140
|
+
j = frame.index
|
|
141
|
+
for k, v in enumerate(ls):
|
|
142
|
+
self.SetItem(j, k, v)
|
|
143
|
+
if frame.pathname:
|
|
144
|
+
self.CheckItem(j)
|
|
145
|
+
|
|
146
|
+
def OnShowItems(self, evt):
|
|
147
|
+
self.Target.select(self.focused_item)
|
|
148
|
+
|
|
149
|
+
def OnRemoveItems(self, evt):
|
|
150
|
+
del self.Target[self.selected_items]
|
|
151
|
+
|
|
152
|
+
def OnSortItems(self, evt): #<wx._controls.ListEvent>
|
|
153
|
+
col = evt.Column
|
|
154
|
+
if col == 0: # reverse the first column
|
|
155
|
+
self.__dir = False
|
|
156
|
+
self.__dir = not self.__dir # toggle 0:ascend/1:descend
|
|
157
|
+
|
|
158
|
+
frames = self.Target.all_frames
|
|
159
|
+
if frames:
|
|
160
|
+
def _eval(x):
|
|
161
|
+
try:
|
|
162
|
+
return eval(x[col].replace('*', '')) # localunit* とか
|
|
163
|
+
except Exception:
|
|
164
|
+
return x[col]
|
|
165
|
+
frame = self.Target.frame
|
|
166
|
+
items = sorted(self.all_items, reverse=self.__dir, key=_eval)
|
|
167
|
+
frames[:] = [frames[int(c[0])] for c in items] # sort by new Id of items
|
|
168
|
+
|
|
169
|
+
lc = list(self.checked_items)
|
|
170
|
+
|
|
171
|
+
for j, c in enumerate(items):
|
|
172
|
+
self.Select(j, False)
|
|
173
|
+
self.CheckItem(j, int(c[0]) in lc)
|
|
174
|
+
for k, v in enumerate(c[1:]): # update data except for id(0)
|
|
175
|
+
self.SetItem(j, k+1, v)
|
|
176
|
+
self.Target.select(frame) # invokes [frame_shown] to select the item
|
|
177
|
+
|
|
178
|
+
def OnSelectAllItems(self, evt):
|
|
179
|
+
for j in range(self.ItemCount):
|
|
180
|
+
self.Select(j)
|
|
181
|
+
|
|
182
|
+
def OnLoadItems(self, evt):
|
|
183
|
+
self.parent.parent.import_index(view=self.Target)
|
|
184
|
+
|
|
185
|
+
def OnSaveItems(self, evt):
|
|
186
|
+
frames = self.Target.all_frames
|
|
187
|
+
selected_frames = [frames[j] for j in self.selected_items]
|
|
188
|
+
if selected_frames:
|
|
189
|
+
self.parent.message("Exporting {} frames.".format(len(selected_frames)))
|
|
190
|
+
self.parent.parent.export_index(frames=selected_frames)
|
|
191
|
+
else:
|
|
192
|
+
self.parent.message("No frame selected.")
|
|
193
|
+
|
|
194
|
+
def OnEditAnnotation(self, evt):
|
|
195
|
+
frames = self.Target.all_frames
|
|
196
|
+
if frames:
|
|
197
|
+
frame = frames[self.focused_item]
|
|
198
|
+
with wx.TextEntryDialog(self, frame.name,
|
|
199
|
+
'Enter an annotation', frame.annotation) as dlg:
|
|
200
|
+
if dlg.ShowModal() == wx.ID_OK:
|
|
201
|
+
frame.annotation = dlg.Value
|
|
202
|
+
|
|
203
|
+
def OnItemSelected(self, evt):
|
|
204
|
+
frames = self.Target.all_frames
|
|
205
|
+
frame = frames[evt.Index]
|
|
206
|
+
self.parent.message(frame.pathname)
|
|
207
|
+
evt.Skip()
|
|
208
|
+
|
|
209
|
+
## --------------------------------
|
|
210
|
+
## Actions of frame-handler
|
|
211
|
+
## --------------------------------
|
|
212
|
+
|
|
213
|
+
def on_frame_loaded(self, frame):
|
|
214
|
+
j = frame.index
|
|
215
|
+
self.InsertItem(j, str(j))
|
|
216
|
+
for k in range(j+1, self.ItemCount): # id(0) を更新する
|
|
217
|
+
self.SetItem(k, 0, str(k))
|
|
218
|
+
self.UpdateInfo(frame)
|
|
219
|
+
|
|
220
|
+
def on_frame_shown(self, frame):
|
|
221
|
+
j = frame.index
|
|
222
|
+
self.SetItemFont(j, self.Font.Bold())
|
|
223
|
+
self.Select(j)
|
|
224
|
+
self.Focus(j)
|
|
225
|
+
|
|
226
|
+
def on_frame_hidden(self, frame):
|
|
227
|
+
j = frame.index
|
|
228
|
+
self.SetItemFont(j, self.Font)
|
|
229
|
+
self.Select(j, False)
|
|
230
|
+
|
|
231
|
+
def on_frames_removed(self, indices):
|
|
232
|
+
for j in reversed(indices):
|
|
233
|
+
self.DeleteItem(j)
|
|
234
|
+
for k in range(self.ItemCount): # id(0) を更新する
|
|
235
|
+
self.SetItem(k, 0, str(k))
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class Plugin(Layer):
|
|
239
|
+
"""Property list of Grpah buffers.
|
|
240
|
+
"""
|
|
241
|
+
menukey = "Plugins/Extensions/&Buffer listbox\tCtrl+b"
|
|
242
|
+
caption = "Property list"
|
|
243
|
+
dockable = False
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def all_pages(self):
|
|
247
|
+
return [self.nb.GetPage(i) for i in range(self.nb.PageCount)]
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def message(self):
|
|
251
|
+
return self.statusline
|
|
252
|
+
|
|
253
|
+
def Init(self):
|
|
254
|
+
self.nb = aui.AuiNotebook(self, size=(400,150),
|
|
255
|
+
style = (aui.AUI_NB_DEFAULT_STYLE|aui.AUI_NB_RIGHT)
|
|
256
|
+
&~(aui.AUI_NB_CLOSE_ON_ACTIVE_TAB|aui.AUI_NB_MIDDLE_CLICK_CLOSE)
|
|
257
|
+
)
|
|
258
|
+
self.attach(self.graph, "graph")
|
|
259
|
+
self.attach(self.output, "output")
|
|
260
|
+
|
|
261
|
+
self.statusline = StatusBar(self)
|
|
262
|
+
self.layout((
|
|
263
|
+
self.nb,
|
|
264
|
+
(self.statusline, 0, wx.EXPAND),
|
|
265
|
+
),
|
|
266
|
+
expand=2, border=0, vspacing=0,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def on_focus_set(v):
|
|
270
|
+
self.parent.select_view(self.nb.CurrentPage.Target)
|
|
271
|
+
v.Skip()
|
|
272
|
+
|
|
273
|
+
self.nb.Bind(wx.EVT_CHILD_FOCUS, on_focus_set)
|
|
274
|
+
|
|
275
|
+
def attach(self, target, caption):
|
|
276
|
+
if target not in [lc.Target for lc in self.all_pages]:
|
|
277
|
+
lc = CheckList(self, target)
|
|
278
|
+
self.nb.AddPage(lc, caption)
|
|
279
|
+
|
|
280
|
+
def detach(self, target):
|
|
281
|
+
for k, lc in enumerate(self.all_pages):
|
|
282
|
+
if target is lc.Target:
|
|
283
|
+
self.nb.DeletePage(k)
|
mwx/py/line_profile.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#! python3
|
|
2
|
+
"""Line profile.
|
|
3
|
+
"""
|
|
4
|
+
from mwx.graphman import Layer
|
|
5
|
+
from mwx.matplot2lg import LineProfile
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Plugin(Layer):
|
|
9
|
+
"""Line profile of the currently selected buffers.
|
|
10
|
+
"""
|
|
11
|
+
menukey = "Plugins/Extensions/&Line profile\tCtrl+l"
|
|
12
|
+
caption = "Line profile"
|
|
13
|
+
dockable = False
|
|
14
|
+
|
|
15
|
+
def Init(self):
|
|
16
|
+
self.plot = LineProfile(self, log=self.message, size=(300,200))
|
|
17
|
+
|
|
18
|
+
self.layout((self.plot,), expand=2, border=0)
|
|
19
|
+
|
|
20
|
+
@self.handler.bind('page_shown')
|
|
21
|
+
def activate(v):
|
|
22
|
+
self.plot.attach(*self.parent.graphic_windows)
|
|
23
|
+
self.plot.linplot(self.parent.selected_view.frame)
|
|
24
|
+
|
|
25
|
+
@self.handler.bind('page_closed')
|
|
26
|
+
def deactivate(v):
|
|
27
|
+
self.plot.detach(*self.parent.graphic_windows)
|
mwx/utilus.py
CHANGED
|
@@ -570,7 +570,7 @@ class FSM(dict):
|
|
|
570
570
|
self.update(contexts)
|
|
571
571
|
|
|
572
572
|
def __missing__(self, key):
|
|
573
|
-
raise Exception("FSM
|
|
573
|
+
raise Exception("FSM logic-error: undefined state {!r}".format(key))
|
|
574
574
|
|
|
575
575
|
def __repr__(self):
|
|
576
576
|
return "<{} object at 0x{:X}>".format(self.__class__.__name__, id(self))
|
|
@@ -662,7 +662,7 @@ class FSM(dict):
|
|
|
662
662
|
except BdbQuit:
|
|
663
663
|
pass
|
|
664
664
|
except Exception as e:
|
|
665
|
-
self.dump("- FSM
|
|
665
|
+
self.dump("- FSM exception: {!r}".format(e),
|
|
666
666
|
" event : {}".format(event),
|
|
667
667
|
" from : {}".format(self.__prev_state),
|
|
668
668
|
" to : {}".format(self.__state),
|
|
@@ -799,7 +799,7 @@ class FSM(dict):
|
|
|
799
799
|
warnings.warn(msg, stacklevel=3)
|
|
800
800
|
|
|
801
801
|
if state not in self:
|
|
802
|
-
warn("- FSM
|
|
802
|
+
warn("- FSM warning: [{!r}] context newly created.".format(state))
|
|
803
803
|
self[state] = SSM() # new context
|
|
804
804
|
|
|
805
805
|
context = self[state]
|
|
@@ -808,14 +808,14 @@ class FSM(dict):
|
|
|
808
808
|
|
|
809
809
|
if event in context:
|
|
810
810
|
if state2 != context[event][0]:
|
|
811
|
-
warn("- FSM
|
|
811
|
+
warn("- FSM warning: transaction may conflict.\n"
|
|
812
812
|
" The state {2!r} and the original state is not the same."
|
|
813
813
|
" {0!r} : {1!r} --> {2!r}".format(event, state, state2))
|
|
814
814
|
pass
|
|
815
815
|
context[event][0] = state2 # update transition
|
|
816
816
|
else:
|
|
817
817
|
## if state2 not in self:
|
|
818
|
-
## warn("- FSM
|
|
818
|
+
## warn("- FSM warning: transaction may contradict\n"
|
|
819
819
|
## " The state {2!r} is not found in the contexts."
|
|
820
820
|
## " {0!r} : {1!r} --> {2!r}".format(event, state, state2))
|
|
821
821
|
## pass
|
|
@@ -829,7 +829,7 @@ class FSM(dict):
|
|
|
829
829
|
try:
|
|
830
830
|
transaction.append(action)
|
|
831
831
|
except AttributeError:
|
|
832
|
-
warn("- FSM
|
|
832
|
+
warn("- FSM warning: cannot append new transaction ({!r} : {!r})\n"
|
|
833
833
|
" The transaction must be a list, not a tuple".format(state, event))
|
|
834
834
|
return action
|
|
835
835
|
|
|
@@ -847,12 +847,12 @@ class FSM(dict):
|
|
|
847
847
|
warnings.warn(msg, stacklevel=3)
|
|
848
848
|
|
|
849
849
|
if state not in self:
|
|
850
|
-
warn("- FSM
|
|
850
|
+
warn("- FSM warning: [{!r}] context does not exist.".format(state))
|
|
851
851
|
return
|
|
852
852
|
|
|
853
853
|
context = self[state]
|
|
854
854
|
if event not in context:
|
|
855
|
-
warn("- FSM
|
|
855
|
+
warn("- FSM warning: No such transaction ({!r} : {!r})".format(state, event))
|
|
856
856
|
return
|
|
857
857
|
|
|
858
858
|
transaction = context[event]
|
|
@@ -866,7 +866,7 @@ class FSM(dict):
|
|
|
866
866
|
transaction.remove(action)
|
|
867
867
|
return True
|
|
868
868
|
except AttributeError:
|
|
869
|
-
warn("- FSM
|
|
869
|
+
warn("- FSM warning: removing action from context ({!r} : {!r})\n"
|
|
870
870
|
" The transaction must be a list, not a tuple".format(state, event))
|
|
871
871
|
return False
|
|
872
872
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
mwx/__init__.py,sha256=5B4YSOuijG1Uo5-FLtLHGB52Cp_F4vnN--4wGPBx7do,2398
|
|
2
|
+
mwx/bookshelf.py,sha256=FrissUYdGXLABOzJmMaQU6GXvu6n_9DVW3d5wGwQzjM,6613
|
|
3
|
+
mwx/controls.py,sha256=9C1sXBwQl-oZtCb3rXoT-doW48Sv1J359toNq9ScF9M,47173
|
|
4
|
+
mwx/framework.py,sha256=HiXX52Go64R167S9GiKs1SEbkxx-sMps88VDI9mIw5w,73951
|
|
5
|
+
mwx/graphman.py,sha256=SKYJFxi-TqWOALRczYz-M-2ZYEbqiZuAuKCfEIedctY,70156
|
|
6
|
+
mwx/images.py,sha256=mrnUYH12I3XLVSZcEXlpVltX0XMxufbl2yRvDIQJZqc,49957
|
|
7
|
+
mwx/matplot2.py,sha256=qaF_gvLoLn-TimLbRR59KUavNr1ZpZQdSMqjzJk47rk,32682
|
|
8
|
+
mwx/matplot2g.py,sha256=Txcz3yusiFF0j1QnAS0dCGTbGrkx4VC6BZbWbjIRZnw,65585
|
|
9
|
+
mwx/matplot2lg.py,sha256=nKXxy1XZRLTo0WijeIGd0WtQrlSpI1cxkGRwCktrJjY,27263
|
|
10
|
+
mwx/mgplt.py,sha256=ITzxA97yDwr_35BUk5OqnyskSuKVDbpf2AQCKY1jHTI,5671
|
|
11
|
+
mwx/nutshell.py,sha256=5HErOObnOprMc6gYnXV7wY-ALnlwNpmhV8f0EyipYA4,135816
|
|
12
|
+
mwx/utilus.py,sha256=THDxQ-QqbHYVc8iX8qN9yxvfcf7Pvpm7sfTP9ipYvzs,37040
|
|
13
|
+
mwx/wxmon.py,sha256=Qk86VbuuW2rR46pqEYLur13G_aloWz5SVv6sib30YY0,12717
|
|
14
|
+
mwx/wxpdb.py,sha256=2z3ZD9Oo1H-ONBHlaprkB9hrTmAI7o03sqO46ppEFE4,19129
|
|
15
|
+
mwx/wxwil.py,sha256=JK1du4i1RVMbDLqN8jLRDSu_JhKEp4mhHVMElzo4yoE,5578
|
|
16
|
+
mwx/wxwit.py,sha256=2gFHi-8nwWRh26RdvZ_AUP--8PDjJB8TUU72y2fBUWM,7557
|
|
17
|
+
mwx/py/__init__.py,sha256=NzBazvjN9KMFoylsHsLiNOHbcDrNflgSwEFlkT12Rnw,39
|
|
18
|
+
mwx/py/ffmpeg_view.py,sha256=vUYNybIJsF1JGkDzjBgDyBQvDh8e1oKHlEMY5Fwc8L4,9399
|
|
19
|
+
mwx/py/fft_view.py,sha256=1bc6e6muXoo2EHV-w1w4glB7ecPjc6aQXlbX-UplC6I,2642
|
|
20
|
+
mwx/py/filling.py,sha256=KaHooM32hrGGgqw75Cbt8lAvACwC6RXadob9LGgNnEc,16806
|
|
21
|
+
mwx/py/frame_listview.py,sha256=T-2xSv_D2bk9fJ64aiSEe1rJRTeqaIpLVAYEUXW5vz8,10110
|
|
22
|
+
mwx/py/line_profile.py,sha256=WJB5z7F53yg4dII2R36IFZvtkSOGWT669b1HmAAXSnQ,816
|
|
23
|
+
mwxlib-0.93.7.dist-info/LICENSE,sha256=PGtRKCaTkmUDlBQwpptJAxJtdqxIUtAmdBsaT9nUVkA,1091
|
|
24
|
+
mwxlib-0.93.7.dist-info/METADATA,sha256=7uW6eS4CsshRUk0W1Qf7o4qqcpVZNJjbA-KKyWW-zQc,1925
|
|
25
|
+
mwxlib-0.93.7.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
26
|
+
mwxlib-0.93.7.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
|
|
27
|
+
mwxlib-0.93.7.dist-info/RECORD,,
|
mwxlib-0.93.5.dist-info/RECORD
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
mwx/__init__.py,sha256=5B4YSOuijG1Uo5-FLtLHGB52Cp_F4vnN--4wGPBx7do,2398
|
|
2
|
-
mwx/bookshelf.py,sha256=FrissUYdGXLABOzJmMaQU6GXvu6n_9DVW3d5wGwQzjM,6613
|
|
3
|
-
mwx/controls.py,sha256=9C1sXBwQl-oZtCb3rXoT-doW48Sv1J359toNq9ScF9M,47173
|
|
4
|
-
mwx/framework.py,sha256=SeFOWD9YzxgP5WlkYFeONWj9EoToH7IQ5LmyJbECqMw,73951
|
|
5
|
-
mwx/graphman.py,sha256=0ppruAsbvUK6vUKnRc3-6gNW83V8XVp9Xr8mQrAaLTI,70267
|
|
6
|
-
mwx/images.py,sha256=mrnUYH12I3XLVSZcEXlpVltX0XMxufbl2yRvDIQJZqc,49957
|
|
7
|
-
mwx/matplot2.py,sha256=g902yipGCiO5SARTSafbE3c6-ZkQ-JbOkXSo7iltnDQ,32761
|
|
8
|
-
mwx/matplot2g.py,sha256=d0g48JhBWvqqmHQKvuwEAQqa8ol9MX8UvEHU__hQiz4,65552
|
|
9
|
-
mwx/matplot2lg.py,sha256=mCeT_OBwsq7OamW56WRg9YqyXe6WPJrjCfCVAzDvSlw,26343
|
|
10
|
-
mwx/mgplt.py,sha256=ITzxA97yDwr_35BUk5OqnyskSuKVDbpf2AQCKY1jHTI,5671
|
|
11
|
-
mwx/nutshell.py,sha256=5HErOObnOprMc6gYnXV7wY-ALnlwNpmhV8f0EyipYA4,135816
|
|
12
|
-
mwx/utilus.py,sha256=IaB9RYPatprJXsulyrM2kEcVr-3ji0M88PUYBr9Nx6k,37040
|
|
13
|
-
mwx/wxmon.py,sha256=Qk86VbuuW2rR46pqEYLur13G_aloWz5SVv6sib30YY0,12717
|
|
14
|
-
mwx/wxpdb.py,sha256=2z3ZD9Oo1H-ONBHlaprkB9hrTmAI7o03sqO46ppEFE4,19129
|
|
15
|
-
mwx/wxwil.py,sha256=JK1du4i1RVMbDLqN8jLRDSu_JhKEp4mhHVMElzo4yoE,5578
|
|
16
|
-
mwx/wxwit.py,sha256=2gFHi-8nwWRh26RdvZ_AUP--8PDjJB8TUU72y2fBUWM,7557
|
|
17
|
-
mwx/py/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
mwx/py/filling.py,sha256=KaHooM32hrGGgqw75Cbt8lAvACwC6RXadob9LGgNnEc,16806
|
|
19
|
-
mwxlib-0.93.5.dist-info/LICENSE,sha256=PGtRKCaTkmUDlBQwpptJAxJtdqxIUtAmdBsaT9nUVkA,1091
|
|
20
|
-
mwxlib-0.93.5.dist-info/METADATA,sha256=-z6_xKYj1N7D9gfLX6jJ6Yn1fPXwjnMvJO1utGox9Cc,1925
|
|
21
|
-
mwxlib-0.93.5.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
22
|
-
mwxlib-0.93.5.dist-info/top_level.txt,sha256=SI1Mh118AstnUFGPNq5aMNKiAnVNmZk1S9Ij-OwAEpY,4
|
|
23
|
-
mwxlib-0.93.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|