Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Matplotlib path.contains_points returns false for points on some edges but not others

I'm attempting to use Matplotlib to find all points contained within a polygonal path, but it seems to be missing a few. More specifically, my path is a rectangle, and the points are on an underlying uniform grid. In the following test script it will not consider the points laying on the top line of the polygon as being part of the polygon, but will consider the points on the rest of the edges.

Code:

import matplotlib.path as mpltPath

polygon = [(5,5),(10,5),(10,10),(5,10)]
width =11
height =11

points = [[0,0],[1,0],[2,0],[3,0],[4,0],[5,0],[6,0],[7,0],[8,0],[9,0],[10,0],[11,0], \
          [0,1],[1,1],[2,1],[3,1],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[11,1],\
          [0,2],[1,2],[2,2],[3,2],[4,2],[5,2],[6,2],[7,2],[8,2],[9,2],[10,2],[11,2],\
          [0,3],[1,3],[2,3],[3,3],[4,3],[5,3],[6,3],[7,3],[8,3],[9,3],[10,3],[11,3],\
          [0,4],[1,4],[2,4],[3,4],[4,4],[5,4],[6,4],[7,4],[8,4],[9,4],[10,4],[11,4],\
          [0,5],[1,5],[2,5],[3,5],[4,5],[5,5],[6,5],[7,5],[8,5],[9,5],[10,5],[11,5],\
          [0,6],[1,6],[2,6],[3,6],[4,6],[5,6],[6,6],[7,6],[8,6],[9,6],[10,6],[11,6],\
          [0,7],[1,7],[2,7],[3,7],[4,7],[5,7],[6,7],[7,7],[8,7],[9,7],[10,7],[11,7],\
          [0,8],[1,8],[2,8],[3,8],[4,8],[5,8],[6,8],[7,8],[8,8],[9,8],[10,8],[11,8],\
          [0,9],[1,9],[2,9],[3,9],[4,9],[5,9],[6,9],[7,9],[8,9],[9,9],[10,9],[11,9],\
          [0,10],[1,10],[2,10],[3,10],[4,10],[5,10],[6,10],[7,10],[8,10],[9,10],[10,10],[11,10],\
          [0,11],[1,11],[2,11],[3,11],[4,11],[5,11],[6,11],[7,11],[8,11],[9,11],[10,11],[11,11]]


path = mpltPath.Path(polygon)
inside = path.contains_points(points)
print(inside)

As is, the code above will return

[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 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  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False False False False False False False False]

I would expect row 5 of the result to contain True values like the ones following it. If I change the coordinates in the polygon from 5 to 4.9, then I do get the result I expect.

I'm assuming this has something to do with misusing or misunderstanding the function, but I'm not quite sure what or how that might be.

EDIT: It was brought up that contains_points should be returning False for points that fall on the edges of the polygon. In my example, we see this behaviour for the top edge [5,5]-[10,5], but not for the other edges (i.e. [5,5]-[5,10], [5,10]-[10,10], and [10,10]-[10,5]). These three other edges correspond to the first and last columns with True values and the last row containing True values in the example output above. It is this apparent inconsistency that's the problem.

like image 503
Alex B Avatar asked Nov 06 '17 18:11

Alex B


1 Answers

update: Is now an open issue in matplotlib.


Excluding lines on the border might be the expected behavior for a function like contains_points. However, in this case, the points on the border of a polygon are not treated in a consistent manner:

In the example you give line 5 indicates the exclusion of points on the boarder, but rows 5 10 and line 10 indicate an inclusion of the boarder points.

Drawing the path with polygon[::-1], so inversed orientation, leads to the expected behavior for all borders except for line 10 where again inclusion is applied.

To me there is no logical pattern apparent here. But even if there is one, this behavior certainly is confusing and should be fixed.


Now you can still get the desired behavior by avoiding the points to lay on the boarders of your polygon. You can do so using the radius attribute of the contains_points function:

radius allows the path to be made slightly larger or smaller.

So if you provide some small, positive or negative value for the radius attribute like so:

# ...
path = mpltPath.Path(polygon)
inside = path.contains_points(points,radius=0.1)

print(inside)

you get:

[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 False False False
 False False False False False False False False False False False False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False  True  True  True  True  True  True False
 False False False False False False False False False False False False]

Note, whether the radius should be positive or negative depends on the orientation. You find more information about this here. As a rule of thumb: A positive radius expands the path when the path goes counterclockwise and shrinks the path when the path goes clockwise.

like image 124
jojo Avatar answered Nov 17 '22 18:11

jojo