Numpy Qhull生成Voronoi顶点的域约束

Numpy Qhull生成Voronoi顶点的域约束,numpy,scipy,geometry,computational-geometry,qhull,Numpy,Scipy,Geometry,Computational Geometry,Qhull,简而言之,我的问题是:有可能约束Qhull生成的Voronoi顶点的域吗?如果是的话,怎么能这样做呢 我的问题是:我正在研究一种数据可视化,在这种可视化中,我在二维字段中有点。这些点有点重叠,所以我稍微“抖动”它们,使它们不重叠 我目前执行此任务的方法是使用移动点。Lloyd算法基本上采用初始点位置,计算Voronoi映射,并在算法每次迭代期间将每个点移动到其Voronoi区域的中心 下面是Python中的一个示例: from scipy.spatial import Voronoi from

简而言之,我的问题是:有可能约束Qhull生成的Voronoi顶点的域吗?如果是的话,怎么能这样做呢

我的问题是:我正在研究一种数据可视化,在这种可视化中,我在二维字段中有点。这些点有点重叠,所以我稍微“抖动”它们,使它们不重叠

我目前执行此任务的方法是使用移动点。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()
来自scipy.spatial import Voronoi的

从scipy.spatial导入voronoi\u绘图\u 2d
将matplotlib.pyplot作为plt导入
将numpy作为np导入
导入系统
类字段():
'''
创建一个Voronoi贴图,可用于在二维点阵列上运行Lloyd松弛。
有关背景信息,请参见:https://en.wikipedia.org/wiki/Lloyd%27s_algorithm
'''
定义初始值(自身,arr):
'''
存储将应用劳埃德松弛的点的点和边界框
@param[arr]arr:形状为n,2的numpy数组,其中n是点数
'''
如果不是len(arr):
引发异常('请提供形状为n,2'的numpy数组)
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.points构建Voronoi贴图。有关self.voronoi attrs的背景信息,请参阅:
https://docs.scipy.org/doc/scipy-0.18.1/reference/generated/scipy.spatial.Voronoi.html
'''
eps=sys.float\u info.epsilon
self.voronoi=voronoi(self.points)
self.filtered_regions=[]#在Voronoi贴图内具有顶点的区域列表
对于self.voronoi.regions中的区域:
inside_map=True#该区域是否在Voronoi地图内?
对于区域中的索引:#索引=当前区域中顶点的idx
#检查索引是否在Voronoi映射内(索引==-1在映射外)
如果索引==-1:
内部映射=False
打破
#检查当前坐标是否位于Voronoi贴图的边界框中
其他:
coords=self.voronoi.vertices[索引]
如果不是(自边界盒[0]-eps=coords[0]和
自包围盒[2]-eps=coords[1]):
内部映射=False
打破
#如果区域有顶点且位于Voronoi贴图内,则存储该区域
如果是区域!=[]和内部地图:
self.filtered\u regions.append(区域)
def find_形心(自身、顶点):
'''
找到由“顶点”描述的Voroni区域的质心,并返回
具有该质心的x和y坐标的np数组。
此处用于求二维多边形质心的方法的方程
此处给出:https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
@params:np.array`vertices`形状为n,2的numpy数组
@返回np.array定义x,y坐标的numpy数组
由`顶点描述的质心的`
'''
面积=0
质心_x=0
质心_y=0
对于范围内的i(len(顶点)-1):
步骤=(顶点[i,0]*顶点[i+1,1])-(顶点[i+1,0]*顶点[i,1])
面积+=步长
质心_x+=(顶点[i,0]+顶点[i+1,0])*步长
质心_y+=(顶点[i,1]+顶点[i+1,1])*阶跃
面积/=2
质心_x=(1.0/(6.0*面积))*质心_x
质心_y=(1.0/(6.0*面积))*质心_y
返回np.array([centroid\ux,centroid\uy])
def放松(自我):
'''
在Voronoi贴图中将每个点移动到其单元的质心以“松弛”
点(即抖动它们,以便在空间内分散)。
'''
质心=[]
对于自过滤_区域中的区域:
顶点=self.voronoi.vertices[region+[region[0]],:]
质心=自身。查找_质心(顶点)#获取这些顶点的质心
质心。附加(列表(质心))
self.points=质心#将质心存储为新的点位置
self.build_voronoi()#在给定新点位置的情况下重建voronoi贴图
##
#想象
##
#建立了一个Voronoi图,我们可以用它来运行劳埃德松弛
字段=字段(点)
#绘制没有松弛的点
plt=voronoi\u plot\u 2d(field.voronoi,show\u vertices=False,line\u colors='orange',line\u alpha=0.6,point\u size=2)
plt.show()
#将点放松几次,并显示每次放松的结果
对于范围(6)中的i:
field.relax()#.relax()方法执行劳埃德松弛
plt=voronoi\u plot\u 2d(field.voronoi,show\u vertices=False,line\u colors='orange',line\u alpha=0.6,point\u size=2)
plt.show()
正如我们所看到的,在每次迭代(第2帧和第3帧)期间,原始数据集中的点(第1帧,顶部)重叠越来越少,这太棒了

这种方法的问题是,我目前正在从绘图中删除voronoi区域边界位于初始数据集域之外的点。(如果我不这样做,最外层的点很快就会射入超空间,并远离其余的点。)这最终意味着我会放弃点,这是不好的

我想我可以通过约束Qhull-Voronoi域来解决这个问题,以便只在原始数据域中创建Voronoi顶点

可以用这种方式约束Qhull吗?任何其他人能提供的帮助都将不胜感激


更新
在收到@tfininga的出色响应后,我以有界和无界的形式组织了一个详细的劳埃德迭代。我还编写了一个小程序包,使在数据集上运行有界Lloyd迭代更容易。我想分享这些资源,以防它们帮助其他人进行相关分析。

您遇到的核心问题是没有任何限制
#!/usr/bin/env python
"""
Implementation of a constrained Lloyds algorithm to remove node overlaps.
"""

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


def lloyds(positions, origin=(0,0), scale=(1,1), total_iterations=3):
    positions = positions.copy()
    for _ in range(total_iterations):
        centroids = _get_voronoi_centroids(positions)
        is_valid = _is_within_bbox(centroids, origin, scale)
        positions[is_valid] = centroids[is_valid]
    return positions


def _get_voronoi_centroids(positions):
    voronoi = Voronoi(positions)
    centroids = np.zeros_like(positions)
    for ii, idx in enumerate(voronoi.point_region):
        # ignore voronoi vertices at infinity
        # TODO: compute regions clipped by bbox
        region = [jj for jj in voronoi.regions[idx] if jj != -1]
        centroids[ii] = _get_centroid(voronoi.vertices[region])
    return centroids


def _get_centroid(polygon):
    # TODO: formula may be incorrect; correct one here:
    # https://en.wikipedia.org/wiki/Centroid#Of_a_polygon
    return np.mean(polygon, axis=0)


def _is_within_bbox(points, origin, scale):
    minima = np.array(origin)
    maxima = minima + np.array(scale)
    return np.all(np.logical_and(points >= minima, points <= maxima), axis=1)


if __name__ == '__main__':

    positions = np.random.rand(200, 2)
    adjusted = lloyds(positions)

    fig, axes = plt.subplots(1, 2, sharex=True, sharey=True)
    axes[0].plot(*positions.T, 'ko')
    axes[1].plot(*adjusted.T, 'ro')

    for ax in axes:
        ax.set_aspect('equal')

    fig.tight_layout()
    plt.show()