matplotlib.Path.contains_points:" radius"参数定义不一致

时间:2017-08-30 09:53:37

标签: python matplotlib path contains point

问题:

matplotlib.path中函数contains_point中的radius参数定义不一致。此函数检查指定的点是在闭合路径的内部还是外部。 radius参数用于使路径稍微更小/更大(取决于半径的符号)。通过这种方式,可以将点数取入/取出,这些点接近路径。问题是,半径的符号取决于路径的方向(顺时针或逆时针)。 不一致(在我看来)是存在的,因为在检查点是在路径内部还是在路径之外时,路径的方向被忽略。在严格的数学意义上,人们说:沿路径留下的所有东西都包括在内。

简而言之:

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

示例:

在以下示例中,检查了3个案例 - 每个案例都是顺时针和逆时针路径:

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

    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. 有没有更优雅的方法来规避这个问题?示例可能是:为contains_point使用其他库,以更智能/更正确的方式使用radius参数或使用预定义函数查找路径的方向。

1 个答案:

答案 0 :(得分:4)

我认为这里唯一错误的假设是&#34;路径中留下的所有内容都包括在内。&#34; 。相反,contains_point字面意思是封闭路径是否包含一个点。

然后将radius定义为

  • 当路径逆时针旋转到
  • 时展开路径
  • 当路径顺时针方向缩小路径时

这在以下示例中示出,其中对于(计数器)顺时针路径,绘制了扩展/收缩区域中包括的点。 (红色= not contains_point,蓝色= 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实际上是radius/2.扩展或缩小路径。如上所示,半径为1,包含-1.51.5之间的点,而不是-22之间的点。

关于路径的方向,可能没有一个固定方向。如果您有3个点,则可以明确地确定方向为顺时针,逆时针(或共线)。一旦你有更多的点,方向的概念就没有明确定义。

一个选项可能是检查路径是否是&#34;主要是逆时针&#34;。

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

这样就可以调整radius,如果是&#34;主要是顺时针&#34;路径,

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