From: Martin R. <law...@gm...> - 2005-09-09 13:10:56
|
Hello everyone, I have a question concerning a common task, to dynamically plot the results of some (usually pretty fast) computation. Approach 1: (Routine `do_it_point_by_point` below) just call `plot` successively. Here the update frequency gets slower and slower the more points are added. Approach 2: (Routine `do_it_point_by_point`) Here one obtains a speed improvement, however the code is more complicated (in particular for beginners!). To get some speed improvements I figured out that it is better to open just one plot and then append new data to this one. For large numbers of points to draw this makes the plot faster. Then I wondered 'Why is this so?' and figured out that the plot()-command (in interactive mode) plots the previous points all over again with each new point. Therefore using setp(line, data=('old data + new point')) made everything faster because now was only one object to handle (which of course is getting complexer and complexer). Up to this point it is exactly the same as described in 'anim.py'. My question is now: Is there a way just to add points to a plot? Because even in the append-method MPL draws everything again with each draw(). Only this time MPL has to plot one thing - a circular arc instead of many many points and is therefore faster. Remarks: - Of course this is only reasonable if autoscale_on=False. - in this situation the z-ordering could not be obeyed (which would be perfectly ok for this particular usage) - No previous points have to be replotted (this is what I would want) To play around with this idea I tried to use a modified plot routine, which should just plot the newly added points/lines/...: #------------------------------------------------------------------ def fastplot(*args, **kwargs): b = ishold() h = popd(kwargs, 'hold', None) if h is not None: hold(h) try: ret = gca().plot(*args, **kwargs) renderer = gca().get_renderer_cache() gc = renderer.new_gc() # new graphics context for line in ret: renderer.draw_line(gc, args[0][1], args[0][1],args[1][0], args[1][1]) except: hold(b) raise hold(b) return ret #------------------------------------------------------------------ However, this (mainly a copy of plot()) does not work (nothing appears on the screen). So what do you think: Is there any possibilty to turn the 'draw everything again with each plot()-command' off? My suggestion is that propably the usage of the Renderer (like above) directly (as described in user's manual) could help. But I'm not really able to understand what MPL is doing there. Is it possible to use the commands described there with existing axis to get output on the screen? I.e. using something like ax = gca() renderer = ax.get_renderer_cache() if some other thing has already been plotted? I tried to go on with the code from the user's manual: gc = o.new_gc() rec = o.draw_rectangle(gc,face,100,100,200,200) You can see the rectangle, if interactive-mode is off. This is a drawback because the issue mentioned at the beginning needs ion(). If something like this would be possible it would make things to new students learning python and mpl much easier. Thanks for your help, Martin #--------------------------------------------------------------------------------- from pylab import * import time # for profiling def do_it_point_by_point(x1,y1): print " plot every point seperately ..." n = len(x1) axis([-1,1,-1,1]) axis('scaled') for i in xrange(n): # plot all data step by step plot( [x1[i] ],[y1[i] ],'bo') def do_it_by_append(x1,y1): print " append data to existing plot ..." n = len(x1) points = plot(x1[0]*ones(2),y1[0]*ones(2),'ro')# draw the first point axis([-1,1,-1,1]) axis('scaled') if n>1: x_help = list() x_help.append(x1[0]) y_help = list() y_help.append(y1[0]) for i in xrange(1,n-1): x_help.append(x1[i]) y_help.append(y1[i]) # append the rest and ... setp(points,data=(array(x_help),array(y_help))) # update data n=80 # setting n=300 makes the phi=2.0*pi*arange(n)/n # difference quite obvious x=cos(phi) y=sin(phi) ion() print "" figure(1) subplot(111, autoscale_on=False) tstart = time.time() do_it_point_by_point(x,y) tend = time.time() t_old = tend - tstart close(1) figure(1) tstart = time.time() do_it_by_append(x,y) tend = time.time() t_new = tend - tstart close(1) ioff() print "" print " It took %6.2f s the point-by-point way,"%(t_old) print " %6.2f s the append way."%(t_new) -- 5 GB Mailbox, 50 FreeSMS http://www.gmx.net/de/go/promail +++ GMX - die erste Adresse für Mail, Message, More +++ |
From: John H. <jdh...@ac...> - 2005-09-09 14:04:17
|
>>>>> "Martin" == Martin Richter <law...@gm...> writes: Martin> Then I wondered 'Why is this so?' and figured out that the Martin> plot()-command (in interactive mode) plots the previous Martin> points all over again with each new point. Therefore Martin> using setp(line, data=('old data + new point')) made Martin> everything faster because now was only one object to Martin> handle (which of course is getting complexer and Martin> complexer). Up to this point it is exactly the same as Martin> described in 'anim.py'. Martin> My question is now: Is there a way just to add points to a Martin> plot? Because even in the append-method MPL draws This is one area of the code I have been actively developing in the last few months -- the ability to selectively draw certain artists and reblit just a region of the figure. For a tutorial introduction, see http://www.scipy.org/wikis/topical_software/MatplotlibCookbook I'll include an example script below. For each marker, an object is created, but we only draw each marker once so it is much faster than the typical case when you get an accumulation of independent objects and all have to be redrawn. I realize the code is complex and not suitable for use by your students. This was only meant to be a low level API that we could then provide a user friendly wrapper around. But since these features are very new (only in CVS and only for TKAgg, GTKAgg and WXAgg) we haven't gotten to the stage of making it easy to use. If you have some ideas on an API to make this more friendly, please share them. In the example code below on my system (GTKAgg, 3GHz P4, linux) I get about 134 PPS (points per second) for both N=100 and N=10000 so it scales fine. However, for 10000 points, 134 PPS is still pretty slow, because it takes 74 seconds to finish. For large numbers of points, you can speed it up significantly by only blitting when a certain number of points have been drawn, eg replacing the blit code with if update.cnt%50==0: ax.figure.canvas.blit(ax.bbox) With this approach, I draw about 1000 PPS on my machine Here is the example import time import matplotlib.numerix as nx from matplotlib.patches import Circle from matplotlib.transforms import identity_transform from pylab import figure, show N = 100 x = nx.arange(float(N)) y = nx.mlab.rand(N) fig = figure() ax = fig.add_subplot(111, autoscale_on=False) ax.set_xlim(0,N) ax.set_ylim(-.1, 1.1) radius = 6/72.*ax.figure.dpi.get() # 6 points in pixels def update(*args): # we want to draw a circle at x,y in data coordinates but have the # radius in pyhysical size (points). To accomplish this we use an # identity transform and draw a circle at 0,0 with a radius in # pixels. We apply an translation offset of x,y in data coords if update.cnt==0: update.start = time.time() trans = identity_transform() trans.set_offset( (x[update.cnt], y[update.cnt]), ax.transData) circ = Circle( (0,0), radius=radius, transform=trans ) circ.set_clip_box(ax.bbox) #selectively draw the artist with the draw_artist command #and just reblit the axes bounding box ax.draw_artist(circ) ax.figure.canvas.blit(ax.bbox) update.cnt+=1 if update.cnt==len(x): print 'PPS', update.cnt/(time.time()-update.start) return False else: return True update.cnt=0 # use any GUI timer or idle function you want import gobject gobject.idle_add(update) show() If you do want to blit every marker when it is created, you can reblit just the small region that bounds the marker. The "get_window_extent" call is a bbox that bounds the circle vertices, but you have to pad it a little to account for the line width. The relevant update is l,b,w,h = circ.get_window_extent().get_bounds() pad = 3 bbox = lbwh_to_bbox(l-pad, b-pad, w+2*pad, h+2*pad) ...snip... ax.figure.canvas.blit(bbox) which gives me 777 PPS even when blitting every marker. Here is the complete example import time import matplotlib.numerix as nx from matplotlib.patches import Circle from matplotlib.transforms import identity_transform, lbwh_to_bbox from pylab import figure, show N = 10000 x = nx.arange(float(N)) y = nx.mlab.rand(N) fig = figure() ax = fig.add_subplot(111, autoscale_on=False) ax.set_xlim(0,N) ax.set_ylim(-.1, 1.1) radius = 6/72.*ax.figure.dpi.get() # 6 points in pixels def update(*args): if update.cnt==0: update.start = time.time() trans = identity_transform() trans.set_offset( (x[update.cnt], y[update.cnt]), ax.transData) circ = Circle( (0,0), radius=radius, transform=trans ) circ.set_clip_box(ax.bbox) l,b,w,h = circ.get_window_extent().get_bounds() pad = 3 bbox = lbwh_to_bbox(l-pad, b-pad, w+2*pad, h+2*pad) ax.draw_artist(circ) ax.figure.canvas.blit(bbox) update.cnt+=1 if update.cnt==len(x): print 'PPS', update.cnt/(time.time()-update.start) return False else: return True update.cnt=0 import gobject gobject.idle_add(update) show() |
From: Martin R. <law...@gm...> - 2005-09-13 14:24:16
|
Hello everyone, Hello John, thank you very much for your example - it is really fantastic. This was exactly what I meant. When I was trying to rewrite your code to a 'fastplot()'-command (which would just be able to plot one point at a time) it worked quite well with the Circle()-command you used. But is there a possibility to write the 'fastplot' in a way so that it can be used like fastplot(x,y, marker='s', color='g',markersize = 3)? In the example given below the color is not recognized correctly. Do I use Line2D in a wrong way? A second question aims on the running-behavior: It - well - stumbles a little bit. If you add a print i to the for-loop you can see that the counting is done very smoothly. But the plot isn't (at least at my computer - maybe this is already the answer). Once again: Thanks! Bye, Martin #----------------------------------------------------------------------- """A try to enable a fastplot() command""" def fastplot_points(*args, **kwargs): """This program tries to enable a routine 'fastplot' based on JDH's code doing blit only in a small region around the new object. Notice: args have to be (x, y) and not ([x], [y]) like for plot(). x,y are floats - not vectors! """ from matplotlib.lines import Line2D from matplotlib.transforms import identity_transform, lbwh_to_bbox ax = gca() trans = identity_transform() trans.set_offset( args, ax.transData) line = Line2D([0],[0], transform=trans, **kwargs) line.set_clip_box(ax.bbox) ax.add_line(line) l,b,w,h = line.get_window_extent(ax.get_renderer_cache()).get_bounds() pad = 3 bbox = lbwh_to_bbox(l-pad, b-pad, w+2*pad, h+2*pad) ax.draw_artist(line) ax.figure.canvas.blit(bbox) #----------------------------------------------------------------------- from pylab import * import time n=1000 phi=2.0*pi*arange(n)/n x=cos(phi) y=sin(phi) ion() figure(1) subplot(111, autoscale_on=False) tstart = time.time() title('point by point with blitting') print " fastplot every point seperately ... " axis([-1,1,-1,1]) axis('scaled') draw() # necessary to get renderer for i in xrange(n): # plot all data step by step fastplot_points( x[i], y[i],marker='s',color='r') tend = time.time() print 'It took %4.2f s.'%(tend - tstart) close(1) ioff() -- Lust, ein paar Euro nebenbei zu verdienen? Ohne Kosten, ohne Risiko! Satte Provisionen für GMX Partner: http://www.gmx.net/de/go/partner |
From: John H. <jdh...@ac...> - 2005-09-14 13:25:58
|
>>>>> "Martin" == Martin Richter <law...@gm...> writes: Martin> Hello everyone, Hello John, Martin> thank you very much for your example - it is really Martin> fantastic. This was exactly what I meant. Martin> When I was trying to rewrite your code to a Martin> 'fastplot()'-command (which would just be able to plot one Martin> point at a time) it worked quite well with the Martin> Circle()-command you used. But is there a possibility to Martin> write the 'fastplot' in a way so that it can be used like Martin> fastplot(x,y, marker='s', color='g',markersize = 3)? This approach can work, but may not be as fast as possible since there is more machinery in Line2D than you need (eg the sequence stuff, the optional linestyle) and logically what you are doing is creating a polygon. One option would be to create a polygon factory function or class which parses a marker string and creates the Polygon instance you want for your marker. You could lift the logic from Line2D, eg markerd = { . : _make_point, , : _make_pixel, o : _make_circle, v : _make_triangle_down, ...snip } where, for example _make_triangle_down is a stripped down version of matplotlib.lines.Line2D._draw_triangle_down def _make_triangle_down(size): return ( (-size, size), (size, size), (0, -size)) you could then create the polygon as I did in my original example code # this is just a sketch and not complete code, eg you will want to # compute size appropriately as in the previous example def polygon_factory(marker, **kwargs) func = markerd[marker] verts = func(size) trans = identity_transform() trans.set_offset( (x, y), ax.transData) poly = Polygon( verts, transform=trans, **kwargs ) poly.set_clip_box(ax.bbox) return poly You could then use this function like poly = polygon_factory('s', facecolor='blue', linewidth=2) If you wanted full parsing of matplotlib format strings (eg 'go' for green circle) which includes both color and marker, you can use matplotlib.axes._process_plot_format or a variant of it. Martin> In the example given below the color is not recognized Martin> correctly. Do I use Line2D in a wrong way? Line2D has both an a linestyle and a marker (eg you can pcreate a single line instance which is 'ro--' (red dashed line with circle markers). "color" and "linewidth" control the linestyle properties. "markerfacecolor, ""markeredgecolor", and "markeredgewidth" control the marker properties. Again, this illustrates why a Line2D object is a bit too heavy for your needs. The polygon factory function would be a useful addition to matplotlib. Martin> A second question aims on the running-behavior: It - well Martin> - stumbles a little bit. If you add a print i to the Martin> for-loop you can see that the counting is done very Martin> smoothly. But the plot isn't (at least at my computer - Martin> maybe this is already the answer). As I've pointed out before (eg at the animation wiki http://www.scipy.org/wikis/topical_software/Animations), the pylab animation technique is for simple throw-away animations. To produce nice animations, you need to use your GUI timer or idle function handling. Eg, if I remove your ion, ioff calls and replace your update functionality with def update(*args): i = update.cnt fastplot_points( x[i], y[i],marker='s',color='r') update.cnt +=1 if update.cnt<1000: return True else: return False update.cnt = 0 tend = time.time() print 'It took %4.2f s.'%(tend - tstart) import gobject gobject.idle_add(update) show() The "jerky" update problem you describe goes away. JDH |
From: Martin R. <law...@gm...> - 2005-09-20 10:11:06
|
Hello everyone, Hello John, as you advised in a Mail some days ago concerning a fastplot-command I wrote a polygon_factory and it worked quite well (In large parts taken from Line2D). See the code attached. What I now want to ask you is: Would it be good to write this object-oriented (propably yes, so one gets rid of all those tiny funktions _make_...(size))? One drawback which comes into my mind is: How should this class be used within fastplot()? Initialize an instance each time you plot a single point? Wouldn't this slow down everything a bit (or am I overestimating the time-scales)? On the otherhand using a class would look very 'tidy'. Thanks for your suggestion. Bye, Martin #-------------------------------------------------------------------- """ A routine which uses blitting so that it is possible to plot seperate markers faster than usual. """ from pylab import * import time # for profiling #-------------------------------------------------------------------- from matplotlib.lines import Line2D from matplotlib.transforms import identity_transform, lbwh_to_bbox from Numeric import pi def _make_point(size): size *= .5 if size <= 0.5: return _make_pixel() elif size <= 2: return _make_hexagon1(size, point=True) else: return _make_circle(size, point=True) def _make_pixel(): return ((-0.5, -0.5), (-0.5, 0.5), ( 0.5, 0.5), ( 0.5, -0.5)) def _make_circle(size, point=False): if point: size *= .5 N = 50.0 r = size/2.0 rads = (2*pi/N)*arange(N) xs = r*cos(rads) ys = r*sin(rads) verts = [(xs[0], ys[0])] for x, y in zip(xs[1:], ys[1:]): verts.append((x, y)) return tuple(verts) def _make_triangle_up(size): return (( 0, size), (-size, -size), ( size, -size)) def _make_triangle_down(size): return ((-size, size), ( size, size), ( 0, -size)) def _make_triangle_left(size): return ((-size, 0), ( size, -size), ( size, size)) def _make_triangle_right(size): return (( size, 0), (-size, -size), (-size, size)) def _make_tri_down(size): size1 = size*0.8 size2 = size*0.5 return ((-size1, size2), ( 0, 0), ( size1, size2), ( 0, 0) ( 0, -size) ( 0, 0)) def _make_tri_up(size): size1 = size*0.8 size2 = size*0.5 return ((-size1, -size2), ( 0, 0), ( size1, -size2), ( 0, 0), ( 0, size), ( 0, 0)) def _make_tri_left(size): size1 = size*0.8 size2 = size*0.5 return ((size2, -size1), ( 0, 0), (size2, size1), ( 0, 0), (-size, 0), ( 0, 0)) def _make_tri_right(size): size1 = size*0.8 size2 = size*0.5 return ((-size2, -size1), ( 0, 0), (-size2, size1), ( 0, 0), ( size, 0), ( 0, 0)) def _make_square(size): return ((-size, -size), (-size, size), ( size, size), ( size, -size)) def _make_diamond(size): return (( size, 0), ( 0, -size), (-size, 0), ( 0, size)) def _make_thin_diamond(size): xsize = 0.6*size return (( xsize, 0), ( 0, -size), (-xsize, 0), ( 0, size)) def _make_pentagon(size): sizeX1 = size*0.95 sizeY1 = size*0.31 sizeX2 = size*0.59 sizeY2 = size*0.81 return (( 0, size), (-sizeX1, sizeY1), (-sizeX2, -sizeY2), ( sizeX2, -sizeY2), ( sizeX1, sizeY1)) def _make_hexagon1(size, point=False): if point: size *= .5 sizeX1 = size*0.87 sizeY1 = size*0.5 return (( 0, size), (-sizeX1, sizeY1), (-sizeX1, -sizeY1), ( 0, -size), ( sizeX1, -sizeY1), ( sizeX1, sizeY1)) def _make_hexagon2(size): sizeX1 = size*0.5 sizeY1 = size*0.87 return (( size, 0), ( sizeX1, sizeY1), (-sizeX1, sizeY1), ( -size, 0), (-sizeX1, -sizeY1), ( sizeX1, -sizeY1)) def _make_plus(size): return ((-size, -size), ( size, size), ( 0, 0), ( size, -size), (-size, size), ( 0, 0)) def _make_x(size): return ((-size, 0), ( size, 0), ( 0, 0), ( 0, -size), ( 0, size), ( 0, 0)) def _make_vline(size): return ((0, -size), (0, size)) def _make_hline(size): return ((-size, 0), ( size, 0)) markerd = { '.' : _make_point, ',' : _make_pixel, 'o' : _make_circle, 'v' : _make_triangle_down, '^' : _make_triangle_up, '<' : _make_triangle_left, '>' : _make_triangle_right, '1' : _make_tri_down, '2' : _make_tri_up, '3' : _make_tri_left, '4' : _make_tri_right, 's' : _make_square, 'p' : _make_pentagon, 'h' : _make_hexagon1, 'H' : _make_hexagon2, '+' : _make_plus, 'x' : _make_x, 'D' : _make_diamond, 'd' : _make_thin_diamond, '|' : _make_vline, '_' : _make_hline } def polygon_factory(ax, marker, size, args, **kwargs): """ This function creates a Polygon-instance having a shape according to the given marker. This instance is located at the position specified by the args (containing two floats x and y). """ func = markerd[marker] renderer = ax.get_renderer_cache() verts = func(size = 1.0*renderer.points_to_pixels(int(size))) trans = identity_transform() trans.set_offset( (args[0], args[1]), ax.transData) poly = Polygon( verts, transform=trans, **kwargs ) poly.set_clip_box(ax.bbox) return poly def fastplot_points(*args, **kwargs): """This program tries to enable a routine 'fastplot' using JDH's method of reblitting only a small region around the new object. Notice: args have to be (x, y) and not ([x], [y]) like for plot(). You can select the markers with the keyword marker = 'h' for example. To change their color use facecolor = 'g' or fc = 'g' and edgecolor = 'b' or ec = 'b' respectivly. To vary their size use either ms = 4.5 or markersize = 4.5. For a change of the markeredgewidth use markeredgewith, mew, linewidth or lw (If more of them are given they are respected in this order) Also all other kwargs can be used (antialiased, alpha, ...) """ ax = gca() trans = identity_transform() trans.set_offset(args, ax.transData) marker = kwargs.pop('marker', 'o') # which marker is desired # (default:'o')? msize = kwargs.pop('markersize', kwargs.pop('ms', 6)) # filter out the desired size # (If both kwargsare given # 'markersize' is prefered.) lw = kwargs.pop('markeredgewidth', kwargs.pop('mew', kwargs.pop('linewidth', kwargs.pop('lw', .5)))) # filter out the desired kwargs.update({'lw':lw}) # edgewidth p = polygon_factory(ax, marker, msize, args, **kwargs) p.set_clip_box(ax.bbox) l,b,w,h = p.get_window_extent().get_bounds() pad = 3 bbox = lbwh_to_bbox(l-pad, b-pad, w+2*pad, h+2*pad) ax.draw_artist(p) ax.figure.canvas.blit(bbox) #------------------------------------------------------------------- n=300 phi=2.0*pi*arange(n)/n x=cos(phi) y=sin(phi) ion() figure(1) subplot(111, autoscale_on=False) tstart = time.time() title('point by point with blitting') axis([-1,1,-1,1]) axis('scaled') for i in xrange(n): # plot all data step by step fastplot_points( x[i], y[i],marker='o', fc = 'b') tend = time.time() t_fast = tend - tstart close(1) ioff() print " It took %6.2f s."%(t_fast) #------------------------------------------------------------------- -- 5 GB Mailbox, 50 FreeSMS http://www.gmx.net/de/go/promail +++ GMX - die erste Adresse für Mail, Message, More +++ |
From: John H. <jdh...@ac...> - 2005-09-20 10:29:18
|
>>>>> "Martin" == Martin Richter <law...@gm...> writes: Martin> Hello everyone, Hello John, Martin> as you advised in a Mail some days ago concerning a Martin> fastplot-command I wrote a polygon_factory and it worked Martin> quite well (In large parts taken from Line2D). See the Martin> code attached. What I now want to ask you is: Would it be Martin> good to write this object-oriented (propably yes, so one Martin> gets rid of all those tiny funktions _make_...(size))? One Martin> drawback which comes into my mind is: How should this Martin> class be used within fastplot()? Initialize an instance Martin> each time you plot a single point? Wouldn't this slow down Martin> everything a bit (or am I overestimating the time-scales)? Martin> On the otherhand using a class would look very 'tidy'. Yes, it would be tidy, but you might pay for that in performance as you suggest. As far as tidiness, you can use the existing approach and not add all the _make* functions the __all__ list the module exports -- this would make the helper functions invisible. You could also use nested scopes def my_factory(*args): def helper1(x): pass def helper2(x): pass do_something_and_return() However, since the code you use to compute the vertices is lifted from lines.py, it would be nice to define one set of functions that could be reused by both the line marker code and polygon code. This would be the cleanest design and would be easier to maintain, but it would require refactoring both your code and the lines.py code a little. If you want to take this on, that would be great. JDH |
From: Nadia D. <den...@st...> - 2005-09-20 14:57:57
Attachments:
test_marker.py
markers.py
|
Hi John, Martin, I was experimenting with drawing markers one at a time for a ReadCursor class to provide the option of marking the position of the cursor. I saw this email and read the thread (the users mailing list is very helpful). Since markers and lines go together and implementing markers in the same way for ReadCursor would cause a lot of code duplication, I tried pulling out the relevant functions from lines.py and creating a Marker class - attached. A test function is below. I am using TkAgg, so _newstyle is True and renderer has draw_markers. x and y in Marker.__init__ are sequences and are turned into 1d arrays before passed to draw_markers. I get: Traceback (most recent call last): File "test_marker.py", line 9, in ? m.draw_marker(fcr) File "/Users/dencheva/cvs-matplotlib/matplotlib/lib/matplotlib/markers.py", line 556, in draw_marker markerFunc(renderer, gc, xt, yt) File "/Users/dencheva/cvs-matplotlib/matplotlib/lib/matplotlib/markers.py", line 85, in _draw_pixel renderer.draw_markers(gc, path, xt, yt) IndexError: Unexpected SeqBase<T> length. What am I doing wrong? If I set _newstyle explicitely to False, in order to test this part of the various draw_* functions, I don't get any errors but a marker is not plotted. So, again I must be doing something wrong. If I understand this correctly I need the same functionality as Martin. If either of you have any ideas of what is wrong, I'd appreciate any help. Also we don't have to reinvent the wheel independently, so if this is not the right approach and Martin has a better way I am willing to help with this, by testing or any other way. Thanks, Nadia Dencheva |
From: John H. <jdh...@ac...> - 2005-09-20 22:48:22
|
>>>>> "Nadia" == Nadia Dencheva <den...@st...> writes: Nadia> Hi John, Martin, I was experimenting with drawing markers Nadia> one at a time for a ReadCursor class to provide the option Nadia> of marking the position of the cursor. I saw this email and Nadia> read the thread (the users mailing list is very helpful). Nadia> Since markers and lines go together and implementing Nadia> markers in the same way for ReadCursor would cause a lot of Nadia> code duplication, I tried pulling out the relevant Nadia> functions from lines.py and creating a Marker class - Nadia> attached. A test function is below. I am using TkAgg, so Nadia> _newstyle is True and renderer has draw_markers. x and y Nadia> in Marker.__init__ are sequences and are turned into 1d Nadia> arrays before passed to draw_markers. I get: Nadia> Traceback (most recent call last): File "test_marker.py", Nadia> line 9, in ? m.draw_marker(fcr) File Nadia> "/Users/dencheva/cvs-matplotlib/matplotlib/lib/matplotlib/markers.py", Nadia> line 556, in draw_marker markerFunc(renderer, gc, xt, yt) Nadia> File Nadia> "/Users/dencheva/cvs-matplotlib/matplotlib/lib/matplotlib/markers.py", Nadia> line 85, in _draw_pixel renderer.draw_markers(gc, path, xt, Nadia> yt) IndexError: Unexpected SeqBase<T> length. With newstyle, the signature of draw_markers is draw_markers(gc, path, rgbFace, x, y, trans) where x and y are not transformed and trans is an mpl transform. Here is the docstring from backend_bases.py def _draw_markers(self, gc, path, rgbFace, x, y, trans): """ This method is currently underscore hidden because the draw_markers method is being used as a sentinel for newstyle backend drawing path - a matplotlib.agg.path_storage instance Draw the marker specified in path with graphics context gc at each of the locations in arrays x and y. trans is a matplotlib.transforms.Transformation instance used to transform x and y to display coords. It consists of an optional nonlinear component and an affine. You can access these two components as if transform.need_nonlinear(): x,y = transform.nonlinear_only_numerix(x, y) # the a,b,c,d,tx,ty affine which transforms x and y vec6 = transform.as_vec6_val() ...backend dependent affine... """ pass You may also want to search the dev archives for more elaborate discussions. I'm short on time now so gotta run! JDH |
From: Martin R. <law...@gm...> - 2005-09-29 15:43:35
|
Hello everyone, Hello John, I'm thinking about this fastplot-routine for a while now. Before hacking all the lines.py-code I wanted to ask you about your thoughts. Just to repeate a bit: I changed the previously sent fastplot.py (I'll call it 'plot_points()' from now on) a bit: Now it is possible to draw multiple markers with one call. But all at all it looks like the version sent before. What you John asked for, was a unification with lines.py. Taking for example triangle_down in lines.py one can read: def _draw_triangle_down(self, renderer, gc, xt, yt): offset = 0.5*renderer.points_to_pixels(self._markersize) rgbFace = self._get_rgb_face() if self._newstyle: path = agg.path_storage() path.move_to(-offset, offset) path.line_to(offset, offset) path.line_to(0, -offset) path.end_poly() renderer.draw_markers(gc, path, rgbFace, xt, yt, self._transform) else: for (x,y) in zip(xt, yt): verts = ( (x-offset, y+offset), (x+offset, y+offset), (x, y-offset)) renderer.draw_polygon(gc, rgbFace, verts) I would like to ask you (right before I'll mess everything up ;) if all this at least could work: What I would try for unification is writing the dictionary 'markerd' in lines.py (I'd prefer it right in front of 'class Line2D' as global - but there it isn't readable for a program later, isn't it?) and change the functions a bit like: def _make_triangle_down(size): return reshape([[0, -size, size], [1, size, size], [1, 0, -size]], (3,3)) def _make_x(size): return reshape([[0, -size, -size], [1, size, size], [0, 0, 0], [1, size, -size], [1, -size, size], [0, 0, 0]], (6,3)) and so on ... Then I would try to let the newstyle & oldstyle ploting-method use those functions by writing a (for all markers usable) function: def _draw_marker(self, marker, renderer, gc, xt, yt): func = markerd[marker] verts = func(1.0*renderer.points_to_pixels(int(msize))) rgbFace = self._get_rgb_face() if self._newstyle: path = agg.path_storage() for v in verts.tolist(): if v[0]: path.line_to(v[1], v[2]) if else: path.move_to(v[1], v[2]) path.end_poly() renderer.draw_markers(gc, path, rgbFace, xt, yt, self._transform) else: for (x,y) in zip(xt, yt): vertices = [] for v in verts: vertices.append((x+v[1], y+v[2])) renderer.draw_polygon(gc, rgbFace, tuple(vertices)) # notice: some markers are partially drawn double (e.g x) Then the plot_points-routine would contain a differing line: verts = func(1.0*renderer.points_to_pixels(int(msize)))[:,1:3] All this I already tried a bit but somehow failed (I quess mainly due to variables not beeing defined). A way out of this would be knitting those functions into the class Line2D (it would be pretty easy to define everything there). But than I could have used Line2D from the beginning, couldn't I? So - to cut the long story short - where would you (if ever) add those new lines to the code? Another question would be: Are those new for-loops in _draw_marker() too annoying (in the sense of speed)? (I quess they are vital for the _make_triangle_down()-like functions to keep their simple form so that they can be used by plot and plot_points together.) Best regards, Martin -- Lust, ein paar Euro nebenbei zu verdienen? Ohne Kosten, ohne Risiko! Satte Provisionen für GMX Partner: http://www.gmx.net/de/go/partner |
From: John H. <jdh...@ac...> - 2005-10-06 21:38:37
|
>>>>> "Martin" == Martin Richter <law...@gm...> writes: Martin> Hello everyone, Hello John, Martin> I would like to ask you (right before I'll mess everything Martin> up ;) if all this at least could work: What I would try Martin> for unification is writing the dictionary 'markerd' in Martin> lines.py (I'd prefer it right in front of 'class Line2D' Martin> as global - but there it isn't readable for a program Martin> later, isn't it?) and change the functions a bit like: def Martin> _make_triangle_down(size): return reshape([[0, -size, Hi Martin, Sorry for the delay getting back to you. Perhaps the best approach is to predefine some paths in a new module, eg data.py and define your path dictionary like so from matplotlib import agg markerd = {} # triangle up path = agg.path_storage() path.move_to(0, 1.0) path.line_to(-1.0, -1.0) path.line_to(1.0, -1.0) path.end_poly() markerd['^'] = path # square path = agg.path_storage() path.move_to(-1.0, -1.0) path.line_to(-1.0, 1.0) path.line_to(1.0, 1.0) path.line_to(1.0, -1.0) path.end_poly() markerd['s'] = path We should think about what property of the marker should be conserved (radius in points, area, area of circumscribed circle), because I don't think we're terribly consistent on that one currently. In the current implementation, the markersize of the triangle and square gives the side, for the circle it gives the diameter. I'm not sure what the right answer is here, but while you are doing this refactoring it would be good to rationalize this and document any changes in API_CHANGES. In lines.py you could transform this path to scale it to the requested size; something like def _draw_square(self, renderer, gc, xt, yt): side = renderer.points_to_pixels(self._markersize) s = side*0.5 rgbFace = self._get_rgb_face() if self._newstyle: path = markerd['s'] scale = agg.trans_affine_scaling(s,s) transpath = agg.conv_transform_path(path, scale) renderer.draw_markers(gc, transpath, rgbFace, xt, yt, self._transform) We would then need to modify renderer.draw_markers to take a transformed path "transpath" rather than a path but this would be easy -- I'm including a modified src/_backend_agg.cpp RendererAgg::draw_markers function below if you want to go this route. For code that doesn't want to use the path object, it is easy to extract the moveto/lineto command as well as the vertices like so for i in range(path.total_vertices()): command, x, y = path.vertex(i) print command, x, y With a little thought and care, you should be able to do away with all the _draw_square, _draw_triangle, etc, in Line2D and replace them with a single _draw_path, eg def _draw_path(self, renderer, gc, path, xt, yt): s = renderer.points_to_pixels(self._markersize) scale = agg.trans_affine_scaling(s,s) transpath = agg.conv_transform_path(path, translation) renderer.draw_markers(gc, transpath, rgbFace, xt, yt, self._transform) where the calling function pulls the path out of the dict. Another advantage of this approach is that it could supports arbitrary paths (including splines) and user extensible markers. Eg, the user should be able to do markerd['somekey'] = somepath and then be able to call plot(rand(N), marker='somekey') and have everything work automagically -- your task is growing :-) Seriously, though, I think this would be a very nice addition, and not too much work. Martin> Another question would be: Are those new for-loops in Martin> _draw_marker() too annoying (in the sense of speed)? (I Martin> quess they are vital for the _make_triangle_down()-like I don't think the for loop is a problem because it is one per line object, so as long as you don't create a ton of separate lines, you'll be OK. JDH Here is the modified code from _backend_agg.cpp you will need Py::Object RendererAgg::draw_markers(const Py::Tuple& args) { //_draw_markers_cache(gc, path, rgbFace, xo, yo, transform) theRasterizer->reset_clipping(); _VERBOSE("RendererAgg::_draw_markers"); args.verify_length(6); GCAgg gc = GCAgg(args[0], dpi); agg::conv_transform<agg::path_storage> *ppath; swig_type_info * descr = SWIG_TypeQuery("agg.conv_transform<agg::path_storage> *"); assert(descr); if (SWIG_ConvertPtr(args[1].ptr(),(void **)(&ppath), descr, 0) == -1) throw Py::TypeError("Could not convert agg.conv_transform<agg::path_storage>"); facepair_t face = _get_rgba_face(args[2], gc.alpha); Py::Object xo = args[3]; Py::Object yo = args[4]; PyArrayObject *xa = (PyArrayObject *) PyArray_ContiguousFromObject(xo.ptr(), PyArray_DOUBLE, 1, 1); if (xa==NULL) throw Py::TypeError("RendererAgg::_draw_markers_cache expected numerix array"); PyArrayObject *ya = (PyArrayObject *) PyArray_ContiguousFromObject(yo.ptr(), PyArray_DOUBLE, 1, 1); if (ya==NULL) throw Py::TypeError("RendererAgg::_draw_markers_cache expected numerix array"); Transformation* mpltransform = static_cast<Transformation*>(args[5].ptr()); double a, b, c, d, tx, ty; try { mpltransform->affine_params_api(&a, &b, &c, &d, &tx, &ty); } catch(...) { throw Py::ValueError("Domain error on affine_params_api in RendererAgg::_draw_markers_cache"); } agg::trans_affine xytrans = agg::trans_affine(a,b,c,d,tx,ty); size_t Nx = xa->dimensions[0]; size_t Ny = ya->dimensions[0]; if (Nx!=Ny) throw Py::ValueError(Printf("x and y must be equal length arrays; found %d and %d", Nx, Ny).str()); double heightd = double(height); ppath->rewind(0); //ppath->flip_y(0,0); //typedef agg::conv_curve<agg::path_storage> curve_t; typedef agg::conv_curve<agg::conv_transform<agg::path_storage> > curve_t; curve_t curve(*ppath); //maxim's suggestions for cached scanlines agg::scanline_storage_aa8 scanlines; theRasterizer->reset(); agg::int8u* fillCache = NULL; unsigned fillSize = 0; if (face.first) { theRasterizer->add_path(curve); agg::render_scanlines(*theRasterizer, *slineP8, scanlines); fillSize = scanlines.byte_size(); fillCache = new agg::int8u[fillSize]; // or any container scanlines.serialize(fillCache); } agg::conv_stroke<curve_t> stroke(curve); stroke.width(gc.linewidth); stroke.line_cap(gc.cap); stroke.line_join(gc.join); theRasterizer->reset(); theRasterizer->add_path(stroke); agg::render_scanlines(*theRasterizer, *slineP8, scanlines); unsigned strokeSize = scanlines.byte_size(); agg::int8u* strokeCache = new agg::int8u[strokeSize]; // or any container scanlines.serialize(strokeCache); theRasterizer->reset_clipping(); if (gc.cliprect==NULL) { rendererBase->reset_clipping(true); } else { int l = (int)(gc.cliprect[0]) ; int b = (int)(gc.cliprect[1]) ; int w = (int)(gc.cliprect[2]) ; int h = (int)(gc.cliprect[3]) ; rendererBase->clip_box(l, height-(b+h),l+w, height-b); } double thisx, thisy; for (size_t i=0; i<Nx; i++) { thisx = *(double *)(xa->data + i*xa->strides[0]); thisy = *(double *)(ya->data + i*ya->strides[0]); if (mpltransform->need_nonlinear_api()) try { mpltransform->nonlinear_only_api(&thisx, &thisy); } catch(...) { continue; } xytrans.transform(&thisx, &thisy); thisy = heightd - thisy; //flipy thisx = (int)thisx + 0.5; thisy = (int)thisy + 0.5; agg::serialized_scanlines_adaptor_aa8 sa; agg::serialized_scanlines_adaptor_aa8::embedded_scanline sl; if (face.first) { //render the fill sa.init(fillCache, fillSize, thisx, thisy); rendererAA->color(face.second); agg::render_scanlines(sa, sl, *rendererAA); } //render the stroke sa.init(strokeCache, strokeSize, thisx, thisy); rendererAA->color(gc.color); agg::render_scanlines(sa, sl, *rendererAA); } //for each marker Py_XDECREF(xa); Py_XDECREF(ya); if (face.first) delete [] fillCache; delete [] strokeCache; return Py::Object(); } |
From: Martin R. <law...@gm...> - 2005-10-07 15:46:04
|
Hello everyone, Hello John, first of all: thanks for you mail, it was full of information! I exchanged the lines in _backend_agg.cpp and tried to implement your thoughts. While writing data.py wasn't a problem the trouble started when I tried to run a little sample program with GTKAgg - I got a segmentation fault. Unfortunately I'm not able to see where this one comes from. But when using GTKAgg you're not able to draw markers at all; not even ticks ... plotting a line is still possible when using from pylab import * axis('off') plot(arange(10)) so the error seems really due to the draw_markers() and not something else. Below are the changed files. Can you reproduce the segmentation fault? Best, Martin P.S.: Once this particular example works, I will convert the other markers (Arnd and I have worked out a scheme for the size which seems consistent and simple to implement). #### data.py """ Creates a dictionary containing information on how to draw markers. The values used within the paths are chosen so that the longest dimension of a sourrounding rectangle is 1, e.g: triangle up ... has a height and a width of one square ........ has a height and a width of one ... """ from matplotlib import agg markerd = {} # triangle up path = agg.path_storage() path.move_to( 0, 0.5) path.line_to(-0.5, -0.5) path.line_to( 0.5, -0.5) path.end_poly() markerd['^'] = path # square path = agg.path_storage() path.move_to(-0.5, -0.5) path.line_to(-0.5, 0.5) path.line_to( 0.5, 0.5) path.line_to( 0.5, -0.5) path.end_poly() markerd['s'] = path # tri_up (BTW this one change a bit) offset = 2.0/3.0 offset1 = offset*0.8 offset2 = offset*0.5 path = agg.path_storage() path.move_to(0, 0) path.line_to(0, offset) path.move_to(offset1, -offset2) path.line_to(0, 0) path.line_to(-offset1, -offset2) path.end_poly() markerd['2'] = path #### #### changed routine for lines.py def _draw_square(self, renderer, gc, xt, yt): side = renderer.points_to_pixels(self._markersize) rgbFace = self._get_rgb_face() path = d.markerd['s'] if self._newstyle: scale = agg.trans_affine_scaling(side, side) transpath = agg.conv_transform_path(path, scale) renderer.draw_markers(gc, path, rgbFace, xt, yt,self._transform) else: offset = side*.5 vertices = d.path_2_vertices(path) for (x,y) in zip(xt, yt): for verts in vertices: renderer.draw_polygon(gc, rgbFace, verts) #### ### Example call: plot(arange(10),'bs') ### -- Highspeed-Freiheit. Bei GMX supergünstig, z.B. GMX DSL_Cityflat, DSL-Flatrate für nur 4,99 Euro/Monat* http://www.gmx.net/de/go/dsl |
From: John H. <jdh...@ac...> - 2005-10-07 16:11:12
|
>>>>> "Martin" == Martin Richter <law...@gm...> writes: Martin> Can you reproduce the segmentation fault? Best, Hi Martin, Yes, I reproduced it yesterday <wink> The segfault arises when you pass a path to draw_markers rather than a transformed path. Obviously this needs to be fixed to raise rather than segfault. I do test for this in the extension code why the check in _backend_agg.cpp agg::conv_transform<agg::path_storage> *ppath; swig_type_info * descr = SWIG_TypeQuery("agg.conv_transform<agg::path_storage> *"); assert(descr); if (SWIG_ConvertPtr(args[1].ptr(),(void **)(&ppath), descr, 0) == -1) throw Py::TypeError("Could not convert agg.conv_transform<agg::path_storage>"); but clearly the check is not catching the bogus type and raising the exception as it should. I'll work on this. In the meantime, you can continue to work as long as you make sure you don't call draw_markers with the wrong signature. Eg, in Line2D.draw, something like if funcname in ('_draw_square', '_draw_triangle_up'): markerFunc = getattr(self, funcname) markerFunc(renderer, gc, xt, yt) The segfault you are getting is happening on the tick drawing, which are implemented as line markers (eg _draw_tickleft). Since you probably haven't ported the tick drawing to the new API, you are getting the crash. Guess I shouldn't have left this detail out yesterday!! JDH |