how can I calculate the centroid of a convex hull using python and scipy? All I found are methods for computing Area and Volume.
regards,frank.
Assuming you have constructed the convex hull using scipy.spatial.ConvexHull, the returned object should then have the positions of the points, so the centroid may be as simple as,
import numpy as np
from scipy.spatial import ConvexHull
points = np.random.rand(30, 2) # 30 random points in 2-D
hull = ConvexHull(points)
#Get centoid
cx = np.mean(hull.points[hull.vertices,0])
cy = np.mean(hull.points[hull.vertices,1])
Which you can plot as follows,
import matplotlib.pyplot as plt
#Plot convex hull
for simplex in hull.simplices:
plt.plot(points[simplex, 0], points[simplex, 1], 'k-')
#Plot centroid
plt.plot(cx, cy,'x',ms=20)
plt.show()
The scipy convex hull is based on Qhull which should have method centrum, from the Qhull docs,
A centrum is a point on a facet's hyperplane. A centrum is the average of a facet's vertices. Neighboring facets are convex if each centrum is below the neighbor facet's hyperplane.
where the centrum is the same as a centroid for simple facets,
For simplicial facets with d vertices, the centrum is equivalent to the centroid or center of gravity.
As scipy doesn't seem to provide this, you could define your own in a child class to hull,
class CHull(ConvexHull):
def __init__(self, points):
ConvexHull.__init__(self, points)
def centrum(self):
c = []
for i in range(self.points.shape[1]):
c.append(np.mean(self.points[self.vertices,i]))
return c
hull = CHull(points)
c = hull.centrum()
The simple 'mean' method is not correct if the hull has points that are irregularly spaced around the perimeter, or at least will give a skewed answer. The best approach that I use is to calculate the centroids of the delaunay triangles of the hull points. This will weight the calculation to calculate the centroid as the COM of the shape, not just the average of vertices:
from scipy.spatial import ConvexHull, Delaunay
def _centroid_poly(poly):
T = Delaunay(poly).simplices
n = T.shape[0]
W = np.zeros(n)
C = 0
for m in range(n):
sp = poly[T[m, :], :]
W[m] = ConvexHull(sp).volume
C += W[m] * np.mean(sp, axis=0)
return C / np.sum(W)
poly = np.random.rand(30, 2)
# will calculate the centroid of the convex hull of poly
centroid_hull = _centroid_poly(poly)
Something like this should work.
To find the geometric centre of the hull's vertices simply use,
# Calculate geometric centroid of convex hull
hull = ConvexHull(points)
centroid = np.mean(points[hull.vertices, :], axis=0)
To plot the hull try,
import numpy as np
import pylab as pl
import scipy as sp
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as a3
# Plot polyhedra
ax = a3.Axes3D(pl.figure())
facetCol = sp.rand(3)
for simplex in hull.simplices:
vtx = [points[simplex[0],:], points[simplex[1],:], points[simplex[2],:]]
tri = a3.art3d.Poly3DCollection([vtx], linewidths = 2, alpha = 0.8)
tri.set_color(facetCol)
tri.set_edgecolor('k')
ax.add_collection3d(tri)
# Plot centroid
ax.scatter(centroid0], centroid[1], centroid[2])
plt.axis('equal')
plt.axis('off')
plt.show()
This solution is correct also if the hull has points that are irregularly spaced around the perimeter.
import numpy as np
from scipy.spatial import ConvexHull
def areaPoly(points):
area = 0
nPoints = len(points)
j = nPoints - 1
i = 0
for point in points:
p1 = points[i]
p2 = points[j]
area += (p1[0]*p2[1])
area -= (p1[1]*p2[0])
j = i
i += 1
area /= 2
return area
def centroidPoly(points):
nPoints = len(points)
x = 0
y = 0
j = nPoints - 1
i = 0
for point in points:
p1 = points[i]
p2 = points[j]
f = p1[0]*p2[1] - p2[0]*p1[1]
x += (p1[0] + p2[0])*f
y += (p1[1] + p2[1])*f
j = i
i += 1
area = areaPoly(hull_points)
f = area*6
return [x/f, y/f]
# 'points' is an array of tuples (x, y)
points = np.array(points)
hull = ConvexHull(points)
hull_points = points[hull.vertices]
centroid = centroidPoly(hull_points)
Related
I'm trying to create some artistic "plots" like the ones below:
The color of the regions do not really matter, what I'm trying to achieve is the variable "thickness" of the edges along the Voronoi regions (espescially, how they look like a bigger rounded blob where they meet in corners, and thinner at their middle point).
I've tried by "painting manually" each pixel based on the minimum distance to each centroid (each associated with a color):
n_centroids = 10
centroids = [(random.randint(0, h), random.randint(0, w)) for _ in range(n_centroids)]
colors = np.array([np.random.choice(range(256), size=3) for _ in range(n_centroids)]) / 255
for x, y in it.product(range(h), range(w)):
distances = np.sqrt([(x - c[0])**2 + (y - c[1])**2 for c in centroids])
centroid_i = np.argmin(distances)
img[x, y] = colors[centroid_i]
plt.imshow(img, cmap='gray')
Or by scipy.spatial.Voronoi, that also gives me the vertices points, although I still can't see how I can draw a line through them with the desired variable thickness.
from scipy.spatial import Voronoi, voronoi_plot_2d
# make up data points
points = [(random.randint(0, 10), random.randint(0, 10)) for _ in range(10)]
# add 4 distant dummy points
points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
voronoi_plot_2d(vor)
# colorize
for region in vor.regions:
if not -1 in region:
polygon = [vor.vertices[i] for i in region]
plt.fill(*zip(*polygon))
# fix the range of axes
plt.xlim([-2,12]), plt.ylim([-2,12])
plt.show()
Edit:
I've managed to get a somewhat satisfying result via erosion + corner smoothing (via median filter as suggested in the comments) on each individual region, then drawing it into a black background.
res = np.zeros((h,w,3))
for color in colors:
region = (img == color)[:,:,0]
region = region.astype(np.uint8) * 255
region = sg.medfilt2d(region, 15) # smooth corners
# make edges from eroding regions
region = cv2.erode(region, np.ones((3, 3), np.uint8))
region = region.astype(bool)
res[region] = color
plt.imshow(res)
But as you can see the "stretched" line along the boundaries/edges of the regions is not quite there. Any other suggestions?
This is what #JohanC suggestion looks like. IMO, it looks much better than my attempt with Bezier curves. However, there appears to be a small problem with the RoundedPolygon class, as there are sometimes small defects at the corners (e.g. between blue and purple in the image below).
Edit: I fixed the RoundedPolygon class.
#!/usr/bin/env python
# coding: utf-8
"""
https://stackoverflow.com/questions/72061965/create-voronoi-art-with-rounded-region-edges
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import patches, path
from scipy.spatial import Voronoi, voronoi_plot_2d
def shrink(polygon, pad):
center = np.mean(polygon, axis=0)
resized = np.zeros_like(polygon)
for ii, point in enumerate(polygon):
vector = point - center
unit_vector = vector / np.linalg.norm(vector)
resized[ii] = point - pad * unit_vector
return resized
class RoundedPolygon(patches.PathPatch):
# https://stackoverflow.com/a/66279687/2912349
def __init__(self, xy, pad, **kwargs):
p = path.Path(*self.__round(xy=xy, pad=pad))
super().__init__(path=p, **kwargs)
def __round(self, xy, pad):
n = len(xy)
for i in range(0, n):
x0, x1, x2 = np.atleast_1d(xy[i - 1], xy[i], xy[(i + 1) % n])
d01, d12 = x1 - x0, x2 - x1
l01, l12 = np.linalg.norm(d01), np.linalg.norm(d12)
u01, u12 = d01 / l01, d12 / l12
x00 = x0 + min(pad, 0.5 * l01) * u01
x01 = x1 - min(pad, 0.5 * l01) * u01
x10 = x1 + min(pad, 0.5 * l12) * u12
x11 = x2 - min(pad, 0.5 * l12) * u12
if i == 0:
verts = [x00, x01, x1, x10]
else:
verts += [x01, x1, x10]
codes = [path.Path.MOVETO] + n*[path.Path.LINETO, path.Path.CURVE3, path.Path.CURVE3]
verts[0] = verts[-1]
return np.atleast_1d(verts, codes)
if __name__ == '__main__':
# make up data points
n = 100
max_x = 20
max_y = 10
points = np.c_[np.random.uniform(0, max_x, size=n),
np.random.uniform(0, max_y, size=n)]
# add 4 distant dummy points
points = np.append(points, [[2 * max_x, 2 * max_y],
[ -max_x, 2 * max_y],
[2 * max_x, -max_y],
[ -max_x, -max_y]], axis = 0)
# compute Voronoi tesselation
vor = Voronoi(points)
fig, ax = plt.subplots(figsize=(max_x, max_y))
for region in vor.regions:
if region and (not -1 in region):
polygon = np.array([vor.vertices[i] for i in region])
resized = shrink(polygon, 0.15)
ax.add_patch(RoundedPolygon(resized, 0.2, color=plt.cm.Reds(0.5 + 0.5*np.random.rand())))
ax.axis([0, max_x, 0, max_y])
ax.axis('off')
ax.set_facecolor('black')
ax.add_artist(ax.patch)
ax.patch.set_zorder(-1)
plt.show()
Could something like bezier polygon "approximations" help me with this?
An attempt using Bezier curves:
#!/usr/bin/env python
# coding: utf-8
"""
https://stackoverflow.com/questions/72061965/create-voronoi-art-with-rounded-region-edges
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
from bezier.curve import Curve # https://bezier.readthedocs.io/en/stable/python/index.html
def get_bezier(polygon, n=10):
closed_polygon = np.concatenate([polygon, [polygon[0]]])
# Insert additional points lying along the edges of the polygon;
# this allows us to use higher order bezier curves.
augmented_polygon = np.array(augment(closed_polygon, n))
# The bezier package does not seem to support closed bezier curves;
# to simulate a closed bezier curve, we triplicate the polygon,
# and only evaluate the curve on the inner third.
triplicated_polygon = np.vstack([augmented_polygon, augmented_polygon, augmented_polygon])
bezier_curve = Curve(triplicated_polygon.T, degree=len(triplicated_polygon)-1)
return bezier_curve.evaluate_multi(np.linspace(1./3, 2./3, 100)).T
def augment(polygon, n=10):
new_points = []
for ii, (x0, y0) in enumerate(polygon[:-1]):
x1, y1 = polygon[ii+1]
x = np.linspace(x0, x1, n)
y = np.linspace(y0, y1, n)
new_points.extend(list(zip(x[:-1], y[:-1])))
new_points.append((x1, y1))
return new_points
if __name__ == '__main__':
# make up data points
points = np.random.randint(0, 11, size=(50, 2))
# add 4 distant dummy points
points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)
# compute Voronoi tesselation
vor = Voronoi(points)
# voronoi_plot_2d(vor)
fig, ax = plt.subplots()
for region in vor.regions:
if region and (not -1 in region):
polygon = np.array([vor.vertices[i] for i in region])
bezier_curve_points = get_bezier(polygon, 40)
ax.fill(*zip(*bezier_curve_points))
ax.axis([1, 9, 1, 9])
ax.axis('off')
plt.show()
I have several data points in 3 dimensional space (x, y, z) and have interpolated them using scipy.interpolate.Rbf. This gives me a spline nicely representing the surface of my 3D object. I would now like to determine several x and y pairs that have the same, arbitrary z value. I would like to do that in order to compute the cross section of my 3D object at any given value of z. Does someone know how to do that? Maybe there is also a better way to do that instead of using scipy.interpolate.Rbf.
Up to now I have evaluated the cross sections by making a contour plot using matplotlib.pyplot and extracting the displayed segments. 3D points and interpolated spline
segments extracted using a contour plot
I was able to solve the problem. I have calculated the area by triangulating the x-y data and cutting the triangles with the z-plane I wanted to calculate the cross-sectional area of (z=z0). Specifically, I have searched for those triangles whose z-values are both above and below z0. Then I have calculated the x and y values of the sides of these triangles where the sides are equal to z0. Then I use scipy.spatial.ConvexHull to sort the intersected points. Using the shoelace formula I can then determine the area.
I have attached the example code here:
import numpy as np
from scipy import spatial
import matplotlib.pyplot as plt
# Generation of random test data
n = 500
x = np.random.random(n)
y = np.random.random(n)
z = np.exp(-2*(x-.5)**2-4*(y-.5)**2)
z0 = .75
# Triangulation of the test data
triang= spatial.Delaunay(np.array([x, y]).T)
# Determine all triangles where not all points are above or below z0, i.e. the triangles that intersect z0
tri_inter=np.zeros_like(triang.simplices, dtype=np.int) # The triangles which intersect the plane at z0, filled below
i = 0
for tri in triang.simplices:
if ~np.all(z[tri] > z0) and ~np.all(z[tri] < z0):
tri_inter[i,:] = tri
i += 1
tri_inter = tri_inter[~np.all(tri_inter==0, axis=1)] # Remove all rows with only 0
# The number of interpolated values for x and y has twice the length of the triangles
# Because each triangle intersects the plane at z0 twice
x_inter = np.zeros(tri_inter.shape[0]*2)
y_inter = np.zeros(tri_inter.shape[0]*2)
for j, tri in enumerate(tri_inter):
# Determine which of the three points are above and which are below z0
points_above = []
points_below = []
for i in tri:
if z[i] > z0:
points_above.append(i)
else:
points_below.append(i)
# Calculate the intersections and put the values into x_inter and y_inter
t = (z0-z[points_below[0]])/(z[points_above[0]]-z[points_below[0]])
x_new = t * (x[points_above[0]]-x[points_below[0]]) + x[points_below[0]]
y_new = t * (y[points_above[0]]-y[points_below[0]]) + y[points_below[0]]
x_inter[j*2] = x_new
y_inter[j*2] = y_new
if len(points_above) > len(points_below):
t = (z0-z[points_below[0]])/(z[points_above[1]]-z[points_below[0]])
x_new = t * (x[points_above[1]]-x[points_below[0]]) + x[points_below[0]]
y_new = t * (y[points_above[1]]-y[points_below[0]]) + y[points_below[0]]
else:
t = (z0-z[points_below[1]])/(z[points_above[0]]-z[points_below[1]])
x_new = t * (x[points_above[0]]-x[points_below[1]]) + x[points_below[1]]
y_new = t * (y[points_above[0]]-y[points_below[1]]) + y[points_below[1]]
x_inter[j*2+1] = x_new
y_inter[j*2+1] = y_new
# sort points to calculate area
hull = spatial.ConvexHull(np.array([x_inter, y_inter]).T)
x_hull, y_hull = x_inter[hull.vertices], y_inter[hull.vertices]
# Calculation of are using the shoelace formula
area = 0.5*np.abs(np.dot(x_hull,np.roll(y_hull,1))-np.dot(y_hull,np.roll(x_hull,1)))
print('Area:', area)
plt.figure()
plt.plot(x_inter, y_inter, 'ro')
plt.plot(x_hull, y_hull, 'b--')
plt.triplot(x, y, triangles=tri_inter, color='k')
plt.show()
I am trying to make a shapely Polygon from a binary mask, but I always end up with an invalid Polygon. How can I make a valid polygon from an arbitrary binary mask? Below is an example using a circular mask. I suspect that it is because the points I get from the mask contour are out of order, which is apparent when I plot the points (see images below).
import matplotlib.pyplot as plt
import numpy as np
from shapely.geometry import Point, Polygon
from scipy.ndimage.morphology import binary_erosion
from skimage import draw
def get_circular_se(radius=2):
N = (radius * 2) + 1
se = np.zeros(shape=[N,N])
for i in range(N):
for j in range(N):
se[i,j] = (i - N / 2)**2 + (j - N / 2)**2 <= radius**2
se = np.array(se, dtype="uint8")
return se
return new_regions, np.asarray(new_vertices)
#generates a circular mask
side_len = 512
rad = 100
mask = np.zeros(shape=(side_len, side_len))
rr, cc = draw.circle(side_len/2, side_len/2, radius=rad, shape=mask.shape)
mask[rr, cc] = 1
#makes a polygon from the mask perimeter
se = get_circular_se(radius=1)
contour = mask - binary_erosion(mask, structure=se)
pixels_mask = np.array(np.where(contour==1)[::-1]).T
polygon = Polygon(pixels_mask)
print polygon.is_valid
>>False
#plots the results
fig, ax = plt.subplots()
ax.imshow(mask,cmap='Greys_r')
ax.plot(pixels_mask[:,0],pixels_mask[:,1],'b-',lw=0.5)
plt.tight_layout()
plt.show()
In fact I already found a solution that worked for me, but maybe someone has a better one. The problem was indeed that my points were out of order. Input coordinate order is crucial for making valid polygons. So, one just has to put the points in the right order first. Below is an example solution using a nearest neighbor approach with a KDTree, which I've already posted elsewhere for related problems.
from sklearn.neighbors import KDTree
def polygonize_by_nearest_neighbor(pp):
"""Takes a set of xy coordinates pp Numpy array(n,2) and reorders the array to make
a polygon using a nearest neighbor approach.
"""
# start with first index
pp_new = np.zeros_like(pp)
pp_new[0] = pp[0]
p_current_idx = 0
tree = KDTree(pp)
for i in range(len(pp) - 1):
nearest_dist, nearest_idx = tree.query([pp[p_current_idx]], k=4) # k1 = identity
nearest_idx = nearest_idx[0]
# finds next nearest point along the contour and adds it
for min_idx in nearest_idx[1:]: # skip the first point (will be zero for same pixel)
if not pp[min_idx].tolist() in pp_new.tolist(): # make sure it's not already in the list
pp_new[i + 1] = pp[min_idx]
p_current_idx = min_idx
break
pp_new[-1] = pp[0]
return pp_new
pixels_mask_ordered = polygonize_by_nearest_neighbor(pixels_mask)
polygon = Polygon(pixels_mask_ordered)
print polygon.is_valid
>>True
#plots the results
fig, ax = plt.subplots()
ax.imshow(mask,cmap='Greys_r')
ax.plot(pixels_mask_ordered[:,0],pixels_mask_ordered[:,1],'b-',lw=2)
plt.tight_layout()
plt.show()
I am using the ConvexHull class of scipy to construct a convex hull for a set of points. I am interested in a way to compute the minimum distance of a new point P from the convex hull.
With the help of the internet and a little tweaking by myself I came up with this formula to compute the distance of a point P or a set of points points to the convex hull facets:
np.max(np.dot(self.equations[:, :-1], points.T).T + self.equations[:, -1], axis=-1)
For a convex hull in 2D the equation above will result in the following plot:
As you can see the result is pretty good and correct for points within the convex hull (The distance here is negative and would need to be multiplied with -1). It is also correct for points that are closest to a facet but incorrect for points that are closest to a vertex of the convex hull. (I marked these regions with the dashed lines) For these points the correct minimum distance would be the minimum distance to the convex hull vertices.
How can I distinguish between points that are closest to a facet or closest to a vertex to correctly compute the minimum distance to the convex hull for a point P or a set of points points in an n-Dimensional space (At least 3D)?
if the points of the convex hull are given as a NX2 array and the point is given as p=[x,y]
import math
#from http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
def dist(x1,y1, x2,y2, x3,y3): # x3,y3 is the point
px = x2-x1
py = y2-y1
something = px*px + py*py
u = ((x3 - x1) * px + (y3 - y1) * py) / float(something)
if u > 1:
u = 1
elif u < 0:
u = 0
x = x1 + u * px
y = y1 + u * py
dx = x - x3
dy = y - y3
# Note: If the actual distance does not matter,
# if you only want to compare what this function
# returns to other results of this function, you
# can just return the squared distance instead
# (i.e. remove the sqrt) to gain a little performance
dist = math.sqrt(dx*dx + dy*dy)
return dist
dists=[]
for i in range(len(points)-1):
dists.append(dist(points[i][0],points[i][1],points[i+1][0],points[i+1][1],p[0],p[1]))
dist = min(dists)
I'm trying to colorize a Voronoi Diagram created using scipy.spatial.Voronoi. Here's my code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
# make up data points
points = np.random.rand(15,2)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
voronoi_plot_2d(vor)
# colorize
for region in vor.regions:
if not -1 in region:
polygon = [vor.vertices[i] for i in region]
plt.fill(*zip(*polygon))
plt.show()
The resulting image:
As you can see some of the Voronoi regions at the border of the image are not colored. That is because some indices to the Voronoi vertices for these regions are set to -1, i.e., for those vertices outside the Voronoi diagram. According to the docs:
regions: (list of list of ints, shape (nregions, *)) Indices of the Voronoi vertices forming each Voronoi region. -1 indicates vertex outside the Voronoi diagram.
In order to colorize these regions as well, I've tried to just remove these "outside" vertices from the polygon, but that didn't work. I think, I need to fill in some points at the border of the image region, but I can't seem to figure out how to achieve this reasonably.
Can anyone help?
The Voronoi data structure contains all the necessary information to construct positions for the "points at infinity". Qhull also reports them simply as -1 indices, so Scipy doesn't compute them for you.
https://gist.github.com/pv/8036995
http://nbviewer.ipython.org/gist/pv/8037100
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi
def voronoi_finite_polygons_2d(vor, radius=None):
"""
Reconstruct infinite voronoi regions in a 2D diagram to finite
regions.
Parameters
----------
vor : Voronoi
Input diagram
radius : float, optional
Distance to 'points at infinity'.
Returns
-------
regions : list of tuples
Indices of vertices in each revised Voronoi regions.
vertices : list of tuples
Coordinates for revised Voronoi vertices. Same as coordinates
of input vertices, with 'points at infinity' appended to the
end.
"""
if vor.points.shape[1] != 2:
raise ValueError("Requires 2D input")
new_regions = []
new_vertices = vor.vertices.tolist()
center = vor.points.mean(axis=0)
if radius is None:
radius = vor.points.ptp().max()
# Construct a map containing all ridges for a given point
all_ridges = {}
for (p1, p2), (v1, v2) in zip(vor.ridge_points, vor.ridge_vertices):
all_ridges.setdefault(p1, []).append((p2, v1, v2))
all_ridges.setdefault(p2, []).append((p1, v1, v2))
# Reconstruct infinite regions
for p1, region in enumerate(vor.point_region):
vertices = vor.regions[region]
if all(v >= 0 for v in vertices):
# finite region
new_regions.append(vertices)
continue
# reconstruct a non-finite region
ridges = all_ridges[p1]
new_region = [v for v in vertices if v >= 0]
for p2, v1, v2 in ridges:
if v2 < 0:
v1, v2 = v2, v1
if v1 >= 0:
# finite ridge: already in the region
continue
# Compute the missing endpoint of an infinite ridge
t = vor.points[p2] - vor.points[p1] # tangent
t /= np.linalg.norm(t)
n = np.array([-t[1], t[0]]) # normal
midpoint = vor.points[[p1, p2]].mean(axis=0)
direction = np.sign(np.dot(midpoint - center, n)) * n
far_point = vor.vertices[v2] + direction * radius
new_region.append(len(new_vertices))
new_vertices.append(far_point.tolist())
# sort region counterclockwise
vs = np.asarray([new_vertices[v] for v in new_region])
c = vs.mean(axis=0)
angles = np.arctan2(vs[:,1] - c[1], vs[:,0] - c[0])
new_region = np.array(new_region)[np.argsort(angles)]
# finish
new_regions.append(new_region.tolist())
return new_regions, np.asarray(new_vertices)
# make up data points
np.random.seed(1234)
points = np.random.rand(15, 2)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
regions, vertices = voronoi_finite_polygons_2d(vor)
print "--"
print regions
print "--"
print vertices
# colorize
for region in regions:
polygon = vertices[region]
plt.fill(*zip(*polygon), alpha=0.4)
plt.plot(points[:,0], points[:,1], 'ko')
plt.xlim(vor.min_bound[0] - 0.1, vor.max_bound[0] + 0.1)
plt.ylim(vor.min_bound[1] - 0.1, vor.max_bound[1] + 0.1)
plt.show()
I have a much simpler solution to this problem, that is to add 4 distant dummy points to your point list before calling the Voronoi algorithm.
Based on your codes, I added two lines.
import numpy as np
import matplotlib.pyplot as plt
from scipy.spatial import Voronoi, voronoi_plot_2d
# make up data points
points = np.random.rand(15,2)
# add 4 distant dummy points
points = np.append(points, [[999,999], [-999,999], [999,-999], [-999,-999]], axis = 0)
# compute Voronoi tesselation
vor = Voronoi(points)
# plot
voronoi_plot_2d(vor)
# colorize
for region in vor.regions:
if not -1 in region:
polygon = [vor.vertices[i] for i in region]
plt.fill(*zip(*polygon))
# fix the range of axes
plt.xlim([0,1]), plt.ylim([0,1])
plt.show()
Then the resulting figure just looks like the following.
I don't think there is enough information from the data available in the vor structure to figure this out without doing at least some of the voronoi computation again. Since that's the case, here are the relevant portions of the original voronoi_plot_2d function that you should be able to use to extract the points that intersect with the vor.max_bound or vor.min_bound which are the bottom left and top right corners of the diagram in order figure out the other coordinates for your polygons.
for simplex in vor.ridge_vertices:
simplex = np.asarray(simplex)
if np.all(simplex >= 0):
ax.plot(vor.vertices[simplex,0], vor.vertices[simplex,1], 'k-')
ptp_bound = vor.points.ptp(axis=0)
center = vor.points.mean(axis=0)
for pointidx, simplex in zip(vor.ridge_points, vor.ridge_vertices):
simplex = np.asarray(simplex)
if np.any(simplex < 0):
i = simplex[simplex >= 0][0] # finite end Voronoi vertex
t = vor.points[pointidx[1]] - vor.points[pointidx[0]] # tangent
t /= np.linalg.norm(t)
n = np.array([-t[1], t[0]]) # normal
midpoint = vor.points[pointidx].mean(axis=0)
direction = np.sign(np.dot(midpoint - center, n)) * n
far_point = vor.vertices[i] + direction * ptp_bound.max()
ax.plot([vor.vertices[i,0], far_point[0]],
[vor.vertices[i,1], far_point[1]], 'k--')