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 CHANGED
@@ -1,7 +1,7 @@
1
1
  #! python3
2
2
  """mwxlib framework.
3
3
  """
4
- __version__ = "0.93.5"
4
+ __version__ = "0.93.7"
5
5
  __author__ = "Kazuya O'moto <komoto@jeol.co.jp>"
6
6
 
7
7
  from functools import wraps, partial
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 = self._split_paths(root)
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 = self._split_paths(root)
1149
+ _dirname, name = split_paths(root)
1151
1150
 
1152
1151
  plug = self.get_plug(name)
1153
- if plug and not force: # <plug:name> is already registered.
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 the plot.
301
- Called every time the drawing is updated.
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
- self.message("Copy image to clipboard.")
406
+ ## b = self.selected.get_visible()
407
+ c = self.cursor.visible
407
408
  try:
408
- b = self.selected.get_visible()
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.update_position()
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.update_position()
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
- """Copy - Write buffer data to clipboard."""
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
- """Paste - Read buffer data from clipboard."""
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
- 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
@@ -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
- 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/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")
@@ -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:logic-error: undefined state {!r}".format(key))
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:exception: {!r}".format(e),
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:warning: [{!r}] context newly created.".format(state))
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:warning: transaction may conflict.\n"
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:warning: transaction may contradict\n"
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:warning: cannot append new transaction ({!r} : {!r})\n"
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:warning: [{!r}] context does not exist.".format(state))
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:warning: No such transaction ({!r} : {!r})".format(state, event))
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:warning: removing action from context ({!r} : {!r})\n"
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mwxlib
3
- Version: 0.93.5
3
+ Version: 0.93.7
4
4
  Summary: A wrapper of matplotlib and wxPython (phoenix)
5
5
  Home-page: https://github.com/komoto48g/mwxlib
6
6
  Author: Kazuya O'moto
@@ -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,,
@@ -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,,