26

I searched a lot and cant find any practical answer to my question. I have a polygon. For example:

    [(86, 52), (85, 52), (81, 53), (80, 52), (79, 48), (81, 49), (86, 53),
     (85, 51), (82, 54), (84, 54), (83, 49), (81, 52), (80, 50), (81, 48),
     (85, 50), (86, 54), (85, 54), (80, 48), (79, 50), (85, 49), (80, 51),
     (85, 53), (82, 49), (83, 54), (82, 53), (84, 49), (79, 49)]

I want to get a list of all the points inside this border polygon. I heard alot about polygon triangulation techniques or linear/flood/intersection/... filling algorithms. but i cant really come up with an efficient way of implementing this. This poly is small, imagine a polygon with 1 billion points. I am now using PIL draw polygon to fill the poly with red color and loop inside it to find red points. This is a horribly slow technique:

def render(poly, z):
    xs = [i[0] for i in poly]
    ys = [i[1] for i in poly]
    minx, maxx = min(xs), max(xs)
    miny, maxy = min(ys), max(ys)
    X = maxx - minx + 1
    Y = maxy - miny + 1
    newPoly = [(x - minx, y - miny) for (x, y) in polygons]
    i = Image.new("RGB", (X, Y))
    draw = ImageDraw.Draw(i)
    draw.polygon(newPoly, fill="red")
    # i.show()
    tiles = list()
    w, h = i.size
    print w, h
    for x in range(w):
        for y in range(h):
            data = i.getpixel((x, y))
            if data != (0, 0, 0):
                tiles.append((x + minx, y + miny))

    return tiles

I am searching for a Pythonic way of solving this problem. Thank you all.

Farshid Ashouri
  • 16,143
  • 7
  • 52
  • 66
  • 1
    does `shapely` have anyting to offer you? https://pypi.python.org/pypi/Shapely – dm03514 Jan 24 '14 at 18:10
  • I used shapely, but cant find anything for this problem. Thanks, I will search that. – Farshid Ashouri Jan 24 '14 at 18:16
  • 1
    You're probably looking for this: http://rosettacode.org/wiki/Ray-casting_algorithm. – Joel Cornett Jan 24 '14 at 18:24
  • 1
    Well, The function in your link gives and point and a poly to check if its inside or not. It's not what I need. I can manually create a grid and loop for all items. but Im looking for a more straight-forward method. Thanks btw. – Farshid Ashouri Jan 24 '14 at 19:26
  • @Farsheed Have you found an answer to your question? I am now searching for a similar solution, I have co-ordinates of 100,000 points and co-ordinates of several polygons. I need to remove those points that are inside these polygons – Srivatsan Nov 19 '14 at 10:28
  • Yes, I wrote a Python C module based on a PiintInPoly algorithm. Shapely also has the feature. – Farshid Ashouri Nov 19 '14 at 12:52

5 Answers5

38

I suggest to use matplotlib contains_points()

from matplotlib.path import Path

tupVerts=[(86, 52), (85, 52), (81, 53), (80, 52), (79, 48), (81, 49), (86, 53),
 (85, 51), (82, 54), (84, 54), (83, 49), (81, 52), (80, 50), (81, 48),
 (85, 50), (86, 54), (85, 54), (80, 48), (79, 50), (85, 49), (80, 51),
 (85, 53), (82, 49), (83, 54), (82, 53), (84, 49), (79, 49)]


x, y = np.meshgrid(np.arange(300), np.arange(300)) # make a canvas with coordinates
x, y = x.flatten(), y.flatten()
points = np.vstack((x,y)).T 

p = Path(tupVerts) # make a polygon
grid = p.contains_points(points)
mask = grid.reshape(300,300) # now you have a mask with points inside a polygon
Stanpol
  • 966
  • 1
  • 10
  • 20
  • 1
    you can use np.indices() to make an array with the coordinates – lesolorzanov Jan 18 '18 at 09:27
  • 1
    This is much faster than using shapely to evaluate each point inside a polygon. Working with 1024x1024 images, it was taking me 4 to 6 minutes using shapely to compute each pixel's in or out value relative to a polygon. Using the above matplotlib contains_points() I could do it in average 5 seconds. – the_cat_lady Oct 27 '19 at 11:56
  • This is also significantly faster than using `MultiPoint(points).intersection(polygon)` in Shapely. – Georgy Feb 12 '20 at 10:23
4

Building upon RemcoGerlich's answer here's a validated function:

import numpy as np
import mahotas

def render(poly):
    """Return polygon as grid of points inside polygon.

    Input : poly (list of lists)
    Output : output (list of lists)
    """
    xs, ys = zip(*poly)
    minx, maxx = min(xs), max(xs)
    miny, maxy = min(ys), max(ys)

    newPoly = [(int(x - minx), int(y - miny)) for (x, y) in poly]

    X = maxx - minx + 1
    Y = maxy - miny + 1

    grid = np.zeros((X, Y), dtype=np.int8)
    mahotas.polygon.fill_polygon(newPoly, grid)

    return [(x + minx, y + miny) for (x, y) in zip(*np.nonzero(grid))]

Example:

poly = [
    [0, 0],
    [0, 10],
    [10, 10],
    [10, 0]
]

plt.figure(None, (5, 5))
x, y = zip(*render(poly))
plt.scatter(x, y)
x, y = zip(*poly)
plt.plot(x, y, c="r")
plt.show()

enter image description here

Ulf Aslak
  • 7,876
  • 4
  • 34
  • 56
3

I think drawing the polygon and filling it is a good start, you're going to need something like that anyway and those algorithms are usually fine tuned in C. But don't use a RGB image, use a black/white image, and use numpy.where() to find the pixels where it's 1.

According to this question, the mahotas library has a fill_polygon function that works with numpy arrays.

I'm starting the following code from your function (I would subtract the minx and maxx too) but note that I can't test it at all, I'm not on my dev machine.

import numpy as np
import mahotas

def render(poly): # removed parameter 'z'
    xs = [i[0] for i in poly]
    ys = [i[1] for i in poly]
    minx, maxx = min(xs), max(xs)
    miny, maxy = min(ys), max(ys)
    X = maxx - minx + 1
    Y = maxy - miny + 1
    newPoly = [(x - minx, y - miny) for (x, y) in poly]           

    grid = np.zeros((X, Y), dtype=np.int8)
    mahotas.polygon.fill_polygon(newPoly, grid)

    return [(x + minx, y + miny) for (x, y) in np.where(grid)]
RemcoGerlich
  • 30,470
  • 6
  • 61
  • 79
  • I am testing it. The first point is it should be "from mahotas import polygon". Let me test it more. – Farshid Ashouri Jan 24 '14 at 20:10
  • Ok, Your code is 38% slower than PIL fill method. tested with 1.3M polygons. It takes 5.6 sec for PIL and 6.7 sec for np+mahotas. – Farshid Ashouri Jan 24 '14 at 20:18
  • Oh, cool. That's less than 38% difference, though :-). Can you fill the polygon with PIL and use np.where to find the points? I think PIL's images are numpy arrays too? – RemcoGerlich Jan 24 '14 at 20:21
  • 1
    I wonder how much of the time is spent subtracting minx and miny and adding it back later. Can't really think of a way to avoid it though, if your polygons have arbitrary indices. – RemcoGerlich Jan 24 '14 at 20:25
  • My problem is now how can i use np.where with a img. We can use np.aaray(img) got converting image to array. but how to use where and how can i convert it back to normal list of tiles? – Farshid Ashouri Jan 24 '14 at 20:52
  • To get speed, you should do those loops with numpy arrays: ``newPoly = poly + [minx, miny]``... – luispedro Jan 27 '14 at 08:26
2

You can use a numpy matrix like a binary image, which can be used with Opencv for example or other image processing libs, Solution 1 So a matrix which size is L x H where

L=max(x) - min (x)
H=max(y) - min (y)

As entry we have your list of tuple(x,y) you gave above which name is poly for example :

import numpy as np
matrix =np.zeros((L,H),dtype=np.int32) # you can use np.uint8 if unsigned x ,y

So we have now a matrix of Size L x H filled with 0, we put now 1 at polygon points positions

I think you can simple do that

matrix[poly]=1  # which will put 1 at each (x,y) of the list **poly**

we interpret this as a binary (black/white) image which have a contour drawn on it Assume we want to detect this new contour

import cv2 # opencv import
ContoursListe,hierarchy = cv2.findContours(self.thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
poly2=ContoursListe[0] # we take the first only contour

Note : poly2 is containing a list of points of your polygon and all points forming it, i mean all points of each vertices of your polygon which is what you need can find useful !! you can use cv2.CHAIN_APPROX_SIMPLE parameter to get poly2 containing only end points of the polygon lines which is lighter and which was our input :) important :the type of poly2 is numpy array ,its shape is (n,1,2) and not (n,2)

Now we draw this contour on this image(matrix) and will fill it too :)

cv2.drawContours(matrix,[poly2],-1,(1),thickness=-1) thickness=-1

now we have a matrix where there is 1 on each points forming and filling the polygon , "thickness=-1" has forced to fill this contour, you can put set thickness = 1 to get only the borders if you want to translate, you can do it by adding parameter offset(xtran,ytrans)

to get the indices of all theses points simply call

list_of_points_indices=numpy.nonzero(matrix)

Solution 2

Which is smarter is to directly transform your list of points (poly) to a contour format (poly2) and draw it on the matrix

poly2=poly.reshape(-1,1,2).astype(np.int32)

and draw it on the Matrix matrix

matrix =np.zeros((L,H),dtype=np.int32)

cv2.drawContours(matrix,[poly2],-1,(1),thickness=-1)

And get the list of this points with :

list_of_points_indices=numpy.nonzero(matrix)

Play with thickness to fill or not the polygon , see solution 1 for more details.

Cherif KAOUA
  • 834
  • 12
  • 21
0

Try this code. poly_coords are the coordinates of your polygon, 'coord' is coordinate of point you want to check if it is inside the polygon.

def testP(coord, poly_coords):
    """
    The coordinates should be in the form of list of x and y
    """
    test1 = n.array(poly_coords)
    test2 = n.vstack((poly_coords[1:], poly_coords[:1]))
    test  = test2-test1
    m = test[:,1]/test[:,0]
    c = test1[:,1]-m*test1[:,0]
    xval = (coord[1]-c)/m
    print 'xVal:\t'; print xval
    print (test1[:,0]-xval)*(test2[:,0]-xval)
    check = n.where((xval>=coord[0])&((test1[:,0]-xval)*(test2[:,0]-xval)<0))[0]
    print check
    print len(check)
    if len(check)%2==0:
        return False
    else:
        return True

If you want to make it even faster, take out the part of algo related to polygon, slope and offset and run the rest of code using 'map' function. Something like this:

test1 = n.array( your polygon)
test2 = n.vstack((test1[1:], test1[:1]))
test  = test2-test1
m = test[:,1]/test[:,0]
c = test1[:,1]-m*test1[:,0]

def testP(coord):
    """
    The coordinates should be in the form of list of x and y
    """
    global test, test1, test2, m,c
    xval = (coord[1]-c)/m
    check = n.where((xval>=coord[0])&((test1[:,0]-xval)*(test2[:,0]-xval)<0))[0]
    if len(check)%2==0:
        return False
    else:
        return True
coords = n.array(( your coords in x,y ))
map (testP, coords)

You can remove 'print' commands if you want. This code is made for python 2.7

Ishan Tomar
  • 1,488
  • 1
  • 16
  • 20
  • Would you please explain the `your polygon` and `your coords in x,y`? It seems the `your polygon` means something like `poly = [[0, 0], [0, 10], [10, 10], [10, 0]]`. But I don't know what and how is `your coords in x,y` exactly! – mtoloo Sep 05 '18 at 07:31
  • For polygon, you are absolutely right. 'coords of x,y' refers to coordinate of point to check for being within polygon. The coordinates should be a numpy array such as (5,6) where position of point in x axis is 5 and y axis is 6 – Ishan Tomar Sep 17 '18 at 09:38