How to get list of points inside a polygon in python?

37,455

Solution 1

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

Solution 2

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

Solution 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)]

Solution 4

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.

Share:
37,455
Farshid Ashouri
Author by

Farshid Ashouri

I've been designing and building distributed systems for more years than I probably care to mention. However, since it is also so much fun I'll probably keep on doing this onward - until they'll pry the keyboard out of my cold dead hands or something to that effect. :). Currently I am a lead software engineer at Revolut Ltd. ___ _ _ / __\_ _ _ __ ___| |__ ___ ___ __| | / _\/ _` | '__/ __| '_ \ / _ \/ _ \/ _` | / / | (_| | | \__ \ | | | __/ __/ (_| | \/ \__,_|_| |___/_| |_|\___|\___|\__,_|

Updated on July 19, 2022

Comments

  • Farshid Ashouri
    Farshid Ashouri almost 2 years

    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.