Problem statement:
You need to plot a large collection of polygons in Matplotlib.
Solution:
A simple way to plot filled polygons in Matplotlib is to use the fill function. If you try to plot a collection of polygons in Matplotlib using sequential calls to fill, it can take a lot of time to generate the graph. There are two ways to speed up the plotting. The first is to use Python’s extended call syntax and pass multiple polygons at one time. The other is to create a single list of points which are the corners of the polygon and separate each polygon by a ‘None’ entry. The extended call method yields an appreciable improvement in performance. Forming a single list of polygon corners separated by ‘None’ results in a significant time savings. For the test cases below, using extended call syntax decreased drawing time by about 50% and using a single list separated by ‘None’ decreased drawing time by about 99%.
Execution time for sequential plotting = 2.496564 sec
Execution time for extended call plotting = 1.172675 sec
Execution time when using None = = 0.028234 sec
Sample Code:
''' Code snippet to plot multiple triangle's in Matplotlib using different methods. ''' import time import sys import matplotlib.pyplot as pp import random if sys.platform == "win32": # On Windows, the best timer is time.clock() default_timer = time.clock else: # On most other platforms the best timer is time.time() default_timer = time.time # generate ends for the triangle line segments xtris = [] ytris = [] for i in range(1000): x1 = random.random() x2 = x1 + random.random()/5.0 x3 = x1 + random.random()/5.0 xtips = [x1,x2,x3] y1 = random.random() y2 = y1 + random.random()/5.0 y3 = y1 + random.random()/5.0 ytips = [y1,y2,y3] xtris.append(xtips) ytris.append(ytips) ############################ # time sequential call to matplotlib pp.figure() pp.subplot(1,3,1) t0 = default_timer() for xtips,ytips in zip(xtris,ytris): pp.fill(xtips,ytips, facecolor='b',alpha=0.1, edgecolor='none') t1 = default_timer() pp.title('Sequential Plotting') print 'Execution time for sequential plotting = %f sec' % (t1-t0) # rebuild ends using none to separate polygons xlist = [] ylist = [] for xtips,ytips in zip(xtris,ytris): xlist.extend(xtips) xlist.append(None) ylist.extend(ytips) ylist.append(None) ############################ # build argument list call_list = [] for xtips,ytips in zip(xtris,ytris): call_list.append(xtips) call_list.append(ytips) call_list.append('-b') ############################ # time single call to matplotlib pp.subplot(1,3,2) t0 = default_timer() pp.fill(*call_list, facecolor='b',alpha=0.1, edgecolor='none') t1 = default_timer() pp.title('Single Plot extended call') print 'Execution time for extended call plotting = %f sec' % (t1-t0) ############################ # time single call to matplotlib pp.subplot(1,3,3) t0 = default_timer() pp.fill(xlist,ylist, facecolor='b',alpha=0.1,edgecolor='none') t1 = default_timer() pp.title('Single Plot Using None') print 'Execution time when using None = %f sec' % (t1-t0) pp.show()
Discussion:
Sequential call and extended call syntax produce the same plot results. However, the single list of points using ‘None’ can generate a different image if transparency is used. In the sample code, ‘alpha=0.1’ was used. When sequential fill or extended call syntax is used, the color from each polygon is additive. When the polygon corners are combined into a single list of points, the polygon colors are not additive. This is only an issue when using transparency. If alpha is set to 1.0 (or not set at all), then all of the plots will be identical.
Test Conditions:
- Win 7, 64 bit
- Intel i7
- PythonXY 2.5.6.2
Answers:
- How to speed up Matplotlib plots
- How to plot polygons in Matplotlib
Hi Ed,
ReplyDeletethere is also another possibility to plot collections of triangles/polygons using matplotlib's PolyCollection.
I extended your program with PolyCollections using a list of lists and a numpy array.
This gives a nice speedup in comparison to your 'extended call':
$ python polypoly.py
Execution time for sequential plotting = 1.403035 sec
Execution time for extended call plotting = 1.047142 sec
Execution time when using None = 0.010404 sec
Execution time when using PolyColl + list of lists = 0.170616 sec
Execution time when using PolyColl + np array = 0.132934 sec
The resulting plot: http://img3.picload.org/image/loigla/polypoly.png
The code: https://gist.github.com/1044620
As you can see in the image my 'extended call' plot looks different from the 'sequential call' one. It seems to ignore the edgecolor option somehow.
I was using Ubuntu Linux 10.04, Python 2.7.1 (EPD 7.0-2 (64-bit)), Matplotlib 1.0.1.
Thanks for you informative blog,
Jens
Dear Ed,
ReplyDeletethanks a lot for this great overview. I think the speed up using the seperation of polygons by None is overwhelming. But what would you suggest if different colors should be assigned to each polygon (e.g. colors provided as a list of RGB tuples)? Using the PolyCollection method, I can pass such a list as argument "facecolors". Do you have an idea how to proceed when using pyplot.fill?
Thanks a lot in advance for your help.
Bests regards,
Maik
Hello Ed,
ReplyDeleteI just stumbled upon this posting - its very welcome also over a year later. Thanks a lot!
The huge speedup when using 'None' actually only makes my project possible. I am plotting AMR (adaptive mesh refinement) data, so i need also to use different colors for the polygons. The way i do this right now is by creating x|ylists for each one of the say 256 color-levels which i then loop over with the 'None' method. Its a bit clumsy but the speedup is basically the same once you have many more polygons than color-levels.
Also i noticed that when using the PolyCollection method i get either some anti-aliasing problems (white background shows up as 'grid') or, when i switch of antialiasing, the high resolution regions are not rendered properly (just seeing the background until i zoom in enough) thats a bit of a puzzle there.
Just in case someone might find this useful as well.
Best,
oliver