views:

118

answers:

3

I need to create a numpy 2D array which represents a binary mask of a polygon, using standard Python packages.

  • input: polygon vertices, image dimensions
  • output: binary mask of polygon (numpy 2D array)

(Larger context: I want to get the distance transform of this polygon using scipy.ndimage.morphology.distance_transform_edt.)

Can anyone show me how to do this?

+1  A: 

You could try to use python's Image Library, PIL. First you initialize the canvas. Then you create a drawing object, and you start making lines. This is assuming that the polygon resides in R^2 and that the vertex list for the input are in the correct order.

Input = [(x1, y1), (x2, y2), ..., (xn, yn)] , (width, height)

from PIL import Image, ImageDraw

img = Image.new('L', (width, height), 0)   # The Zero is to Specify Background Color
draw = ImageDraw.Draw(img)

for vertex in range(len(vertexlist)):
    startpoint = vertexlist[vertex]
    try: endpoint = vertexlist[vertex+1]
    except IndexError: endpoint = vertexlist[0] 
    # The exception means We have reached the end and need to complete the polygon
    draw.line((startpoint[0], startpoint[1], endpoint[0], endpoint[1]), fill=1)

# If you want the result as a single list
# You can make a two dimensional list or dictionary by iterating over the height and width variable
list(img.getdata())

# If you want the result as an actual Image
img.save('polgon.jpg', 'JPEG')

Is this what you were looking for, or were you asking something different?

Anil
Thanks Anil, that's basically what I was looking for. It's better if you use the ImageDraw.polygon method (ImageDraw.Draw(img).polygon(vertices, outline=1, fill=1)), and I used the numpy.reshape function to efficiently get a 2D array from the image data (import numpy, M = numpy.reshape(list(img.getdata()), (height, width))). I'll accept your answer if you edit it to include these things.
Isaac Sutherland
A: 

As a slightly more direct alternative to @Anil's answer, matplotlib has matplotlib.nxutils.points_inside_poly that can be used to quickly rasterize an arbitrary polygon. E.g.

import numpy as np
from matplotlib.nxutils import points_inside_poly

nx, ny = 10, 10
poly_verts = [(1,1), (5,1), (5,9),(3,2),(1,1)]

# Create vertex coordinates for each grid cell...
# (<0,0> is at the top left of the grid in this system)
x, y = np.meshgrid(np.arange(nx), np.arange(ny))
x, y = x.flatten(), y.flatten()

points = np.vstack((x,y)).T

grid = points_inside_poly(points, poly_verts)
grid = grid.reshape((ny,nx))

print grid

Which yields (a boolean numpy array):

[[False False False False False False False False False False]
 [False  True  True  True  True False False False False False]
 [False False False  True  True False False False False False]
 [False False False False  True False False False False False]
 [False False False False  True False False False False False]
 [False False False False  True False False False False False]
 [False False False False False False False False False False]
 [False False False False False False False False False False]
 [False False False False False False False False False False]
 [False False False False False False False False False False]]

You should be able to pass grid to any of the scipy.ndimage.morphology functions quite nicely.

Joe Kington
I was avoiding using points_inside_poly because it works with a list of coordinates rather than operating on a binary image directly. Because of this, and because PIL may be able to use hardware acceleration to render my polygon, it appears to me that Anil's solution is more efficient.
Isaac Sutherland
@Issac - Fair enough. As far as I know, PIL doesn't use hardware acceleration of any sort, though... (Has that changed recently?) Also, if you use PIL, there's no need to do `M = numpy.reshape(list(img.getdata()), (height, width)))` as you mention in your comment above. `numpy.array(img)` does the exact same thing much, much more efficiently.
Joe Kington
@Joe - Far out! Thanks for pointing out the numpy.array(img) functionality. And, true, PIL probably still doesn't use hardware acceleration.
Isaac Sutherland
A: 

The answer turns out to be quite simple:

import numpy
from PIL import Image, ImageDraw

# polygon = [(x1,y1),(x2,y2),...] or [x1,y1,x2,y2,...]
# width = ?
# height = ?

img = Image.new('L', (width, height), 0)
ImageDraw.Draw(img).polygon(polygon, outline=1, fill=1)
mask = numpy.array(img)
Isaac Sutherland
I use the image mode 'L', not '1', because Numpy-1.5.0 / PIL-1.1.7 does not support the `numpy.array(img)` conversion nicely for bivalue images. The top of the array contains 8 small subimages 1 / 8th the expected mask size, with the remaining 7 / 8ths of the array filled with garbage. Perhaps the conversion doesn't unpack the binary data properly?
Isaac Sutherland