voronoip 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- voronoip-0.1.0/LICENSE +21 -0
- voronoip-0.1.0/MANIFEST.in +4 -0
- voronoip-0.1.0/PKG-INFO +439 -0
- voronoip-0.1.0/README.md +406 -0
- voronoip-0.1.0/pyproject.toml +51 -0
- voronoip-0.1.0/setup.cfg +4 -0
- voronoip-0.1.0/tests/conftest.py +5 -0
- voronoip-0.1.0/tests/test_voronoip.py +261 -0
- voronoip-0.1.0/voronoip/__init__.py +45 -0
- voronoip-0.1.0/voronoip/diagram.py +371 -0
- voronoip-0.1.0/voronoip/generators.py +182 -0
- voronoip-0.1.0/voronoip/metrics.py +162 -0
- voronoip-0.1.0/voronoip/region.py +105 -0
- voronoip-0.1.0/voronoip.egg-info/PKG-INFO +439 -0
- voronoip-0.1.0/voronoip.egg-info/SOURCES.txt +16 -0
- voronoip-0.1.0/voronoip.egg-info/dependency_links.txt +1 -0
- voronoip-0.1.0/voronoip.egg-info/requires.txt +12 -0
- voronoip-0.1.0/voronoip.egg-info/top_level.txt +1 -0
voronoip-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Emerson Charles do Nascimento Marreiros
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
voronoip-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: voronoip
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Weighted Voronoi Diagrams — multiplicative, additive and power (Laguerre) modes
|
|
5
|
+
Author-email: Emerson Marreiros <ec2763@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/emerson-marreiros/voronoip
|
|
8
|
+
Project-URL: Repository, https://github.com/emerson-marreiros/voronoip
|
|
9
|
+
Project-URL: Issues, https://github.com/emerson-marreiros/voronoip/issues
|
|
10
|
+
Keywords: voronoi,weighted voronoi,computational geometry,diagram,laguerre
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: numpy>=1.23
|
|
23
|
+
Requires-Dist: matplotlib>=3.5
|
|
24
|
+
Provides-Extra: full
|
|
25
|
+
Requires-Dist: scipy>=1.9; extra == "full"
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Requires-Dist: pytest-env; extra == "dev"
|
|
29
|
+
Requires-Dist: scipy>=1.9; extra == "dev"
|
|
30
|
+
Requires-Dist: build; extra == "dev"
|
|
31
|
+
Requires-Dist: twine; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# voronoip — Weighted Voronoi Diagrams for Python
|
|
35
|
+
|
|
36
|
+
A lightweight, dependency-light Python library for constructing and
|
|
37
|
+
visualising **weighted Voronoi diagrams**, including:
|
|
38
|
+
|
|
39
|
+
| Mode | Distance function | Effect of larger weight |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `"multiplicative"` | `dist(p,g) / w(g)` | larger region |
|
|
42
|
+
| `"additive"` | `dist(p,g) − w(g)` | larger region |
|
|
43
|
+
| `"power"` | `dist(p,g)² − w(g)²` | larger region (power diagram) |
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install voronoip
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or, for local development:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install numpy scipy matplotlib
|
|
57
|
+
# clone / copy voronoip/ into your project
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Dependencies: **numpy**, **scipy** (optional, for `boundary_pixels`),
|
|
61
|
+
**matplotlib** (for visualisation).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Quick start
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import numpy as np
|
|
69
|
+
from voronoip import WeightedVoronoi
|
|
70
|
+
|
|
71
|
+
pts = np.array([[0.2, 0.3],
|
|
72
|
+
[0.7, 0.6],
|
|
73
|
+
[0.5, 0.1],
|
|
74
|
+
[0.1, 0.9]])
|
|
75
|
+
w = np.array([1.0, 2.5, 0.5, 1.8])
|
|
76
|
+
|
|
77
|
+
wv = WeightedVoronoi(pts, w, mode="multiplicative", resolution=512)
|
|
78
|
+
wv.compute()
|
|
79
|
+
wv.plot() # shows an interactive matplotlib figure
|
|
80
|
+
wv.to_png("out.png")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## ⚠️ Two mistakes almost everyone makes at first
|
|
86
|
+
|
|
87
|
+
Before jumping into the examples below, read this. These two mistakes
|
|
88
|
+
account for the vast majority of `TypeError` / `AttributeError` reports
|
|
89
|
+
from new users — both examples further down show exactly how to avoid
|
|
90
|
+
them.
|
|
91
|
+
|
|
92
|
+
### 1. `compute()` returns the object itself, not a list of regions
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# ❌ WRONG — celulas becomes the WeightedVoronoi object, not a list
|
|
96
|
+
celulas = diagrama.compute()
|
|
97
|
+
for celula in celulas: # TypeError: 'WeightedVoronoi' object is not iterable
|
|
98
|
+
...
|
|
99
|
+
|
|
100
|
+
# ✅ CORRECT — compute() returns self (useful for chaining);
|
|
101
|
+
# the actual list of regions lives in .regions
|
|
102
|
+
diagrama.compute()
|
|
103
|
+
for regiao in diagrama.regions:
|
|
104
|
+
...
|
|
105
|
+
|
|
106
|
+
# Also valid, thanks to chaining:
|
|
107
|
+
regioes = diagrama.compute().regions
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 2. `voronoip` is **raster-based**, not vector-based — there is no `.vertices`
|
|
111
|
+
|
|
112
|
+
If you've used `scipy.spatial.Voronoi` before, you're used to each
|
|
113
|
+
region being a polygon with `.vertices`. `voronoip` works differently:
|
|
114
|
+
it rasterises the diagram onto a pixel grid (`label_grid`), and each
|
|
115
|
+
`VoronoiRegion` is described by a boolean **pixel mask**, not a list of
|
|
116
|
+
polygon corners.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# ❌ WRONG — VoronoiRegion has no .vertices attribute
|
|
120
|
+
poligono = np.array(regiao.vertices)
|
|
121
|
+
plt.fill(poligono[:, 0], poligono[:, 1])
|
|
122
|
+
|
|
123
|
+
# ✅ CORRECT — let the built-in plot() draw the diagram for you
|
|
124
|
+
fig, ax = diagrama.plot()
|
|
125
|
+
|
|
126
|
+
# ✅ Or, if you need region data programmatically:
|
|
127
|
+
regiao.pixel_mask # (H, W) bool — True where pixels belong to this region
|
|
128
|
+
regiao.area # int — pixel count
|
|
129
|
+
regiao.centroid # (x, y) — mean position of the region
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If you specifically need polygon-style output, this is on the roadmap
|
|
133
|
+
(see *Limitations* below) — it is not currently supported.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## API reference
|
|
138
|
+
|
|
139
|
+
### `WeightedVoronoi(points, weights, **kwargs)`
|
|
140
|
+
|
|
141
|
+
| Parameter | Default | Description |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `points` | — | `(N, 2)` generator coordinates |
|
|
144
|
+
| `weights` | — | `(N,)` generator weights |
|
|
145
|
+
| `mode` | `"multiplicative"` | distance metric |
|
|
146
|
+
| `resolution` | `512` | pixels along longer axis |
|
|
147
|
+
| `domain` | auto (bounding box + 5 %) | `((xmin,xmax),(ymin,ymax))` |
|
|
148
|
+
| `palette` | `"tab20"` | matplotlib colormap name |
|
|
149
|
+
| `show_generators` | `True` | draw seed points |
|
|
150
|
+
| `show_weights` | `False` | annotate weights |
|
|
151
|
+
| `show_boundaries` | `True` | draw cell edges |
|
|
152
|
+
|
|
153
|
+
> **Tip:** always pass `points` and `weights` as `float` arrays
|
|
154
|
+
> (`np.array([...], dtype=float)` or simply use `1.0` instead of `1`).
|
|
155
|
+
> Integer arrays work in most cases, but mixing them with weight-based
|
|
156
|
+
> division (`mode="multiplicative"`) can produce unexpected integer
|
|
157
|
+
> truncation in edge cases — floats avoid the ambiguity entirely.
|
|
158
|
+
|
|
159
|
+
#### Methods
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
wv.compute() # rasterise the diagram (required first) — returns self
|
|
163
|
+
|
|
164
|
+
wv.plot(**kwargs) # returns (fig, ax)
|
|
165
|
+
wv.plot_distance_field() # heat-map of min weighted distance
|
|
166
|
+
wv.plot_comparison() # side-by-side of all 3 modes
|
|
167
|
+
|
|
168
|
+
wv.owner(x, y) # generator index owning (x, y)
|
|
169
|
+
wv.region_of(x, y) # VoronoiRegion containing (x, y)
|
|
170
|
+
wv.nearest_generators(x, y, k=3) # k nearest generators by weighted dist
|
|
171
|
+
|
|
172
|
+
wv.to_png("out.png", dpi=150)
|
|
173
|
+
wv.to_svg("out.svg")
|
|
174
|
+
wv.to_csv("out.csv") # index, x, y, weight, area, centroid
|
|
175
|
+
wv.to_label_array() # (H, W) int ndarray — copy
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
> **Important:** every query method (`owner`, `region_of`,
|
|
179
|
+
> `nearest_generators`) and every plotting/export method requires
|
|
180
|
+
> `.compute()` to have been called first. Calling them beforehand
|
|
181
|
+
> raises `RuntimeError: Call .compute() before accessing diagram data
|
|
182
|
+
> or plotting.` — this is intentional, not a bug.
|
|
183
|
+
|
|
184
|
+
#### Key attributes (after `compute()`)
|
|
185
|
+
|
|
186
|
+
| Attribute | Type | Description |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `label_grid` | `(H, W) int32` | generator index per pixel |
|
|
189
|
+
| `dist_grid` | `(H, W) float64` | minimum weighted distance per pixel |
|
|
190
|
+
| `regions` | `list[VoronoiRegion]` | one object per generator — **this is what you iterate over** |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `VoronoiRegion`
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
r = wv.regions[0]
|
|
198
|
+
|
|
199
|
+
r.index # int — generator index
|
|
200
|
+
r.generator # (2,) float — (x, y)
|
|
201
|
+
r.weight # float
|
|
202
|
+
r.pixel_mask # (H, W) bool
|
|
203
|
+
r.color # (R, G, B) tuple
|
|
204
|
+
|
|
205
|
+
r.area # int — number of pixels
|
|
206
|
+
r.centroid # (2,) float — mean (x, y) of mask pixels
|
|
207
|
+
r.boundary_pixels # (K, 2) row/col indices of boundary pixels
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
> Note that `r.pixel_mask` is the only true source of geometry for a
|
|
211
|
+
> region. `area`, `centroid` and `boundary_pixels` are all *derived*
|
|
212
|
+
> from it — there is no separate vector representation.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
### `voronoip.generators`
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
from voronoip.generators import (
|
|
220
|
+
random_generators, # uniform random
|
|
221
|
+
grid_generators, # regular grid with optional jitter
|
|
222
|
+
poisson_disk_generators, # Bridson blue-noise sampling
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
pts, w = random_generators(n=20, weight_range=(0.5, 2.0), seed=42)
|
|
226
|
+
pts, w = grid_generators(nx=6, ny=6, jitter=0.04, seed=0)
|
|
227
|
+
pts, w = poisson_disk_generators(min_dist=0.1, seed=7)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
All functions return `(points, weights)` tuples ready for
|
|
231
|
+
`WeightedVoronoi`.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### `voronoip.metrics`
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from voronoip.metrics import (
|
|
239
|
+
multiplicative_weighted_distance, # scalar
|
|
240
|
+
additive_weighted_distance,
|
|
241
|
+
power_distance,
|
|
242
|
+
batch_multiplicative, # vectorised over generators
|
|
243
|
+
batch_additive,
|
|
244
|
+
batch_power,
|
|
245
|
+
)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Full worked examples
|
|
251
|
+
|
|
252
|
+
The two examples below are deliberately written end-to-end, including
|
|
253
|
+
the result of `.regions` and `.owner()`, so you can copy them as a
|
|
254
|
+
starting template for your own scripts without hitting the two
|
|
255
|
+
mistakes described above.
|
|
256
|
+
|
|
257
|
+
### Example 1 — Basic diagram with 4 weighted points
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
import numpy as np
|
|
261
|
+
import matplotlib.pyplot as plt
|
|
262
|
+
from voronoip import WeightedVoronoi
|
|
263
|
+
|
|
264
|
+
# Points (x, y) — always use floats
|
|
265
|
+
pontos = np.array([
|
|
266
|
+
[1.0, 1.0],
|
|
267
|
+
[5.0, 2.0],
|
|
268
|
+
[3.0, 6.0],
|
|
269
|
+
[7.0, 7.0]
|
|
270
|
+
])
|
|
271
|
+
|
|
272
|
+
# Weight associated with each point
|
|
273
|
+
pesos = np.array([1.0, 2.0, 0.5, 3.0])
|
|
274
|
+
|
|
275
|
+
# Create the weighted Voronoi object
|
|
276
|
+
vor = WeightedVoronoi(
|
|
277
|
+
points=pontos,
|
|
278
|
+
weights=pesos,
|
|
279
|
+
mode="multiplicative",
|
|
280
|
+
resolution=512,
|
|
281
|
+
show_weights=True # annotate weights directly on the plot
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# compute() returns self — do NOT reassign it to "regioes"
|
|
285
|
+
vor.compute()
|
|
286
|
+
|
|
287
|
+
# ── Visualization (built-in, no manual polygon drawing needed) ─────
|
|
288
|
+
fig, ax = vor.plot()
|
|
289
|
+
ax.set_title("Diagrama de Voronoi Ponderado - voronoip")
|
|
290
|
+
plt.show()
|
|
291
|
+
|
|
292
|
+
# ── Region data — iterate over .regions, not over vor itself ───────
|
|
293
|
+
print("Regiões:")
|
|
294
|
+
for regiao in vor.regions:
|
|
295
|
+
print(regiao)
|
|
296
|
+
print(f" Área: {regiao.area} px")
|
|
297
|
+
print(f" Centróide: {regiao.centroid}")
|
|
298
|
+
print(f" Peso: {regiao.weight}")
|
|
299
|
+
|
|
300
|
+
# ── Query which region owns an arbitrary point ──────────────────────
|
|
301
|
+
x, y = 4.0, 4.0
|
|
302
|
+
idx = vor.owner(x, y)
|
|
303
|
+
print(f"\nDono do ponto ({x}, {y}): gerador {idx} → {vor.regions[idx]}")
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Example 2 — Antenna signal coverage (real-world use case)
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
import numpy as np
|
|
310
|
+
import matplotlib.pyplot as plt
|
|
311
|
+
from voronoip import WeightedVoronoi
|
|
312
|
+
|
|
313
|
+
# Antenna locations
|
|
314
|
+
antenas = np.array([
|
|
315
|
+
[2.0, 8.0],
|
|
316
|
+
[8.0, 9.0],
|
|
317
|
+
[5.0, 5.0],
|
|
318
|
+
[1.0, 2.0],
|
|
319
|
+
[9.0, 3.0]
|
|
320
|
+
])
|
|
321
|
+
|
|
322
|
+
# Signal strength (weight) — higher power covers a larger area
|
|
323
|
+
potencia = np.array([5.0, 3.0, 2.0, 1.0, 4.0])
|
|
324
|
+
|
|
325
|
+
diagrama = WeightedVoronoi(
|
|
326
|
+
points=antenas,
|
|
327
|
+
weights=potencia,
|
|
328
|
+
mode="multiplicative",
|
|
329
|
+
resolution=512,
|
|
330
|
+
show_generators=False, # we'll draw the antennas manually below
|
|
331
|
+
show_weights=False,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
diagrama.compute() # ✅ no reassignment — returns self
|
|
335
|
+
|
|
336
|
+
# ── Visualization ────────────────────────────────────────────────────
|
|
337
|
+
fig, ax = diagrama.plot()
|
|
338
|
+
|
|
339
|
+
# Custom antenna markers (triangles instead of the default dots)
|
|
340
|
+
ax.scatter(antenas[:, 0], antenas[:, 1],
|
|
341
|
+
s=180, c="black", marker="^", zorder=6)
|
|
342
|
+
|
|
343
|
+
# Power labels
|
|
344
|
+
for i, p in enumerate(potencia):
|
|
345
|
+
x, y = antenas[i]
|
|
346
|
+
ax.text(x + 0.15, y, f"P={p}", fontsize=9, zorder=7,
|
|
347
|
+
bbox=dict(boxstyle="round,pad=0.2", fc="white", alpha=0.6, lw=0))
|
|
348
|
+
|
|
349
|
+
ax.set_title("Cobertura de Antenas usando Voronoi Ponderado")
|
|
350
|
+
ax.set_xlabel("X")
|
|
351
|
+
ax.set_ylabel("Y")
|
|
352
|
+
ax.grid(True, alpha=0.3)
|
|
353
|
+
plt.show()
|
|
354
|
+
|
|
355
|
+
# ── Coverage data per antenna — iterate over .regions ───────────────
|
|
356
|
+
print("Cobertura por antena:")
|
|
357
|
+
for regiao in diagrama.regions:
|
|
358
|
+
i = regiao.index
|
|
359
|
+
cx, cy = regiao.centroid
|
|
360
|
+
print(f" Antena {i+1} (P={potencia[i]}) "
|
|
361
|
+
f"→ área: {regiao.area} px "
|
|
362
|
+
f"centróide: ({cx:.2f}, {cy:.2f})")
|
|
363
|
+
|
|
364
|
+
# ── Signal intensity heat-map ───────────────────────────────────────
|
|
365
|
+
fig2, ax2 = diagrama.plot_distance_field(cmap="plasma")
|
|
366
|
+
ax2.set_title("Intensidade de Sinal (distância ponderada)")
|
|
367
|
+
plt.show()
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## More examples
|
|
373
|
+
|
|
374
|
+
### Comparison of all three modes
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
wv = WeightedVoronoi(pts, w, mode="multiplicative", resolution=400)
|
|
378
|
+
wv.compute()
|
|
379
|
+
fig, axes = wv.plot_comparison(figsize=(18, 6))
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Distance field heat-map
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
wv.plot_distance_field(cmap="plasma")
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### Querying which region owns a point
|
|
389
|
+
|
|
390
|
+
```python
|
|
391
|
+
idx = wv.owner(0.5, 0.5)
|
|
392
|
+
region = wv.region_of(0.5, 0.5)
|
|
393
|
+
print(region)
|
|
394
|
+
# VoronoiRegion(index=1, generator=(0.700, 0.600), weight=2.500, area=14832 px)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Exporting
|
|
398
|
+
|
|
399
|
+
```python
|
|
400
|
+
wv.to_png("voronoi.png", dpi=200)
|
|
401
|
+
wv.to_svg("voronoi.svg")
|
|
402
|
+
wv.to_csv("voronoi.csv")
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
## Limitations
|
|
408
|
+
|
|
409
|
+
- **No polygon/vector output yet.** `voronoip` is raster-first by
|
|
410
|
+
design — regions are pixel masks, not lists of polygon vertices.
|
|
411
|
+
If your use case strictly requires exact vector polygons (e.g. for
|
|
412
|
+
GIS or CAD pipelines), this library is not yet a drop-in
|
|
413
|
+
replacement for `scipy.spatial.Voronoi`.
|
|
414
|
+
- Diagram accuracy (boundary smoothness, area precision) scales with
|
|
415
|
+
`resolution` — low resolutions will show visibly blocky cell edges.
|
|
416
|
+
- `boundary_pixels` requires the optional `scipy` dependency
|
|
417
|
+
(`pip install voronoip[full]`).
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Project structure
|
|
422
|
+
|
|
423
|
+
```
|
|
424
|
+
voronoip/
|
|
425
|
+
├── __init__.py # public API
|
|
426
|
+
├── diagram.py # WeightedVoronoi class
|
|
427
|
+
├── region.py # VoronoiRegion dataclass
|
|
428
|
+
├── generators.py # random / grid / Poisson-disk seed generators
|
|
429
|
+
└── metrics.py # distance functions + registry
|
|
430
|
+
tests/
|
|
431
|
+
└── test_voronoip.py # full test suite (pytest)
|
|
432
|
+
README.md
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
---
|
|
436
|
+
|
|
437
|
+
## License
|
|
438
|
+
|
|
439
|
+
MIT
|