如何使用 scipy 的 Delaunay 找到两个不同集合中点的 Delaunay 邻居?

Posted

技术标签:

【中文标题】如何使用 scipy 的 Delaunay 找到两个不同集合中点的 Delaunay 邻居?【英文标题】:How to find the Delaunay neighbours of points in two different sets using scipy's Delaunay? 【发布时间】:2021-12-26 06:42:48 【问题描述】:

见https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.Delaunay.html

考虑两组点。对于 X_ 中的每个点,我想在“点”中找到最近的 delaunay 邻居。我认为一种缓慢的方法是一次形成points 的Delaunay 三角剖分加上X_ 的一个点,然后以某种方式进行邻居查找。使用 scipy(或其他工具)是否有更有效的方法?

from pylab import *
from scipy.spatial import Delaunay

np.random.seed(1)

points = np.random.randn(10, 2)
X_ = np.random.randn(4, 2)

tri = Delaunay(points, incremental=True)

plt.triplot(points[:,0], points[:,1], tri.simplices, alpha=0.5)

plot(X_[:, 0], X_[:, 1], 'ro', alpha=0.5)

for i, (x, y) in enumerate(points):
    text(x, y, i)
    
for i, (x, y) in enumerate(X_):
    text(x, y, i, color='g')


tri.points, tri.points.shape

【问题讨论】:

【参考方案1】:

理想情况下,最简单的解决方案可能如下所示:

Generate the Delaunay triangulation of "points"
For each point X in X_
    Add point X to the triangulation
    Get the neighbors of X in the augmented triangulation
    Remove point X from triangulation

使用scipy.spatial.Delaunay,您可以将incrementally add points 用于三角测量,但不是删除它们的机制。所以这种方法不能直接应用。

我们可以避免实际将新点添加到三角剖分中,而只需确定它将连接到哪些点而不实际更改三角剖分。基本上,我们需要识别Bowyer-Watson algorithm中的空腔,并收集空腔中三角形的顶点。如果建议的点位于其外接圆内,则 Delaunay 三角形是空腔的一部分。这可以从包含该点的三角形开始增量/局部构建,然后仅测试相邻的三角形。

这里是执行此算法的代码(针对单个测试点)。

import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Delaunay

num_tri_points = 50

rng = np.random.default_rng(1)

# points in the Delaunay triangulation
points = rng.random((num_tri_points, 2))

# test point for identifying neighbors in the combined triangulation
X = rng.random((1, 2))*.8 + .1

tri = Delaunay(points)

# determine if point lies in the circumcircle of simplex of Delaunay triangulation tri...
# note: this code is not robust to numerical errors and could be improved with exact predicates
def in_circumcircle(tri, simplex, point):
    coords = tri.points[tri.simplices[simplex]]
    ax = coords[0][0]
    ay = coords[0][1]
    bx = coords[1][0]
    by = coords[1][1]
    cx = coords[2][0]
    cy = coords[2][1]
    d = 2 * (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by))
    ux = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d
    uy = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d

    rad_sq = (ax-ux)*(ax-ux)+(ay-uy)*(ay-uy)
    point_dist_sq = (point[0]-ux)*(point[0]-ux)+(point[1]-uy)*(point[1]-uy)
    
    return point_dist_sq < rad_sq

# find the triangle containing point X
starting_tri = tri.find_simplex(X)

# remember triangles that have been tested so we don't repeat
tris_tested = set()

# collect the set of neighboring vertices in the potential combined triangulation
neighbor_vertices = set()

# queue triangles for performing the incircle check
tris_to_process = [ starting_tri[0] ]

while len(tris_to_process):        
    # get the next triangle
    tri_to_process = tris_to_process.pop()

    # remember that we have checked this triangle
    tris_tested.add(tri_to_process)

    # is the proposed point inside the circumcircle of the triangle
    if in_circumcircle(tri, tri_to_process, X[0,:]):
        # if so, add the vertices of this triangle as neighbors in the combined triangulation
        neighbor_vertices.update(tri.simplices[tri_to_process].flatten())
        # queue the neighboring triangles for processing
        for nbr in tri.neighbors[tri_to_process]:
            if nbr not in tris_tested:
                tris_to_process.append(nbr)

# plot the results    
plt.triplot(points[:,0], points[:,1], tri.simplices, alpha=0.7)
plt.plot(X[0, 0], X[0, 1], 'ro', alpha=0.5)
plt.plot(tri.points[list(neighbor_vertices)][:,0], tri.points[list(neighbor_vertices)][:,1], 'bo', alpha=0.7)
plt.show()

运行代码会给出以下图,其中包含原始三角剖分、测试点(红色)和识别出的相邻顶点(蓝色)。

【讨论】:

这很好,现在回到这个。我现在正在寻找 nD 中的实现(最快,所有技巧),即在圆周上。从一些快速阅读来看,我的猜测是快速库只是解决和硬编码(或使用 sympy)Cayley-Menger 矩阵的反转......请参阅此处的讨论。 math.stackexchange.com/questions/4056099/… 更新:这显然被称为 Tinfour 算法gwlucastrig.github.io/TinfourDocs/… 更新:见github.com/python-adaptive/adaptive/blob/master/adaptive/… 更新:我认为我们还必须区别对待船体外点的情况。可能只是用船体上的新点和两个相邻点创建 tris 并检查一些东西。我认为与向三角剖分添加一个点相同的操作。

以上是关于如何使用 scipy 的 Delaunay 找到两个不同集合中点的 Delaunay 邻居?的主要内容,如果未能解决你的问题,请参考以下文章

SciPy Delaunay三角剖分改变了单纯形的多个点,以实现参数的微小变化

将单纯形从 Delaunay 三角剖分转换为 networkx 图

scipy.spatial 中的凸壳例程给了我原来的一组点

来自 voronoi 镶嵌的 Delaunay 三角剖分

具有包裹尺寸的德劳内三角剖分?

从 scipy.spatial.Delauna 加速“find_simplex”