matplotlib.path.contains_points inconsistent behavior

Question:

I have been using matplotlib.path.contains_points() method without any figures or plots. I am getting inconsistent results depending on the path. In the following example, a simple square path works, but a longer path from an ellipse does not:

import numpy as np
from skimage.draw import ellipse_perimeter
from matplotlib.path import Path
import matplotlib.pyplot as plt

s = 100
squarepath = Path([(0,0), (0, s), (s,s), (s, 0)])
print(squarepath.contains_points([(s/2, s/2)]))

(xv,yv) = ellipse_perimeter(200,360,260,360)
xyverts =  np.stack((xv,yv),axis=1)
ellipsepath = Path(xyverts)
pt = (213,300)
print(ellipsepath.contains_points([pt]))

fig,ax = plt.subplots()
plt.scatter(ellipsepath.vertices[:,0],ellipsepath.vertices[:,1])
plt.scatter(pt[0],pt[1])
plt.show()

I am using contains_points() before anything is plotted, so there should not be any data versus display coordinates issue as is discussed in other similar questions about contains_points(). What else could be going on here?

Asked By: J B

||

Answers:

The problem is that ellipse_perimeter is returning the points of the ellipse perimeter in an order that does not define a path. Vertices are then created between points accross your ellipse. You can check that by ploting your path as patch instead of using scatter:

from matplotlib.patches import PathPatch
ax.add_patch(PathPatch(ellipsepath, facecolor='yellow', lw=1)) # instead of scatter(ellipsepath...)

Output (without changing anything else to your code):

ellipse_path_wrong

As you can see instead of seeing a yellow ellipse as specified with facecolor, we only see the lines (linking points all over the perimeter).

I guess the easiest way to solve this would be to use patches.Ellipse and call get_path().

However, if you’re stuck with skimage, you’ll have to sort your points before creating a path:

import numpy as np
from skimage.draw import ellipse_perimeter
from matplotlib.path import Path
import matplotlib.pyplot as plt
from matplotlib.patches import PathPatch

(xv,yv) = ellipse_perimeter(200,360,260,360)
xyverts =  list(zip(xv, yv))
pt_top = sorted([(x,y) for x,y in xyverts if y >= 360], key=lambda c: c[0])
pt_bottom = sorted([(x,y) for x,y in xyverts if y < 360], key=lambda c: -c[0])

xyverts = pt_top + pt_bottom

ellipsepath = Path(xyverts)
pt = (213,300)
print(ellipsepath.contains_points([pt]))

fig,ax = plt.subplots()
ax.add_patch(PathPatch(ellipsepath, facecolor='yellow', lw=1))
plt.scatter(pt[0],pt[1])
plt.show()

Output:

enter image description here

Answered By: Tranbi
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.