1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
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()
|