Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Path 'contains_points' does not work well with Bezier curves #8734

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Jeitan opened this issue Jun 8, 2017 · 2 comments
Closed

Path 'contains_points' does not work well with Bezier curves #8734

Jeitan opened this issue Jun 8, 2017 · 2 comments

Comments

@Jeitan
Copy link

Jeitan commented Jun 8, 2017

I am uncertain whether this is a bug report, an enhancement request, or both. I am observing that the behavior of Path.contains_points does not well handle paths that contain Bezier curves (cubic curves specificially although it may be more general). When I fill out a True/False mask of points underneath a particular path, the results are quite distorted.

This code makes a simple closed path with one line and one cubic curve, and produces the following plot:

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

# Make the Path
verts = [(1.0, 1.5), (-2.0, 0.25), (-1.0, 0.0), (1.0, 0.5), (1.0, 1.5)]
codes = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CLOSEPOLY]
path1 = Path(verts, codes)

# Make a field with points to select
nx, ny = 101, 51
x = np.linspace(-2, 2, nx)
y = np.linspace(0, 2, ny)

yy, xx = np.meshgrid(y, x)
pts = np.column_stack((xx.ravel(), yy.ravel()))

# Construct a True/False array of contained points
tf = path1.contains_points(pts).reshape(nx, ny)

# Make a PathPatch for display
patch1 = PathPatch(path1, facecolor='c', edgecolor='b', lw=2, alpha=0.5)

# Plot the true/false array, the patch, and the vertices
fig, ax = plt.subplots()
ax.imshow(tf.T, origin='lower', extent=(x[0], x[-1], y[0], y[-1]))
ax.add_patch(patch1)
ax.plot(*zip(*verts), 'ro-')

plt.show()

contains_points mask overlaid with actual Patch

I see two things here:

  1. Clearly there are some approximations happening which severely chop off the tip of the cubic curve. I can't see anything in the public API that would allow me to tell it that I want more lines for the approximation.
  2. The approximated points look like they're not even on the curve.

I first posted this as a Stack Overflow question here. As mentioned in a comment there, the same behavior results from the to_polygons method.

Also, I have a hard time following really low-level guts of Agg, so I don't know for sure but it may be related to issue #6076. In any case, the workaround expressed there doesn't seem to work for my case...

Matplotlib version

  • Operating System: Windows 10 64-bit
  • Matplotlib Version: 2.0.0
  • Python Version: 3.5.2
  • Jupyter Version (if applicable): 4.2.1 (not really applicable though)
  • Other Libraries:

This is from a Windows installation of Anaconda 4.3.0.

@tacaswell
Copy link
Member

I think this is the same issue as #6076 and a slightly different set of transforms makes it work (if you crank the number of pixels in your image way up it follows pretty well) but there might be some race conditions for when the transforms are setup / the window resized, etc. You might be interested in something like shapely (https://pypi.python.org/pypi/Shapely) which may have an API better tuned for end-user work.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib.patches import PathPatch
import matplotlib.cm as mcm
from copy import copy

# Make the Path
verts = [(1.0, 1.5), (-2.0, 0.25), (-1.0, 0.0), (1.0, 0.5), (1.0, 1.5)]
codes = [Path.MOVETO, Path.CURVE4, Path.CURVE4, Path.CURVE4, Path.CLOSEPOLY]
path1 = Path(verts, codes)

# Make a field with points to select
nx, ny = np.array([101, 51]) # * 10
x = np.linspace(-2, 2, nx)
y = np.linspace(0, 2, ny)

yy, xx = np.meshgrid(y, x)
pts = np.column_stack((xx.ravel(), yy.ravel()))

# Construct a True/False array of contained points
tf = path1.contains_points(pts).reshape(nx, ny)

# Make a PathPatch for display
patch1 = PathPatch(path1, facecolor='c', edgecolor='b', lw=2, alpha=0.5)

# set up a color map which sets under to transparent
cmap = copy(mcm.get_cmap('gray_r'))
cmap.set_under('w', alpha=0)


# Plot the true/false array, the patch, and the vertices
fig, ax = plt.subplots()
ax.add_patch(patch1)
ax.plot(*zip(*verts), 'ro-')

hit_patch = path1.transformed(ax.transData)
tf2 = hit_patch.contains_points(ax.transData.transform(pts)).reshape(nx, ny)
ax.imshow(tf.T, origin='lower', extent=(x[0], x[-1], y[0], y[-1]),
          alpha=.75, cmap=cmap, vmin=.5)




ax.imshow(tf2.T, origin='lower', extent=(x[0], x[-1], y[0], y[-1]),
          alpha=.75, cmap=cmap, vmin=.5)


plt.show()

so

@Jeitan
Copy link
Author

Jeitan commented Jun 13, 2017

Huh, that transformation did work? I must have just not tried it right. Thanks for confirming that it's the same as 6076, I couldn't tell for sure. Unfortunately I don't have access to shapely on the system I need it for (it's an offline system and getting things loaded onto it is agonizingly slow and painful), but at least there is this workaround. Thanks!

@Jeitan Jeitan closed this as completed Jun 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants