matplotlib.Path.contains_points : “radius” parameter defined inconsistently

后端 未结 1 529
一整个雨季
一整个雨季 2021-01-06 11:20

Problem:

The radius parameter in the function contains_point in matplotlib.path is defined inconsistently. This function checks if a specified point

相关标签:
1条回答
  • 2021-01-06 11:40

    I think the only wrong assumption here is "everything which is left along the path is included.". Instead, contains_point literally means whether or not a closed path includes a point.

    The radius is then defined to

    • expand the path when the path goes counterclockwise and to
    • shrink the path when the path goes clockwise

    This is shown in the following example, where for a (counter)clockwise path the points included in the expanded/shunk area are plotted. (red = not contains_point, blue = contains_point)

    import matplotlib.pyplot as plt
    import matplotlib.path as path
    import matplotlib.patches as patches
    import numpy as np
    
    verts=np.array([[-1,  1 ],[-1, -1 ],[ 1, -1 ],[ 1, 0 ],[ 1,  1],[-1,  1 ]])
    
    ccwPath=path.Path(verts, closed=True) 
    cwPath=path.Path(verts[::-1,:], closed=True) 
    
    paths = [ccwPath, cwPath]
    pathstitle = ["ccwPath", "cwPath"]
    radii = [1,-1]
    
    testPoint=(np.random.rand(400,2)-.5)*4
    
    c = lambda p,x,r: p.contains_point(x,radius=r)
    
    fig, axes = plt.subplots(nrows=len(paths),ncols=len(radii))
    
    for j  in range(len(paths)):
        for i in range(len(radii)):
            ax = axes[i,j]
            r = radii[i]
            patch = patches.PathPatch(paths[j], fill=False, lw=2)
            ax.add_patch(patch)
            col = [c(paths[j], point[0], r) for point in zip(testPoint)]
            ax.scatter(testPoint[:,0], testPoint[:,1], c=col, s=8, vmin=0,vmax=1, cmap="bwr_r")
            ax.set_title("{}, r={}".format(pathstitle[j],radii[i]) )
    
    plt.tight_layout()
    plt.show()
    

    A particularity, which does not seem to be documented at all is that radius actually expands or shrinks the path by radius/2.. This is seen above as with a radius of 1, points between -1.5 and 1.5 are included instead of points between -2 and 2.

    Concerning the orientation of a path, there may not be one fix orientation. If you have 3 points, orientation can be unambiguously determined to be clockwise, counterclockwise (or colinear). Once you have more points, the concept of orientation is not well defined.

    An option may be to check if the path is "mostly counterclockwise".

    def is_ccw(p):
        v = p.vertices-p.vertices[0,:]
        a = np.arctan2(v[1:,1],v[1:,0])
        return (a[1:] >= a[:-1]).astype(int).mean() >= 0.5
    

    This would then allow to adjust the radius in case of "mostly clockwise" paths,

    r = r*is_ccw(p) - r*(1-is_ccw(p))
    

    such that a positive radius always expands the path and a negative radius always shrinks it.

    Complete example:

    import matplotlib.pyplot as plt
    import matplotlib.path as path
    import matplotlib.patches as patches
    import numpy as np
    
    verts=np.array([[-1,  1 ],[-1, -1 ],[ 1, -1 ],[ 1, 0 ],[ 1,  1],[-1,  1 ]])
    
    ccwPath=path.Path(verts, closed=True) 
    cwPath=path.Path(verts[::-1,:], closed=True) 
    
    paths = [ccwPath, cwPath]
    pathstitle = ["ccwPath", "cwPath"]
    radii = [1,-1]
    
    testPoint=(np.random.rand(400,2)-.5)*4
    
    c = lambda p,x,r: p.contains_point(x,radius=r)
    
    def is_ccw(p):
        v = p.vertices-p.vertices[0,:]
        a = np.arctan2(v[1:,1],v[1:,0])
        return (a[1:] >= a[:-1]).astype(int).mean() >= 0.5
    
    fig, axes = plt.subplots(nrows=len(radii),ncols=len(paths))
    
    for j  in range(len(paths)):
        for i in range(len(radii)):
            ax = axes[i,j]
            r = radii[i]
            isccw = is_ccw(paths[j]) 
            r = r*isccw - r*(1-isccw)
            patch = patches.PathPatch(paths[j], fill=False, lw=2)
            ax.add_patch(patch)
            col = [c(paths[j], point[0], r) for point in zip(testPoint)]
            ax.scatter(testPoint[:,0], testPoint[:,1], c=col, s=8, vmin=0,vmax=1, cmap="bwr_r")
            ax.set_title("{}, r={} (isccw={})".format(pathstitle[j],radii[i], isccw) )
    
    plt.tight_layout()
    plt.show()
    

    0 讨论(0)
提交回复
热议问题