Source code for monalisa.geometry._voronoi
"""Port of src/geom123/bmVoronoi.m (MATLAB voronoin/convhulln -> scipy Qhull)."""
import numba
import numpy as np
from scipy.spatial import ConvexHull, QhullError, Voronoi
from monalisa._mat import disp, double
from monalisa.geometry._volume_element1 import volume_element1
@numba.njit(cache=True)
def _polygon_areas(vertices, flat, ptr, out):
"""Area of the convex polygons ``vertices[flat[ptr[k]:ptr[k+1]]]`` (shoelace
after sorting the vertices by angle around their centroid). Degenerate
polygons get -1, like a failing ``convhulln``."""
for k in range(ptr.size - 1):
idx = flat[ptr[k] : ptr[k + 1]]
n = idx.size
if n < 3:
out[k] = -1.0
continue
cx = 0.0
cy = 0.0
for i in range(n):
cx += vertices[idx[i], 0]
cy += vertices[idx[i], 1]
cx /= n
cy /= n
ang = np.empty(n)
for i in range(n):
ang[i] = np.arctan2(vertices[idx[i], 1] - cy, vertices[idx[i], 0] - cx)
order = np.argsort(ang)
area = 0.0
for i in range(n):
a = idx[order[i]]
b = idx[order[(i + 1) % n]]
area += vertices[a, 0] * vertices[b, 1] - vertices[b, 0] * vertices[a, 1]
area = 0.5 * abs(area)
out[k] = area if area > 0.0 else -1.0
[docs]
def voronoi(x, dispFlag=True):
"""Voronoi cell volume of each point of ``x`` (``[imDim, nPt]``).
Unbounded cells get -1. For ``imDim == 1`` the 1D lengths of
``volume_element1`` are returned (as a column, like MATLAB).
"""
x = double(x)
imDim, nPt = x.shape
if imDim == 1:
return volume_element1(x)
if imDim not in (2, 3):
raise ValueError("voronoi: only 1, 2 or 3 dimensions are supported")
if dispFlag:
disp("Running 'voronoin' and 'convhulln'... can take some time ...")
vor = Voronoi(x.T, qhull_options="Qbb")
regions = [vor.regions[r] if r >= 0 else [-1] for r in vor.point_region]
bounded = np.array([len(r) > 0 and -1 not in r for r in regions])
out = -np.ones((1, nPt))
if imDim == 2:
sel = np.flatnonzero(bounded)
lens = np.array([len(regions[j]) for j in sel], dtype=np.int64)
ptr = np.concatenate([[0], np.cumsum(lens)]).astype(np.int64)
flat = np.fromiter(
(v for j in sel for v in regions[j]), dtype=np.int64, count=int(ptr[-1])
)
areas = np.empty(sel.size)
_polygon_areas(vor.vertices, flat, ptr, areas)
out[0, sel] = areas
else:
for j in np.flatnonzero(bounded):
try:
out[0, j] = ConvexHull(vor.vertices[regions[j]]).volume
except QhullError:
out[0, j] = -1.0
if dispFlag:
disp("... 'voronoin' and 'convhulln' done !")
return out