from __future__ import absolute_import, print_function """ Implements a fast replacement for calling DrawLines with an array as an argument. It uses weave, so you'll need that installed. Copyright: Space Telescope Science Institute License: BSD Style Designed by: Enthought, Inc. Author: Eric Jones eric@enthought.com I wrote this because I was seeing very bad performance for DrawLines when called with a large number of points -- 5000-30000. Now, I have found the performance is sometimes OK, and sometimes very poor. Drawing to a MemoryDC seems to be worse than drawing to the screen. My first cut of the routine just called PolyLine directly, but I got lousy performance for this also. After noticing the slowdown as the array length grew was much worse than linear, I tried the following "chunking" algorithm. It is much more efficient (sometimes by 2 orders of magnitude, but usually only a factor of 3). There is a slight drawback in that it will draw end caps for each chunk of the array which is not strictly correct. I don't imagine this is a major issue, but remains an open issue. """ import scipy.weave as weave from numpy.random import * from numpy import * from wxPython.wx import * """ const int n_pts = _Nline[0]; const int bunch_size = 100; const int bunches = n_pts / bunch_size; const int left_over = n_pts % bunch_size; for (int i = 0; i < bunches; i++) { Polyline(hdc,(POINT*)p_data,bunch_size); p_data += bunch_size*2; //*2 for two longs per point } Polyline(hdc,(POINT*)p_data,left_over); """ def polyline(dc,line,xoffset=0,yoffset=0): #------------------------------------------------------------------------ # Make sure the array is the correct size/shape #------------------------------------------------------------------------ shp = line.shape assert(len(shp) == 2 and shp[1] == 2) #------------------------------------------------------------------------ # Offset data if necessary #------------------------------------------------------------------------ if xoffset or yoffset: line = line + array((xoffset,yoffset),line.typecode()) #------------------------------------------------------------------------ # Define the win32 version of the function #------------------------------------------------------------------------ if sys.platform == 'win32': # win32 requires int type for lines. if not issubclass(line.dtype.type, int) or not line.iscontiguous(): line = line.astype(int) code = """ HDC hdc = (HDC) dc->GetHDC(); Polyline(hdc,(POINT*)line,Nline[0]); """ else: if (line.typecode() != uint16 or not line.iscontiguous()): line = line.astype(uint16) code = """ GdkWindow* win = dc->m_window; GdkGC* pen = dc->m_penGC; gdk_draw_lines(win,pen,(GdkPoint*)line,Nline[0]); """ weave.inline(code,['dc','line']) #------------------------------------------------------------------------ # Find the maximum and minimum points in the drawing list and add # them to the bounding box. #------------------------------------------------------------------------ max_pt = maximum.reduce(line,0) min_pt = minimum.reduce(line,0) dc.CalcBoundingBox(max_pt[0],max_pt[1]) dc.CalcBoundingBox(min_pt[0],min_pt[1]) #----------------------------------------------------------------------------- # Define a new version of DrawLines that calls the optimized # version for numpy arrays when appropriate. #----------------------------------------------------------------------------- def NewDrawLines(dc,line): """ """ if (type(line) is ndarray): polyline(dc,line) else: dc.DrawLines(line) #----------------------------------------------------------------------------- # And attach our new method to the wxPaintDC class # !! We have disabled it and called polyline directly in this example # !! to get timing comparison between the old and new way. #----------------------------------------------------------------------------- #wxPaintDC.DrawLines = NewDrawLines if __name__ == '__main__': from wxPython.wx import * import time class Canvas(wxWindow): def __init__(self, parent, id=-1, size=wxDefaultSize): wxWindow.__init__(self, parent, id, wxPoint(0, 0), size, wxSUNKEN_BORDER | wxWANTS_CHARS) self.calc_points() EVT_PAINT(self, self.OnPaint) EVT_SIZE(self, self.OnSize) def calc_points(self): w,h = self.GetSizeTuple() #x = randint(0+50, w-50, self.point_count) #y = randint(0+50, h-50, len(x)) x = arange(0,w,typecode=int32) y = h/2.*sin(x*2*pi/w)+h/2. y = y.astype(int32) self.points = concatenate((x[:,newaxis],y[:,newaxis]),-1) def OnSize(self,event): self.calc_points() self.Refresh() def OnPaint(self,event): w,h = self.GetSizeTuple() print(len(self.points)) dc = wxPaintDC(self) dc.BeginDrawing() # This first call is slow because your either compiling (very slow) # or loading a DLL (kinda slow) # Resize the window to get a more realistic timing. pt_copy = self.points.copy() t1 = time.clock() offset = array((1,0)) mod = array((w,0)) x = pt_copy[:,0] ang = 2*pi/w size = 1 red_pen = wxPen('red',size) white_pen = wxPen('white',size) blue_pen = wxPen('blue',size) pens = iter([red_pen,white_pen,blue_pen]) phase = 10 for i in range(1500): if phase > 2*pi: phase = 0 try: pen = pens.next() except: pens = iter([red_pen,white_pen,blue_pen]) pen = pens.next() dc.SetPen(pen) polyline(dc,pt_copy) next_y = (h/2.*sin(x*ang-phase)+h/2.).astype(int32) pt_copy[:,1] = next_y phase += ang t2 = time.clock() print('Weave Polyline:', t2-t1) t1 = time.clock() pt_copy = self.points.copy() pens = iter([red_pen,white_pen,blue_pen]) phase = 10 for i in range(1500): if phase > 2*pi: phase = 0 try: pen = pens.next() except: pens = iter([red_pen,white_pen,blue_pen]) pen = pens.next() dc.SetPen(pen) dc.DrawLines(pt_copy) next_y = (h/2.*sin(x*ang-phase)+h/2.).astype(int32) pt_copy[:,1] = next_y phase += ang t2 = time.clock() dc.SetPen(red_pen) print('wxPython DrawLines:', t2-t1) dc.EndDrawing() class CanvasWindow(wxFrame): def __init__(self, id=-1, title='Canvas',size=(500,500)): parent = NULL wxFrame.__init__(self, parent,id,title, size=size) self.canvas = Canvas(self) self.Show(1) class MyApp(wxApp): def OnInit(self): frame = CanvasWindow(title="Speed Examples",size=(500,500)) frame.Show(true) return true app = MyApp(0) app.MainLoop()