我的问题简而言之:是否可以限制Qhull生成的Voronoi顶点的定义域?如果可以,如何做到?
我的问题和背景:我正在处理一个数据可视化项目,在2D场中有一些点。这些点有些重叠在一起,所以我会稍微“抖动”它们,以使它们不重叠。
我目前解决这个问题的方法是使用Lloyd's algorithm移动这些点。Lloyd算法基本上需要初始点位置,计算出Voronoi图,并在算法的每次迭代期间将每个点移动到其Voronoi区域的中心。
以下是Python示例:
我认为我可以通过限制Qhull Voronoi域来解决这个问题,从而仅在原始数据域内创建Voronoi顶点。
有可能以这种方式限制Qhull吗?任何人能提供的帮助都将不胜感激!
我的问题和背景:我正在处理一个数据可视化项目,在2D场中有一些点。这些点有些重叠在一起,所以我会稍微“抖动”它们,以使它们不重叠。
我目前解决这个问题的方法是使用Lloyd's algorithm移动这些点。Lloyd算法基本上需要初始点位置,计算出Voronoi图,并在算法的每次迭代期间将每个点移动到其Voronoi区域的中心。
以下是Python示例:
from scipy.spatial import Voronoi
from scipy.spatial import voronoi_plot_2d
import matplotlib.pyplot as plt
import numpy as np
import sys
class Field():
'''
Create a Voronoi map that can be used to run Lloyd relaxation on an array of 2D points.
For background, see: https://en.wikipedia.org/wiki/Lloyd%27s_algorithm
'''
def __init__(self, arr):
'''
Store the points and bounding box of the points to which Lloyd relaxation will be applied
@param [arr] arr: a numpy array with shape n, 2, where n is number of points
'''
if not len(arr):
raise Exception('please provide a numpy array with shape n,2')
x = arr[:, 0]
y = arr[:, 0]
self.bounding_box = [min(x), max(x), min(y), max(y)]
self.points = arr
self.build_voronoi()
def build_voronoi(self):
'''
Build a Voronoi map from self.points. For background on self.voronoi attrs, see:
https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.spatial.Voronoi.html
'''
eps = sys.float_info.epsilon
self.voronoi = Voronoi(self.points)
self.filtered_regions = [] # list of regions with vertices inside Voronoi map
for region in self.voronoi.regions:
inside_map = True # is this region inside the Voronoi map?
for index in region: # index = the idx of a vertex in the current region
# check if index is inside Voronoi map (indices == -1 are outside map)
if index == -1:
inside_map = False
break
# check if the current coordinate is in the Voronoi map's bounding box
else:
coords = self.voronoi.vertices[index]
if not (self.bounding_box[0] - eps <= coords[0] and
self.bounding_box[1] + eps >= coords[0] and
self.bounding_box[2] - eps <= coords[1] and
self.bounding_box[3] + eps >= coords[1]):
inside_map = False
break
# store hte region if it has vertices and is inside Voronoi map
if region != [] and inside_map:
self.filtered_regions.append(region)
def find_centroid(self, vertices):
'''
Find the centroid of a Voroni region described by `vertices`, and return a
np array with the x and y coords of that centroid.
The equation for the method used here to find the centroid of a 2D polygon
is given here: https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
@params: np.array `vertices` a numpy array with shape n,2
@returns np.array a numpy array that defines the x, y coords
of the centroid described by `vertices`
'''
area = 0
centroid_x = 0
centroid_y = 0
for i in range(len(vertices)-1):
step = (vertices[i, 0] * vertices[i+1, 1]) - (vertices[i+1, 0] * vertices[i, 1])
area += step
centroid_x += (vertices[i, 0] + vertices[i+1, 0]) * step
centroid_y += (vertices[i, 1] + vertices[i+1, 1]) * step
area /= 2
centroid_x = (1.0/(6.0*area)) * centroid_x
centroid_y = (1.0/(6.0*area)) * centroid_y
return np.array([centroid_x, centroid_y])
def relax(self):
'''
Moves each point to the centroid of its cell in the Voronoi map to "relax"
the points (i.e. jitter them so as to spread them out within the space).
'''
centroids = []
for region in self.filtered_regions:
vertices = self.voronoi.vertices[region + [region[0]], :]
centroid = self.find_centroid(vertices) # get the centroid of these verts
centroids.append(list(centroid))
self.points = centroids # store the centroids as the new point positions
self.build_voronoi() # rebuild the voronoi map given new point positions
##
# Visualize
##
# built a Voronoi diagram that we can use to run lloyd relaxation
field = Field(points)
# plot the points with no relaxation relaxation
plt = voronoi_plot_2d(field.voronoi, show_vertices=False, line_colors='orange', line_alpha=0.6, point_size=2)
plt.show()
# relax the points several times, and show the result of each relaxation
for i in range(6):
field.relax() # the .relax() method performs lloyd relaxation
plt = voronoi_plot_2d(field.voronoi, show_vertices=False, line_colors='orange', line_alpha=0.6, point_size=2)
plt.show()
正如我们所看到的,在每次迭代过程中(第2和第3帧),原始数据集中的点(第1帧,顶部)重叠越来越少,这非常好!
这种方法的问题在于,我目前正在从绘图中删除那些Voronoi区域边界超出初始数据集域的点。(如果我不这样做,最外层的点会很快射向超空间并远离其他点。)这最终意味着我最终要丢弃一些点,这是不好的。我认为我可以通过限制Qhull Voronoi域来解决这个问题,从而仅在原始数据域内创建Voronoi顶点。
有可能以这种方式限制Qhull吗?任何人能提供的帮助都将不胜感激!
更新
在收到 @tfinniga 的出色回复后,我撰写了一篇博客文章,详细介绍了Lloyd迭代的有界和无界形式。我还制作了一个小软件包lloyd,使得在数据集上运行有界Lloyd迭代更加容易。我希望分享这些资源,以帮助其他人进行相关分析。