Sunday, February 27, 2011

How to quickly plot polygons in Matplotlib

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.

PolygonSample


Test Conditions:


Answers:

  • How to speed up Matplotlib plots
  • How to plot polygons in Matplotlib
This work is licensed under a Creative Commons Attribution By license.

3 comments:

  1. Hi Ed,

    there 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

    ReplyDelete
  2. Dear Ed,

    thanks 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

    ReplyDelete
  3. Hello Ed,

    I 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

    ReplyDelete