From: Andrew P <gr...@gm...> - 2005-09-27 17:02:14
|
Hello, I have a program that spits out an array like: [[1 1 1 1 0] [1 1 1 0 0] [0 1 0 0 0] [0 0 1 0 1] [1 1 0 0 0]] Does anybody have a suggestion about the quickest way to draw this? I'm really very new at this. I'm just trying to avoid looping over each element in the array to create a new array with RGB values, since I want to have this animate as it draws, and scroll. This is for cellular automata, i= n case anybody cares :) I've tried this in the debugger shell with a 100x100 array: >>>img =3D wx.EmptyImage(100,100) >>>img.SetData(a.tostring()) Traceback (most recent call last): File "<input>", line 1, in ? File "C:\Python24\Lib\site-packages \wx-2.6-msw-unicode\wx\_core.py", line 2652, in SetData return _core_.Image_SetData(*args, **kwargs) ValueError: Invalid data buffer size. I saw the drawPointList method, but I don't see a way to get from a 2D arra= y to a what looks like a list of x,y pairs? Is that what it wants? Something like [[0,1],[0,2],[0,4]] instead of [0,1,1,0,1]? Either way, that doesn't seem like a very direct method coming from an array, but I could be way off here. I don't see a way to quickly get to that point tho. Thanks for any help, Andrew |
From: Alex T. <al...@tw...> - 2005-09-27 23:07:34
|
Andrew P wrote: > Hello, > > I have a program that spits out an array like: > > [[1 1 1 1 0] > [1 1 1 0 0] > [0 1 0 0 0] > [0 0 1 0 1] > [1 1 0 0 0]] > > Does anybody have a suggestion about the quickest way to draw this? > > I'm really very new at this. I'm just trying to avoid looping over > each element in the array to create a new array with RGB values, since > I want to have this animate as it draws, and scroll. This is for > cellular automata, in case anybody cares :) > How quickly do you need to draw it ? (And is 100x100 a realistic size limit, or do you need bigger ?) I tried a very simple loop as you described (see code below) and it took around 0.07 seconds to draw it on my 2-3 year old laptop (actually, to erase the previous set and draw a new set of points). You could probably optimize this significantly by drawing only those points which have changed rather than drawing all the previous set (to erase them) and then all the new set. But I'd recommend not optimizing it until (or unless) you have proven that you need to - try it the simple way first, and see whether optimization is needed or not. > I saw the drawPointList method, but I don't see a way to get from a > 2D array to a what looks like a list of x,y pairs? Is that what it > wants? Something like [[0,1],[0,2],[0,4]] instead of [0,1,1,0,1]? > Either way, that doesn't seem like a very direct method coming from an > array, but I could be way off here. I don't see a way to quickly get > to that point tho. > You probably don't want points - I think this would likely be too small so each individual point would be barely visible. So this sample code uses rectangles (I set the size to be 4 pixels). This is only the relevant snippet - but hopefully the context is clear enough (but let me know if you'd like the whole example and I'll send it to you directly - but note it's been adapted from another similar experiment I did, so has quite a bit of cruft in it ...) (first generate random points, then erase prior set, then draw new set)) > start = time.clock() > l2d = [] > for i in range(K): > line = [] > for j in range(K): > line.append(random.randint(0,1)) > l2d.append(line) > print "generate", K, "took", time.clock()- start > > start = time.clock() > pens = [] > self.components.bmpCanvas.freeze() > self.components.bmpCanvas.drawRectangleList(self.rects, > self.whitepens) > > self.rects = [] > pens = [] > self.whitepens = [] > siz = 4 > for i in range(K): > for j in range(K): > if l2d[i][j]: > self.rects.append( (siz*i, siz*j, siz, siz) ) > pens.append(self.basicpen) > self.whitepens.append(self.whitepen) > self.components.bmpCanvas.drawRectangleList(self.rects, pens) > self.components.bmpCanvas.thaw() > next = time.clock() > > print "Took ", next-start -- Alex Tweedly http://www.tweedly.net -- No virus found in this outgoing message. Checked by AVG Anti-Virus. Version: 7.0.344 / Virus Database: 267.11.6/111 - Release Date: 23/09/2005 |
From: Andrew P <gr...@gm...> - 2005-09-28 05:19:38
|
Hi Alex, Let me explain a little more what I'm trying to do. First off. here is a screenshot of what I did today: http://www.stefanscott.com/ca_example.jpg That is a 500x400 cellular automata, and a little cramped. What I'd like to do is have it scroll, and this is why I was asking about the best way to draw my numpy array. Optimizing is one thing, not being stupid because you don't know how to draw to the canvas properly is another :) I just found this java example of what I'm trying to do, picture worth a thousand words and all that: http://math.hws.edu/xJava/CA/ Right now I convert to a point list and draw, which is really slow. For instance, with my array code below, I get: >>> timeIt(10) 1.1327484359 Which I am happy with. My original non-numpy code was 20x slower :) That's ten calculations of a 1000x1000 CA, which is more the size I am shooting for. I have a point, I promise.. Lets say I want a scrolling speed of 2 seconds a screen, gives me 2 seconds to calculate 1000 lines, so back of the napkin puts me at about .15 seconds to calculate 1000 lines on my machine, which is much less than 2 seconds, and is great for my 1ghz Apple iBook even. So far so good. But then I try timing the conversion of my array to a point list: >>> a =3D do_ca(1000,1000,90) >>> def timePL(num): ... res =3D [] ... t1 =3D time.clock() ... for i in range(num): ... for i in range(1000): ... [res.append([x,i]) for x in nonzero(a[i])] ... res =3D [] ... t2 =3D time.clock() ... print t2 - t1 ... >>> timePL(10) 1.06879561509 It takes me 1/10th of a second just to convert my numpy array to a point list! Which puts me at 100 seconds if I wanted to scroll a full screen one line at a time. More realistically, the scrolling would probably take place at maybe 25 fps, or so. But that would still be 40 redraws, or 4 seconds at full steam ahead on my fairly fast and modern computer, let alone my poor iBook. I have to reiterate this is my first GUI app ever, and it's amazing what I've done just today in my first day of Pythoncard. But I'm definitely not trying for speed for the sake of speed. I just need to know how to do it right. I'd like to draw it as it calculates, and have it scroll when it reaches the bottom. Popping off the first row of my array and calculating the next bottom row is practically instant in numpy, but then I'm stuck wit= h that slow iteration over each element. Which is why I was wondering if ther= e was a way to draw the array directly, without an RGB or point list conversion inbetween. I don't even think I can cache the points list, since I'd still have to interate over it to do +1 to the row coordinates. Which may be faster, but still feels like I'm polishing a turd :) Am I missing something really obvious? Here is my benchmarking code: from Numeric import * import time # the numpy bit is messy, but the fastest i could come up with: # 3 element window, 4*row+2*row+row, to make integers from # 3 bit binaries. 1<< converts to 8 bit place, # &rule checks rule number for membership, # not_equal changes any number !=3D to zero to 1 def do_ca(w,h,rule): # make zeros array,set a[0] middle element to 1 to seed a =3D zeros((h,w)) a[0,w/2] =3D 1 res =3D [] for i in range(h-1): r =3D a[i] r2 =3D a[i+1] not_equal((1<<(4*r[:-2]+2*r[1:-1]+r[2:])&rule),0, r2[1:-1]) not_equal((1<<(4*r[-1]+2*r[0]+r[1])&rule),0, r2[0:0]) not_equal((1<<(4*r[-2]+2*r[1]+r[0])&rule),0, r2[-1:]) # uncomment next line and return res to get list for # drawPointList(res): # [res.append([x,i]) for x in nonzero(r2)] return a def timeIt(num): t1 =3D time.clock() for i in range(num): do_ca(1000,1000,90) t2 =3D time.clock() print t2 - t1 |
From: Alex T. <al...@tw...> - 2005-09-28 14:43:38
|
Andrew P wrote: > Hi Alex, > > Let me explain a little more what I'm trying to do. First off. here > is a screenshot of what I did today: > Cool. Thanks Andrew - I have a much better idea now. Here's what I believe (tell me if I'm wrong), followed by what I conclude from those beliefs. You're doing CA where each row depends on the previous row (or perhaps in the future on previous rows). Once a cell's value has been determined, it never changes. You need to scroll as the cells are determined - not backwards and forwards under user control. From that I conclude: You should calculate one row at a time, draw it, and then move on to the next one. You should use one one-dim arrays (lists) to hold the previous and current rows You should scroll by using bit-blits, not by re-drawing. so (after the initial phase to fill the window), what you'll do in each cycle through the loop is calculate the next line scroll the current window up by one line draw in newly calculated row at bottom of window. Here's the core part of a sample program to do that .... no doubt it could be improved, but I kept it as simple so I could figure it out. I use a button called "Change" so I can single-step through the process, and when I run with the timer, I simply call the single-step function - could certainly be cleaned up :-) (And for the boundary condition at start/end of the list, I used a try... except ... - very lazy of me). > def draw_row(self, y): > points = [] > pens = [] > for i in range(self.width): > if self.cells[i]: > points.append( (i,y) ) > pens.append( self.basicpen ) > self.components.bmpCanvas.drawPointList( points, pens ) > > > def on_Change_mouseClick(self, event): > if self.row == 0: > self.draw_row(0) > self.row += 1 > return > > start = time.clock() > > if self.row >= self.height: > cv = self.components.bmpCanvas > cv.blit( (0,0), (self.width,self.height), cv._bufImage, (0,1)) > cv.drawRectangleList( [ (0,self.height, self.width,1)], > [self.whitepen]) > y = self.height > else: > y = self.row > > cells = self.change(self.cells) > self.cells = cells > > self.draw_row(y) > self.row += 1 > > ## print "took ", time.clock()-start > return > > def on_bmpCanvas_timer(self, event): > self.on_Change_mouseClick(event) > return > > > def change(self, cells): > new = [] > rule = [ 0, 1, 1, 0, 1, 0, 0, 1 ] > > for i in range(len(cells)): > try: > newval = 4*cells[i-1] + 2*cells[i] + cells[i+1] > except: > newval = 0 > new.append( rule[newval] ) > return new If you want the entire sample, let me know .... -- Alex Tweedly http://www.tweedly.net -- No virus found in this outgoing message. Checked by AVG Anti-Virus. Version: 7.0.344 / Virus Database: 267.11.6/111 - Release Date: 23/09/2005 |
From: Andrew P <gr...@gm...> - 2005-09-28 16:12:07
|
Awesome. Thank you so much. This was the obvious bit I was missing. I just needed a nudge towards blit. I'm sure this is all pretty obvious to anybody whose programmed with a GUI before, or at least drawn anything out in one. I already have a generator function, which returns the next iteration ad nauseum. I managed to build a window down from the top row with one drawPointList at a time, and it worked really well. But when I got to the bottom I just started scratching my head. My first algorithm looked a lot like yours. After tweaking pure Python as much as I could I ended up with: def makeArray1(firstrow): result =3D firstrow[:] result[0][w/2] =3D 1 for row in range(h-1): last =3D result[row] next =3D result[row+1] for i in range(w-1): next[i] =3D rule[4*last[i-1]+2*last[i]+last[i+1]] next[i+1] =3D rule[4*last[i]+2*last[i+1]+last[0]] return result Which gets a bit ugly because it's quite a bit faster to assign directly to an index, than to append. And pulling the row lookups out of the inner loop also speeds it up quite a bit. It's very fast with psyco (about 20x), but I suspect yours (and my first readable one) would be about the same once compiled. That was the fastest native version I came up with tho. With numpy I got within 20% or so of psycos performance, and stayed cross-platform. My iBook really appreciated this, my brain did not. I feel like I'm making excuses for my ugly numpy algorithm, which is almost entirely incomprehensible to me. I guess there's no sin in optimizing an inner loop that applies to a million elements. Especially with an iBook involved. The only other interesting tidbit was it was faster to do all that 1<<element&rule stuff than to just use a lookup table to get a 1 or 0. So numpy is just bloody fast. Or maybe array access is slow. I don't care. I'm done with that bloody algorithm :) Thanks again! Andrew On 9/28/05, Alex Tweedly <al...@tw...> wrote: > > Andrew P wrote: > > > Hi Alex, > > > > Let me explain a little more what I'm trying to do. First off. here > > is a screenshot of what I did today: > > > Cool. Thanks Andrew - I have a much better idea now. > > Here's what I believe (tell me if I'm wrong), followed by what I > conclude from those beliefs. > > You're doing CA where each row depends on the previous row (or perhaps > in the future on previous rows). > Once a cell's value has been determined, it never changes. > You need to scroll as the cells are determined - not backwards and > forwards under user control. > > From that I conclude: > > You should calculate one row at a time, draw it, and then move on to the > next one. > You should use one one-dim arrays (lists) to hold the previous and > current rows > You should scroll by using bit-blits, not by re-drawing. > > so (after the initial phase to fill the window), what you'll do in each > cycle through the loop is > > calculate the next line > scroll the current window up by one line > draw in newly calculated row at bottom of window. > > > Here's the core part of a sample program to do that .... no doubt it > could be improved, but I kept it as simple so I could figure it out. I > use a button called "Change" so I can single-step through the process, > and when I run with the timer, I simply call the single-step function - > could certainly be cleaned up :-) > > (And for the boundary condition at start/end of the list, I used a > try... except ... - very lazy of me). > > > def draw_row(self, y): > > points =3D [] > > pens =3D [] > > for i in range(self.width): > > if self.cells[i]: > > points.append( (i,y) ) > > pens.append( self.basicpen ) > > self.components.bmpCanvas.drawPointList( points, pens ) > > > > > > def on_Change_mouseClick(self, event): > > if self.row =3D=3D 0: > > self.draw_row(0) > > self.row +=3D 1 > > return > > > > start =3D time.clock() > > > > if self.row >=3D self.height: > > cv =3D self.components.bmpCanvas > > cv.blit( (0,0), (self.width,self.height), cv._bufImage, (0,1)) > > cv.drawRectangleList( [ (0,self.height, self.width,1)], > > [self.whitepen]) > > y =3D self.height > > else: > > y =3D self.row > > > > cells =3D self.change(self.cells) > > self.cells =3D cells > > > > self.draw_row(y) > > self.row +=3D 1 > > > > ## print "took ", time.clock()-start > > return > > > > def on_bmpCanvas_timer(self, event): > > self.on_Change_mouseClick(event) > > return > > > > > > def change(self, cells): > > new =3D [] > > rule =3D [ 0, 1, 1, 0, 1, 0, 0, 1 ] > > > > for i in range(len(cells)): > > try: > > newval =3D 4*cells[i-1] + 2*cells[i] + cells[i+1] > > except: > > newval =3D 0 > > new.append( rule[newval] ) > > return new > > > If you want the entire sample, let me know .... > > -- > Alex Tweedly http://www.tweedly.net > > > > -- > No virus found in this outgoing message. > Checked by AVG Anti-Virus. > Version: 7.0.344 / Virus Database: 267.11.6/111 - Release Date: 23/09/200= 5 > > |