1  import sys 
  2   
  3  WXVER = '2.8' 
  4  WX_GE_2_8 = True 
  5   
  6   
  7  if not hasattr(sys, "frozen"): 
  8      import wxversion 
  9      if wxversion.checkInstalled(WXVER): 
 10          wxversion.select(WXVER) 
 11       
 12      else: 
 13          import wx, webbrowser 
 14          app = wx.PySimpleApp() 
 15          retval = wx.MessageBox("This software is optimised for wxPython version %s, but you don't appear to have it installed.\nWould you like to install the latest version?" % WXVER, "wxPython Version Error", style = wx.YES_NO) 
 16          print "Return valur %s" % retval 
 17          if(retval == wx.YES): 
 18              app.MainLoop() 
 19              webbrowser.open("http://wxPython.org/") 
 20              sys.exit() 
 21          WX_GE_2_8 = False 
 22   
 23  import wx 
 24  import wx.html 
 25  import  wx.lib.newevent 
 26  import wx.lib.colourdb 
 27  import random 
 28   
 29  try: 
 30      import numpy as N 
 31       
 32  except ImportError: 
 33      errorText = ( 
 34          "The FloatCanvas requires the numpy module, version 1.* \n\n" 
 35          "You can get info about it at:\n" 
 36          "http://numpy.scipy.org/\n\n" 
 37      ) 
 38   
 39  try: 
 40      from floatcanvas import NavCanvas, FloatCanvas 
 41  except ImportError: 
 42      from wx.lib.floatcanvas import NavCanvas, FloatCanvas 
 43   
 44  import thread 
 45  from mod_braun import * 
 46  from config import * 
 47  import braun_help 
 48   
 49  (UpdateLineEvent, EVT_UPDATE_GRAPH) = wx.lib.newevent.NewEvent() 
 53      """Convert paths to the platform-specific separator""" 
 54      import os 
 55      st = apply(os.path.join, tuple(path.split('/'))) 
 56       
 57      if path.startswith('/'): 
 58          st = '/' + st 
 59      return st 
  60   
 61  if 0: 
 63 -            def __init__(self, MainWindowClass, queue): 
  64                  self.queue = queue 
 65                  self.MainWindowClass = MainWindowClass 
 66                  bmp = wx.Image(opj(SPLASH_PATH)).ConvertToBitmap() 
 67                  style = wx.SPLASH_CENTRE_ON_SCREEN | wx.SPLASH_TIMEOUT 
 68                  wx.SplashScreen.__init__(self, bmp, style, 3000, None, -1) 
 69                  self.Bind(wx.EVT_CLOSE, self.OnClose) 
 70                  wx.Yield() 
 71                  self.ShowMain() 
  72   
 74                  self.Hide() 
 75                  evt.Skip() 
  76   
 78                  frame = self.MainWindowClass(self.queue) 
 79                  frame.Show() 
   80   
 82      "Class to display some HTML documentation in a dialog window" 
 83      text = braun_help.TEXT 
 85         wx.Dialog.__init__(self, parent, -1, 'Braun help') 
 86         html = wx.html.HtmlWindow(self, -1, size=(420, -1)) 
 87         py_version = sys.version.split()[0] 
 88         html.SetPage(self.text) 
 89         btn = html.FindWindowById(wx.ID_OK) 
 90         ir = html.GetInternalRepresentation() 
 91         html.SetSize( (ir.GetWidth()+25, ir.GetHeight()+25) ) 
 92         self.SetClientSize(html.GetSize()) 
 93         self.CentreOnParent(wx.BOTH) 
   94   
 96      """ 
 97   
 98      The SmoothLine class is identical to the Line class except that it uses a 
 99      GC rather than a DC. 
100   
101      """ 
102 -    def _Draw(self, dc , WorldToPixel, ScaleWorldToPixel, HTdc=None): 
 103          Points = WorldToPixel(self.Points) 
104          GC = wx.GraphicsContext.Create(dc) 
105          c = wx.Color() 
106          c.SetFromName(self.LineColor) 
107          r,g,b = c.Get() 
108          c = wx.Color(r,g,b,LINE_OPACITY) 
109          GC.SetPen(wx.Pen(c, self.LineWidth, self.LineStyleList[self.LineStyle])) 
110          GC.DrawLines(Points) 
  111   
113      _highest_index = 0 
115          self.index = 0 
116          self.size = RING_BUFFER_SIZE 
117          self.current_value_index = RING_BUFFER_SIZE - 1 
118          self.changed = False 
119          self.started = False 
120          self.__content = [0] * self.size 
 122          self.index = (self.index + 1) % self.size 
123           
124          self.current_value_index = self.index 
125          self.started = True 
126          self.changed = True 
127          self.__content[self.index] = value 
128          self.set_highest_index() 
 129 -    def maintain(self): 
 130          self.current_value = self.__content[self.index] 
131          self.index = (self.index + 1) % self.size 
132           
133          self.__content[self.index] = self.current_value 
 135          self.current_value = self.__content[self.index] 
136          self.index = (self.index + 1) % self.size 
137          self.current_value_index = self.index 
138          self.__content[self.index] = self.current_value 
 140          self.current_value = self.__content[self.index] 
141          while self.index != self.highest_index(): 
142              self.index = (self.index + 1) % self.size 
143              self.current_value_index = self.index 
144              self.__content[self.index] = self.current_value 
145              self.set_highest_index() 
 147          self.__content.insert(0, value) 
148          self.__content.pop() 
 150          return self.current_value_index 
 155   
156       
158          return self.__content 
  159   
165          event = UpdateLineEvent(id = self.id, value = self.value,\ 
166                   realvalue = self.realvalue) 
167          wx.PostEvent(self.frame, event) 
  168   
169 -class MainAppWindow(wx.Frame): 
 170      '''Standard application main window with file, view and help menus''' 
171 -    def __init__(self, *args, **kwargs): 
 172          wx.Frame.__init__(self, style = wx.DEFAULT_FRAME_STYLE, *args, **kwargs) 
173          self.SetExtraStyle(wx.FRAME_EX_METAL) 
174          self.CreateStatusBar() 
175   
176          filemenu = wx.Menu() 
177          helpmenu = wx.Menu() 
178          viewmenu = wx.Menu() 
179   
180          filemenu.Append(wx.ID_SAVEAS, 
181              "Save &As...\tctrl-s", "Save as PNG") 
182          filemenu.Append(wx.ID_EXIT,  
183              "E&xit\tctrl-q", "Terminate the program") 
184   
185          helpmenu.Append(wx.ID_ABOUT, "&About Braun...") 
186          helpmenu.Append(wx.ID_HELP, "&Help\tctrl-?", "Help using Braun") 
187          viewmenu.Append(wx.ID_ZOOM_IN, "Zoom in\tctrl-+") 
188          viewmenu.Append(wx.ID_ZOOM_OUT, "Zoom out\tctrl--") 
189          viewmenu.Append(ID_ZOOM_TO_FIT, "Zoom to fit\tctrl-0") 
190   
191          menubar = wx.MenuBar() 
192          menubar.Append(filemenu, "&File") 
193          menubar.Append(viewmenu, "&View") 
194          menubar.Append(helpmenu, "&Help") 
195          self.SetMenuBar(menubar) 
196   
197          wx.EVT_MENU(self, wx.ID_ZOOM_IN, self.on_zoom_in) 
198          wx.EVT_MENU(self, wx.ID_ZOOM_OUT, self.on_zoom_out) 
199          wx.EVT_MENU(self, ID_ZOOM_TO_FIT, self.on_zoom_to_fit) 
200          wx.EVT_MENU(self, wx.ID_ABOUT, self.on_about) 
201          wx.EVT_MENU(self, wx.ID_HELP, self.on_help) 
202          wx.EVT_MENU(self, wx.ID_CLOSE, self.on_close) 
203          wx.EVT_MENU(self, wx.ID_SAVEAS, self.on_saveas) 
204          wx.EVT_MENU(self, wx.ID_EXIT, self.on_exit) 
 205   
206 -    def on_about(self, event): 
 207          d = wx.MessageDialog(self, 
208          "Copyright Jamie Bullock 2007.\nLicensed under the GNU GPL version 3 or later", "About Braun %s" % VERSION, wx.OK) 
209          d.ShowModal() 
210          d.Destroy() 
 211   
212 -    def on_help(self, event): 
 213          helptext = "" 
214          d = BraunHelp(None) 
215          d.ShowModal() 
216          d.Destroy() 
 217   
218 -    def on_close(self, event): 
 220   
 223   
224 -class BraunMainWindow(MainAppWindow): 
 225 -    def __init__(self, queue): 
 226          MainAppWindow.__init__(self, None, wx.ID_ANY, title="Braun",  
227              size=(800,600)) 
228          self.queue = queue 
229          self.fps = FPS 
230          self.clock = None 
231          self.lines = [None] * MAX_LINES 
232          self.data = [None] * MAX_LINES 
233          self.text_ids = [None] * MAX_LINES 
234          self.text_names = [None] * MAX_LINES 
235          self.text_values = [None] * MAX_LINES 
236          self.real_values = [0.0] * MAX_LINES 
237          self.Canvas = None 
238          self.done_first_draw = False 
239          self.tics = []  
240          self.tic_values = [0] * N_TICS 
241          self.redraw_tics = False 
242          self.redraw_values = False 
243          self.tic_max = GLOBAL_MAXIMUM 
244          self.tic_min = GLOBAL_MINIMUM 
245          self.do_resize = True 
246          self.other_frame = False 
247   
248          wx.lib.colourdb.updateColourDB() 
249          colours = wx.lib.colourdb.getColourList() 
250          colours.remove('BLACK') 
251          self.my_colour_list = ['TOMATO1'] 
252          for colour in self.my_colour_list: 
253              colours.remove(colour) 
254          for i, colour in enumerate(colours): 
255              if i % 22 == 0 and 'GREY' not in colour: 
256                  self.my_colour_list.append(colour) 
257          assert len(self.my_colour_list) >= MAX_LINES 
258   
259          self.buffers = [RingBuffer() for i in range(MAX_LINES)] 
260   
261          self.Bind(EVT_UPDATE_GRAPH, self.on_update) 
262          Dispatcher(queue, self).start() 
263          FloatCanvas.FloatCanvas.OnSize = self.on_size 
264   
265          NC = NavCanvas.NavCanvas(self,wx.ID_ANY,(500,300), 
266                                    ProjectionFun = None, 
267                                    Debug = 0, 
268                                    BackgroundColor = "BLACK" 
269                                    ) 
270   
271          self.zoom_factor = 1.0 
272          self.Canvas = NC.Canvas 
273          self.Canvas.NumBetweenBlits = 1000 
274   
275          tb = NC.ToolBar 
276          StopButton = wx.Button(tb, wx.ID_ANY, "Stop") 
277          PlayButton = wx.Button(tb, wx.ID_ANY, "Run") 
278          tb.AddSeparator() 
279          tb.AddControl(StopButton) 
280          tb.AddControl(PlayButton) 
281          tb.Realize() 
282   
283          StopButton.Bind(wx.EVT_BUTTON, self.on_stop) 
284          PlayButton.Bind(wx.EVT_BUTTON, self.on_run) 
285           
286           
287          wx.CallAfter(self.hide_show_hack, StopButton) 
288          wx.CallAfter(self.hide_show_hack, PlayButton) 
289   
290          self.Show(True) 
291          self.Bind(wx.EVT_TIMER, self.on_timer) 
292          self.clock = wx.Timer(self) 
293          self.delay = 1000 / self.fps 
294          self.run_braun() 
 295   
296 -    def hide_show_hack(self, object): 
 297           
298          "This is a complete hack to work around a bug on OS-X" 
299          object.Hide() 
300          object.Show() 
 301   
302 -    def on_saveas(self, event=None): 
 303          import os 
304          dlg = wx.FileDialog( 
305              self, message="Save as PNG ...", defaultDir=os.getcwd(), 
306              defaultFile="", wildcard="*.png", style=wx.SAVE 
307              ) 
308          if dlg.ShowModal() == wx.ID_OK: 
309              path = dlg.GetPath() 
310              if not(path[-4:].lower() == ".png"): 
311                  path = path+".png" 
312                  self.Canvas.SaveAsImage(path) 
 313   
314 -    def on_exit(self, event): 
 316   
317 -    def on_timer(self, event): 
 318          for i,buffer in enumerate(self.buffers):  
319              data = buffer.dump() 
320              self.data[i][:,1] = movavg(data, SMOOTHING) 
321          self.do_resize_zoom() 
322          self.draw() 
 323   
324 -    def on_stop(self, event): 
 325          if self.clock is not None: 
326              self.clock.Stop() 
 327   
328 -    def on_run(self, event): 
 329          self.redraw_values = True 
330          self.run_braun() 
 331   
332 -    def on_size(self, event): 
 333          "Used to override FloatCanvas.OnSize" 
334          if self.Canvas is not None: 
335              self.Canvas.InitializePanel()         
336              self.Canvas.SizeTimer.Start(50, oneShot=True) 
337              self.Canvas.ZoomToBB() 
 338   
339 -    def on_zoom_to_fit(self, event): 
 340          self.Canvas.ZoomToBB() 
 341   
342 -    def on_zoom_in(self, event): 
 343          self.zoom_factor = 1.1 
344          self.do_zoom() 
 345   
346 -    def on_zoom_out(self, event): 
 347          self.zoom_factor = 0.9 
348          self.do_zoom() 
 349   
350 -    def on_update(self, event): 
 351          self.buffers[event.id].append(event.value)     
352          for i,buffer in enumerate(self.buffers): 
353              if i != event.id: 
354                  buffer.maintain_as_other() 
355          self.real_values[event.id] = event.realvalue 
 356   
358          zoom = self.zoom_factor 
359          self.Canvas.Zoom(zoom) 
 360   
361 -    def run_braun(self): 
 362          Canvas = self.Canvas 
363          if self.done_first_draw: 
364              Canvas.ClearAll(ResetBB=False) 
365              self.redraw_tics = True 
366          else: 
367              Canvas.ClearAll() 
368          self.draw_text() 
369          self.draw_axis() 
370          self.do_resize_zoom() 
371          self.update_text() 
372          smooth = SMOOTHING - 1 
373   
374          if not self.done_first_draw: 
375              self.time = \ 
376                  GRAPH_WIDTH*N.pi*N.arange(RING_BUFFER_SIZE - \ 
377                      smooth)/float(RING_BUFFER_SIZE - smooth) 
378              for i in range(MAX_LINES): 
379                  self.data[i] = N.zeros((RING_BUFFER_SIZE - smooth, 2)) 
380                  self.data[i][:,0] = self.time          
381   
382          for i,data in enumerate(self.data): 
383              if self.buffers[i].started: 
384                  if WX_GE_2_8 and ANTIALIASED_LINES:    
385                      line = SmoothLine(data, 
386                                  LineColor = self.my_colour_list[i], 
387                                  LineWidth = LINE_WIDTH) 
388                      Canvas.AddObject(line) 
389                      self.lines[i] = line 
390                  else: 
391                      self.lines[i] = Canvas.AddLine(data, 
392                                 LineColor = self.my_colour_list[i], 
393                                 LineWidth = 1) 
394   
395          self.clock.Start(self.delay) 
396          self.do_resize_zoom() 
397          Canvas.Draw() 
398          self.done_first_draw = True 
 399   
400 -    def do_resize_zoom(self): 
 401          if self.do_resize: 
402              self.Canvas.ZoomToBB() 
403              self.do_resize = False 
 404   
406          if self.other_frame: 
407              self.other_frame = False 
408              return True 
409          else: 
410              self.other_frame = True 
411              return False 
 412   
414           
415          if self.every_other_frame(): 
416              self.update_text() 
417          for i,buffer in enumerate(self.buffers): 
418              if buffer.changed: 
419                  self.buffers[i].changed = False 
420              else: 
421                  self.buffers[i].maintain_as_current() 
422              if self.lines[i] is not None: 
423                  self.lines[i].SetPoints(self.data[i]) 
424              elif buffer.started: 
425                  if WX_GE_2_8 and ANTIALIASED_LINES:    
426                      line = SmoothLine(self.data[i], 
427                                  LineColor = self.my_colour_list[i], 
428                                  LineWidth = LINE_WIDTH) 
429                      self.Canvas.AddObject(line) 
430                      self.lines[i] = line 
431                  else: 
432                      self.lines[i] = self.Canvas.AddLine(self.data[i], 
433                                 LineColor = self.my_colour_list[i], 
434                                 LineWidth = 1) 
435          self.Canvas.Draw(Force=True) 
 436   
437 -    def draw_axis(self): 
 438          Canvas = self.Canvas 
439          tic_x = SCALE_X_OFFSET 
440          scale_multiplier = SCALE_MULTIPLIER * TIC_SCALE_MULTIPLIER 
441          self.compute_tics() 
442          self.tics = [] 
443          for i, tic_value in enumerate(self.tic_values): 
444              tic_pos = i * TIC_DISTANCE 
445              tic_y = tic_pos * scale_multiplier + SCALE_Y_OFFSET 
446              tic_value = compact_number(tic_value, Y_AXIS_NUMBER_PRECISION)  
447              mytic = self.Canvas.AddScaledText(tic_value, 
448                                  (tic_x, tic_y), 
449                                  Size = FONT_SIZE,  
450                                  Color = "Grey", 
451                                  Position = 'cr') 
452              self.tics.append(mytic) 
453   
454           
455           
456          self.Canvas.AddRectangle((-0.3, -1.2 * SCALE_MULTIPLIER),  
457                       (GRAPH_WIDTH * N.pi + 1.5, 2.7 * SCALE_MULTIPLIER),  
458                                      LineColor = None) 
 459   
460 -    def draw_text(self): 
 461          x = NAMESPACE_X_OFFSET 
462          y = NAMESPACE_Y_OFFSET 
463          for i in xrange(MAX_LINES): 
464              Point = (x, y) 
465              name = namespace[i][:10] 
466              self.text_names[i] = \ 
467                  self.Canvas.AddScaledText("%s:" % name, Point,  
468                      Size = FONT_SIZE, Color = self.my_colour_list[i]) 
469              x += NAMESPACE_NUMBER_PADDING 
470              Point = (x, y) 
471              self.text_values[i] = \ 
472                  self.Canvas.AddScaledText("", Point, Size = FONT_SIZE,  
473                      Color = self.my_colour_list[i]) 
474              x += NAMESPACE_X_HOPSIZE 
 475   
476 -    def do_rescale(self, value): 
 477          "Rescale Y axis and return true if scale has changed" 
478          scale_changed = False 
479          if value > self.tic_max: 
480              self.tic_max = value 
481              scale_changed = True 
482          if value < self.tic_min: 
483              self.tic_min = value 
484              scale_changed = True 
485          return scale_changed 
 486   
487 -    def compute_tics(self): 
 488          tic_distance = (self.tic_max - self.tic_min) / float(N_TICS - 1) 
489          for i in range(N_TICS): 
490              self.tic_values[i] = (i * tic_distance)  
 491   
492      @staticmethod 
493 -    def oversize_number(value): 
 494          if value > 10e3 or value < -10e3: 
495              return True 
496          else:  
497              return False 
 498   
499 -    def update_text(self): 
 500          need_compute_tics = False 
501          for i,value in enumerate(self.text_values): 
502              real_value = self.real_values[i] 
503   
504               
505              if not need_compute_tics: 
506                  need_compute_tics = self.do_rescale(real_value) 
507   
508               
509              name = namespace[i][:8] 
510   
511              self.text_names[i].SetText("%s" % name) 
512              if self.buffers[i].changed or self.redraw_values: 
513                  val = compact_number(real_value, 3) 
514                  value.SetText(val) 
515                  self.redraw_values = False 
516   
517          if need_compute_tics: 
518              self.compute_tics() 
519   
520          for i, tic_value in enumerate(self.tic_values): 
521              y = self.tic_values[i] 
522              y = compact_number(y, Y_AXIS_NUMBER_PRECISION) 
523              self.tics[i].SetText(y) 
 524   
526           
527          Dispatcher(self.queue, self).stop() 
528          self.Destroy() 
  529   
531      ''' returns an n period moving average for the time series s 
532   
533          s is a list ordered from oldest (index 0) to most recent (index -1) 
534          n is an integer 
535   
536          returns a numeric array of the moving average 
537      ''' 
538   
539      s = N.array(s) 
540      c = N.cumsum(s) 
541      return (c[n-1:] - c[:-n+1]) / float(n) 
 542   
545   
546      D = N.array(s) 
547      S = D.size 
548      A = N.zeros(S-(n-1), float) 
549      for j in range(n): 
550          A += D[j:S-(n-1)+j] 
551          A /= float(n * .05) 
552   
553      return A 
 554   
556      "Return a number as a string compacted to a given number of digits" 
557      import decimal 
558      decimal.getcontext().prec = digits 
559      x = decimal.Decimal(str(number)) 
560      x += 0 
561      return x.to_eng_string() 
 562