代码之家  ›  专栏  ›  技术社区  ›  Markus Dutschke

matplotlib。路径包含_点:“半径”参数定义不一致

  •  4
  • Markus Dutschke  · 技术社区  · 7 年前

    问题:

    matplotlib.path 定义不一致。此函数用于检查指定点是否位于闭合路径的内部或外部。半径参数用于使路径变小/变大(取决于半径的符号)。通过这种方式,可以考虑/不考虑靠近路径的点。问题是,半径的符号取决于路径的方向(顺时针或逆时针)。 这个 (在我看来)是存在的,因为在检查点是在路径内还是在路径外时忽略了路径的方向。从数学的严格意义上讲,一个人说:沿途留下的一切都包括在内。

    简而言之:

    如果路径为逆时针方向,则正半径会考虑更多点。

    例子:

    1. 是包含正半径的点(靠近路径)
    2. 是否包含原点(位于两条路径的中间)

    代码:

    import matplotlib.path as path
    import numpy as np
    
    
    verts=np.array([[-11.5,  16. ],[-11.5, -16. ],[ 11.5, -16. ],[ 11.5,  16. ],[-11.5,  16. ]])
    
    ccwPath=path.Path(verts, closed=True) 
    cwPath=path.Path(verts[::-1,:], closed=True) 
    
    testPoint=[12,0]
    
    
    print('contains:         ','|\t', '[12,0], radius=3','|\t', '[12,0], radius=-3','|\t', '[0,0]|')
    
    print('counterclockwise: ','|\t'
    ,'{0:>16s}'.format(str(ccwPath.contains_point(testPoint,radius=3) )),'|\t'
    ,'{0:>17s}'.format(str(ccwPath.contains_point(testPoint,radius=-3) )),'|\t'
    ,ccwPath.contains_point([0,0],radius=0) ,'|\t'
    ,'=> radius increases tolerance \t'
    )
    
    print('clockwise:        ','|\t'
    ,'{0:>16s}'.format(str(cwPath.contains_point(testPoint,radius=3) )),'|\t'
    ,'{0:>17s}'.format(str(cwPath.contains_point(testPoint,radius=-3) )),'|\t'
    ,cwPath.contains_point([0,0],radius=0) ,'|\t'
    ,'=> radius decreases tolerance \t'
    )
    

    输出:

    contains:          |     [12,0], radius=3 |      [12,0], radius=-3 |     [0,0]|
    counterclockwise:  |                 True |                  False |     True |  => radius increases tolerance 
    clockwise:         |                False |                   True |     True |  => radius decreases tolerance 
    

    我提出的唯一想法是将路径强制为逆时针方向,并根据此使用半径。

    import matplotlib.path as path
    import numpy as np
    
    
    verts=np.array([[-11.5,  16. ],[-11.5, -16. ],[ 11.5, -16. ],[ 11.5,  16. ],[-11.5,  16. ]])
    
    #comment following line out to make isCounterClockWise crash
    #verts=np.array([[-11.5,  16. ],[-10,0],[-11.5, -16. ],[ 11.5, -16. ],[ 11.5,  16. ],[-11.5,  16. ]])
    
    ccwPath=path.Path(verts, closed=True) 
    cwPath=path.Path(verts[::-1,:], closed=True) 
    
    testPoint=[12,0]
    
    def isCounterClockWise(myPath):
    
            #directions from on vertex to the other
            dirs=myPath.vertices[1:]-myPath.vertices[0:-1]
            #rot: array of rotations at ech edge
            rot=np.cross(dirs[:-1],dirs[1:]) 
            if len(rot[rot>0])==len(rot):
                #counterclockwise
                return True
            elif len(rot[rot<0])==len(rot):
                #clockwise
                return False
            else:
                assert False, 'no yet implemented: This case applies if myPath is concave'
    
    def forceCounterClockWise(myPath):
        if not isCounterClockWise(myPath):
            myPath.vertices=myPath.vertices[::-1]
    
    
    forceCounterClockWise(cwPath)
    print('contains:         ','|\t', '[12,0], radius=3','|\t', '[12,0], radius=-3','|\t', '[0,0]|')
    
    print('counterclockwise: ','|\t'
    ,'{0:>16s}'.format(str(ccwPath.contains_point(testPoint,radius=3) )),'|\t'
    ,'{0:>17s}'.format(str(ccwPath.contains_point(testPoint,radius=-3) )),'|\t'
    ,ccwPath.contains_point([0,0],radius=0) ,'|\t'
    ,'=> radius increases tolerance \t'
    )
    
    print('forced ccw:      ','|\t'
    ,'{0:>16s}'.format(str(cwPath.contains_point(testPoint,radius=3) )),'|\t'
    ,'{0:>17s}'.format(str(cwPath.contains_point(testPoint,radius=-3) )),'|\t'
    ,cwPath.contains_point([0,0],radius=0) ,'|\t'
    ,'=> radius increases tolerance \t'
    )
    

    给出以下输出:

    contains:          |     [12,0], radius=3 |      [12,0], radius=-3 |     [0,0]|
    counterclockwise:  |                 True |                  False |     True |  => radius increases tolerance 
    forced ccw:       |                  True |                  False |     True |  => radius increases tolerance 
    

    1. 有人知道为什么会出现这种不一致吗?
    2. 有没有更优雅的方式来回避这个问题?例如:使用其他库作为包含点,以更智能/正确的方式使用半径参数,或使用预定义函数查找路径的方向。
    1 回复  |  直到 7 年前
        1
  •  4
  •   ImportanceOfBeingErnest    7 年前

    我认为唯一错误的假设是 . 相反 contains_point 字面意思是指闭合路径是否包含点。

    radius

    • 当路径逆时针方向移动并到达时,展开路径

    下面的示例显示了这一点,其中对于(逆)顺时针路径,绘制了扩展/顺行区域中包含的点。(红色= not contains_point ,蓝色= )

    enter image description here

    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()
    

    一个似乎根本没有记录的特殊性是 实际上通过以下方式扩展或收缩路径: radius/2. 1 ,点之间 -1.5 1.5 包括而不是点之间 -2 2 .

    一个选项可能是检查路径是否“大部分是逆时针的”。

    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
    

    这将允许调整 半径 对于“大部分顺时针”路径,

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

    这样,正半径总是扩展路径,负半径总是收缩路径。

    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()
    

    enter image description here