geodesicdomes 1.3.3__py3-none-any.whl

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.
@@ -0,0 +1,560 @@
1
+ Metadata-Version: 2.4
2
+ Name: geodesicdomes
3
+ Version: 1.3.3
4
+ Summary: Geodesic domes (icosahedral, tetrahedral and dodecahedral spherical lattices) with fast neighbour search, flat grids, map projections and an interactive rotatable map viewer -- the lattice layer for spherical self-organising maps.
5
+ Author-email: Masahiro Takatsuka <masa@takatsuka.org>
6
+ Maintainer-email: Masahiro Takatsuka <masa@takatsuka.org>
7
+ License-Expression: AGPL-3.0-or-later
8
+ Project-URL: Homepage, https://github.com/takatsuka/GeodesicDome
9
+ Project-URL: Source, https://github.com/takatsuka/GeodesicDome
10
+ Project-URL: Issues, https://github.com/takatsuka/GeodesicDome/issues
11
+ Project-URL: Documentation, https://github.com/takatsuka/GeodesicDome/blob/main/docs/index.md
12
+ Project-URL: Changelog, https://github.com/takatsuka/GeodesicDome/blob/main/CHANGELOG.md
13
+ Project-URL: Paper, https://doi.org/10.1016/j.neunet.2006.05.021
14
+ Keywords: geodesic dome,geodesic sphere,icosahedron,tetrahedron,dodecahedron,spherical grid,icosphere,self-organizing map,spherical SOM,neighbour search,map projection,equal earth,mesh,gpu,cuda
15
+ Classifier: Development Status :: 4 - Beta
16
+ Classifier: Intended Audience :: Science/Research
17
+ Classifier: Intended Audience :: Developers
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
27
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
28
+ Classifier: Topic :: Scientific/Engineering :: Visualization
29
+ Classifier: Typing :: Typed
30
+ Requires-Python: >=3.10
31
+ Description-Content-Type: text/markdown
32
+ License-File: LICENSE
33
+ License-File: NOTICE
34
+ Requires-Dist: numpy>=1.23
35
+ Provides-Extra: interactive
36
+ Requires-Dist: matplotlib>=3.6; extra == "interactive"
37
+ Provides-Extra: gpu
38
+ Requires-Dist: torch>=2.1; extra == "gpu"
39
+ Provides-Extra: examples
40
+ Requires-Dist: matplotlib>=3.6; extra == "examples"
41
+ Requires-Dist: pillow; extra == "examples"
42
+ Provides-Extra: notebooks
43
+ Requires-Dist: matplotlib>=3.6; extra == "notebooks"
44
+ Requires-Dist: pillow; extra == "notebooks"
45
+ Requires-Dist: jupyterlab; extra == "notebooks"
46
+ Requires-Dist: ipywidgets>=8; extra == "notebooks"
47
+ Requires-Dist: ipympl; extra == "notebooks"
48
+ Provides-Extra: legacy
49
+ Requires-Dist: plotly; extra == "legacy"
50
+ Requires-Dist: dash; extra == "legacy"
51
+ Provides-Extra: dev
52
+ Requires-Dist: matplotlib>=3.6; extra == "dev"
53
+ Requires-Dist: pytest>=7; extra == "dev"
54
+ Requires-Dist: pytest-cov; extra == "dev"
55
+ Requires-Dist: ruff<0.17,>=0.16; extra == "dev"
56
+ Requires-Dist: build; extra == "dev"
57
+ Requires-Dist: twine; extra == "dev"
58
+ Provides-Extra: all
59
+ Requires-Dist: matplotlib>=3.6; extra == "all"
60
+ Requires-Dist: pillow; extra == "all"
61
+ Requires-Dist: jupyterlab; extra == "all"
62
+ Requires-Dist: ipywidgets>=8; extra == "all"
63
+ Requires-Dist: ipympl; extra == "all"
64
+ Requires-Dist: plotly; extra == "all"
65
+ Requires-Dist: dash; extra == "all"
66
+ Requires-Dist: pytest>=7; extra == "all"
67
+ Requires-Dist: pytest-cov; extra == "all"
68
+ Requires-Dist: ruff<0.17,>=0.16; extra == "all"
69
+ Requires-Dist: build; extra == "all"
70
+ Requires-Dist: twine; extra == "all"
71
+ Dynamic: license-file
72
+
73
+ # GeodesicDome — geodesic domes and grids for Python
74
+
75
+ [![PyPI](https://img.shields.io/pypi/v/geodesicdomes.svg)](https://pypi.org/project/geodesicdomes/)
76
+ [![Python](https://img.shields.io/pypi/pyversions/geodesicdomes.svg)](https://pypi.org/project/geodesicdomes/)
77
+ [![tests](https://github.com/takatsuka/GeodesicDome/actions/workflows/tests.yml/badge.svg)](https://github.com/takatsuka/GeodesicDome/actions/workflows/tests.yml)
78
+ [![License: AGPL v3](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue.svg)](https://github.com/takatsuka/GeodesicDome/blob/main/LICENSE)
79
+
80
+ `mt.geodesicdome` builds **geodesic spheres** (domes of any frequency on an icosahedron, tetrahedron or dodecahedron) and
81
+ **flat hexagonal/rectilinear grids**, with fast neighbour search on both. It was written as the
82
+ lattice layer for Self-Organising Maps (SOMs), but it works just as well for meshing,
83
+ sampling points evenly on a sphere, or cellular automata on a globe.
84
+
85
+ | What | Name |
86
+ |---|---|
87
+ | `pip install …` | `geodesicdomes` |
88
+ | Python import | `mt.geodesicdome` (`mt` is a namespace package shared by future `mt.*` libraries) |
89
+ | Repository | [`takatsuka/GeodesicDome`](https://github.com/takatsuka/GeodesicDome) |
90
+
91
+ ![geodesic domes of frequency 1, 2, 4 and 8](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/02_domes_3d.png)
92
+
93
+ **What you get**
94
+
95
+ | Feature | Where |
96
+ |---|---|
97
+ | Geodesic dome of any frequency *f*: 10f²+2 points, 20f² triangles, all on the unit sphere | `mt.geodesicdome.grid.geodesicdome.GeodesicDome` |
98
+ | Other base solids: tetrahedron (4HSOM array, 2f²+2 points) and dodecahedron (30f²+2 points) | `GeodesicDome(f, base='tetrahedron' / 'dodecahedron')` |
99
+ | Neighbour search and *k*-ring neighbourhoods that continue across the seams of the net | `get_neighbours`, `get_neighbours_in_distance` |
100
+ | NumPy export of points and triangles (outward-facing winding) | `get_all_xyz`, `get_all_triangles` |
101
+ | A data payload per vertex, e.g. SOM weight vectors | `vertex.set_data(...)` / `vertex.data` |
102
+ | Four map projections to flatten the sphere | `mt.geodesicdome.projection` |
103
+ | **Interactive map: drag with the mouse to rotate the sphere inside the projection** | `mt.geodesicdome.interactive.ProjectionViewer` |
104
+ | Flat hexagonal or rectilinear grids, with borders or as a torus | `mt.geodesicdome.grid.plane.Plane` |
105
+
106
+ ---
107
+
108
+ ## 1. Installation
109
+
110
+ Requires Python ≥ 3.10 and NumPy. The interactive viewer and the examples also need matplotlib.
111
+
112
+ ```bash
113
+ pip install geodesicdomes # the library (numpy only)
114
+ pip install "geodesicdomes[interactive]" # + matplotlib, for mt.geodesicdome.interactive
115
+ pip install "geodesicdomes[gpu]" # + torch, to use an NVIDIA (CUDA) or Apple Silicon (MPS) GPU
116
+ ```
117
+
118
+ Check that it works:
119
+
120
+ ```bash
121
+ python -c "from mt.geodesicdome.grid.geodesicdome import GeodesicDome; print(GeodesicDome(4))"
122
+ # GeodesicDome(frequency=4)
123
+ ```
124
+
125
+ The examples, the tests and `setup_env.sh` are in the
126
+ [GitHub repository](https://github.com/takatsuka/GeodesicDome), not in the pip package.
127
+
128
+ ### Working on this repository: `setup_env.sh` (recommended)
129
+
130
+ One command creates a virtual environment with everything the project uses: numpy, matplotlib, pillow,
131
+ plotly and dash for the older viewers, and pytest. It also installs this package in editable mode, so any
132
+ `.py` file in any folder of the project can `import mt.geodesicdome`.
133
+
134
+ ```bash
135
+ cd GeodesicDome
136
+ ./setup_env.sh # once
137
+ python examples/01_quickstart.py # works straight away, in the same terminal
138
+ python examples/09_interactive_projection.py
139
+ ```
140
+
141
+ * **It leaves you in a ready shell.** When it finishes, it opens a shell in which the environment is active
142
+ (the prompt starts with `(GeodesicDome)`), so `python` is the project's Python. Type `exit` to return to
143
+ your previous shell. `--no-shell` skips this.
144
+ * **New terminals are ready too.** It adds a small block to `~/.zshrc` (and `~/.bashrc` if you have one) that
145
+ activates the environment whenever you are inside the project and deactivates it when you leave.
146
+ `--no-shell-hook` skips this; `./setup_env.sh --remove-shell-hook` removes it.
147
+ * **The environment lives in `~/.venvs/GeodesicDome`, outside the repository.** This repository sits in Google Drive,
148
+ which syncs every file of a virtual environment and can make them online-only, so imports hang or fail.
149
+ * **Anywhere else**, run `source ~/.venvs/GeodesicDome/bin/activate`, or call the environment's Python
150
+ directly: `~/.venvs/GeodesicDome/bin/python path/to/script.py`.
151
+ * **It is safe to re-run at any time.** A healthy environment is reused, and missing packages are added. A broken
152
+ one, for example after `brew upgrade python`, is rebuilt automatically.
153
+ * **It picks the newest Python ≥ 3.10 it can find.** Use `--python /opt/homebrew/bin/python3.13` to choose one.
154
+ * **It installs the GPU packages this machine can use.** PyTorch with MPS on Apple Silicon; on Linux/Windows with an
155
+ NVIDIA GPU, PyTorch built for the CUDA version the driver supports (read from `nvidia-smi`) plus the matching CuPy;
156
+ PyTorch for ROCm on an AMD GPU. Without a GPU nothing extra is installed and computations use every CPU core. The
157
+ last step checks that the GPU really works. `--gpu torch|cupy|none` narrows or skips this, and
158
+ `--torch-index cu126` (or a URL) overrides the PyTorch wheel index.
159
+ * **Other options:** `--recreate` builds from scratch, `--check` only verifies, `--minimal` installs numpy only
160
+ (and no GPU packages), and `--no-legacy` skips plotly and dash. See `./setup_env.sh --help`.
161
+ * **IDE:** in PyCharm or VS Code, choose `~/.venvs/GeodesicDome/bin/python` as the project interpreter.
162
+
163
+ ### Other ways to install
164
+
165
+ ```bash
166
+ pip install "git+https://github.com/takatsuka/GeodesicDome.git" # latest development version
167
+ pip install -e ".[interactive]" # from a local checkout, editable
168
+ ```
169
+
170
+ Optional extras: `interactive` (matplotlib), `examples` (+ pillow), `legacy` (plotly and dash, for the old
171
+ viewer scripts), `dev` (pytest, ruff, build, twine), and `all`.
172
+
173
+ ---
174
+
175
+ ## 2. Quick start
176
+
177
+ ```python
178
+ import numpy as np
179
+ from mt.geodesicdome.grid.geodesicdome import GeodesicDome
180
+
181
+ dome = GeodesicDome(frequency=8) # icosahedron with each edge cut into 8
182
+
183
+ xyz = dome.get_all_xyz() # (N, 3) unit vectors
184
+ tri = dome.get_all_triangles().reshape(-1, 3) # (20 f², 3) indices into xyz
185
+ print(xyz.shape, tri.shape) # (729, 3) (1280, 3)
186
+
187
+ # neighbours of a vertex, addressed by its (x, y) position on the index grid
188
+ v = dome.get_vertex_at(12, 14)
189
+ dome.unmark_vertices() # always reset before a search
190
+ ring1 = dome.get_neighbours(v, False) # 6 neighbours (5 at the 12 corners)
191
+
192
+ dome.unmark_vertices()
193
+ rings = dome.get_neighbours_in_distance(v, 3) # [[ring 1], [ring 2], [ring 3]]
194
+ print([len(r) for r in rings]) # [6, 12, 18]
195
+
196
+ v.set_data(np.zeros(3)) # attach anything to a vertex
197
+ ```
198
+
199
+ You can also refine step by step. `split` multiplies the frequency, so
200
+ `GeodesicDome(2).split(3)` gives frequency 6.
201
+
202
+ ---
203
+
204
+ ## 3. Core concepts
205
+
206
+ ### 3.1 Frequency
207
+
208
+ | frequency *f* | unique points 10f²+2 | triangles 20f² | stored vertices | mean edge (unit sphere) |
209
+ |---:|---:|---:|---:|---:|
210
+ | 1 | 12 | 20 | 22 | 1.052 |
211
+ | 2 | 42 | 80 | 63 | 0.582 |
212
+ | 4 | 162 | 320 | 205 | 0.298 |
213
+ | 8 | 642 | 1 280 | 729 | 0.150 |
214
+ | 12 | 1 442 | 2 880 | 1 573 | 0.100 |
215
+
216
+ Every point has 6 neighbours, except the 12 original icosahedron corners, which have 5.
217
+ Rings near a corner are therefore slightly smaller (for example 5, 10, 15 around a corner itself).
218
+
219
+ ### 3.2 The unfolded net and seam vertices
220
+
221
+ Internally, the icosahedron is unfolded onto an integer grid. Each vertex has a grid position
222
+ `(vertex.x, vertex.y)`, and its neighbours on the sphere are always the six grid offsets
223
+ `(±1, 0)`, `(0, ±1)`, `(+1, +1)` and `(−1, −1)`. That is what makes neighbour look-ups fast.
224
+
225
+ This indexed geodesic data structure was introduced for the spherical self-organising map in:
226
+
227
+ > Y. Wu and M. Takatsuka, "Spherical self-organizing map using efficient indexed geodesic data structure,"
228
+ > *Neural Networks*, vol. 19, no. 6–7, pp. 900–910, 2006. [doi:10.1016/j.neunet.2006.05.021](https://doi.org/10.1016/j.neunet.2006.05.021)
229
+
230
+ ```bibtex
231
+ @article{wu2006spherical,
232
+ author = {Wu, Yingxin and Takatsuka, Masahiro},
233
+ title = {Spherical self-organizing map using efficient indexed geodesic data structure},
234
+ journal = {Neural Networks},
235
+ volume = {19},
236
+ number = {6--7},
237
+ pages = {900--910},
238
+ year = {2006},
239
+ month = {07},
240
+ doi = {10.1016/j.neunet.2006.05.021}
241
+ }
242
+ ```
243
+
244
+ ![the unfolded net](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/03_unfolded_net.png)
245
+
246
+ To fold the net back into a sphere, points on its border are **stored more than once**, and each copy
247
+ lists the others in `vertex.same_vertices`. As a result:
248
+
249
+ * `get_all_vertices()` / `get_all_xyz()` return **N ≥ 10f²+2** entries (for example 729 instead of 642 at f = 8).
250
+ * Neighbour searches already handle the copies, so rings continue across seams and never
251
+ contain the same point twice.
252
+ * To get a clean mesh with each point exactly once, de-duplicate by position.
253
+ `examples/dome_utils.py` has a ready-made `unique_mesh(dome)` that returns
254
+ `(points, faces, index_map)`.
255
+
256
+ ### 3.3 The `visited` flag
257
+
258
+ The neighbour searches mark vertices with `vertex.visited = True` so they are not returned twice.
259
+ **Call `dome.unmark_vertices()` before every new query**. Otherwise vertices from the previous
260
+ query will be missing from the result.
261
+
262
+ ### 3.4 Coordinates
263
+
264
+ * `vertex.coord` is the unit vector (x, y, z). The dome is built with its poles on the **±y** axis.
265
+ * `vertex.latlon_coord` is `(colatitude from +y, longitude)` in radians.
266
+ * `vertex.id` is the row of the vertex in `get_all_xyz()`. It is assigned when faces are built
267
+ (`get_faces()` / `get_all_triangles()`), so call one of those first.
268
+ * The map projections use **+z** as their north pole (`latitude = arcsin(z)`).
269
+
270
+ ### 3.5 Base polyhedra: tetrahedron, icosahedron, dodecahedron
271
+
272
+ The icosahedron is the default. `base=` builds the dome on another solid, with the same index grid, the same
273
+ six neighbour offsets and the same API:
274
+
275
+ ```python
276
+ GeodesicDome(8) # icosahedron: 642 points (10f²+2)
277
+ GeodesicDome(8, base='tetrahedron') # tetrahedron: 130 points ( 2f²+2)
278
+ GeodesicDome(8, base='dodecahedron') # dodecahedron: 1922 points (30f²+2)
279
+ ```
280
+
281
+ `base` takes `'icosahedron'` / `'icosa'`, `'tetrahedron'` / `'tetra'`, `'dodecahedron'` / `'dodeca'` (any case).
282
+ `GeodesicDome(...)` then returns an `IcosahedronDome`, `TetrahedronDome` or `DodecahedronDome`, all subclasses
283
+ of `GeodesicDome`; `dome.base` says which.
284
+
285
+ | base | unique points | triangles | stored vertices (grid) | corners | net |
286
+ |---|---:|---:|---|---|---|
287
+ | `'tetrahedron'` | 2f²+2 | 4f² | (f+1)(2f+1): an `(f+1) × (2f+1)` array, every cell used | 4, with 3 neighbours | the 4HSOM orthogonal array of de Sousa & Oliveira (2012) |
288
+ | `'icosahedron'` | 10f²+2 | 20f² | 729 at f = 8 | 12, with 5 neighbours | the 5-strip net of Wu & Takatsuka (2006) |
289
+ | `'dodecahedron'` | 30f²+2 | 60f² | 2 081 at f = 8 | 12 pentagon centres, with 5 neighbours | the pentakis dodecahedron on the strip net, cut along pentagon edges |
290
+
291
+ * **Tetrahedron.** Following de Sousa & Oliveira's 4HSOM, the four faces form a parallelogram stored as
292
+ `f + 1` rows (`y = 0..f`) by `2f + 1` columns (`x = 0..2f`). Column `0` and column `2f` are the same points,
293
+ and the bottom and top rows fold about their middle points: `(i, 0) = (2f − i, 0)` and `(i, f) = (2f − i, f)`.
294
+ The four corners are A at `(0, 0)`/`(2f, 0)`, B at `(f, 0)`, C at `(0, f)`/`(2f, f)` and D, the north
295
+ pole, at `(f, f)`. Its cells vary much more in area than the icosahedron's.
296
+ * **Dodecahedron.** Each pentagon is cut into 5 triangles meeting at its centre (the pentakis dodecahedron) and
297
+ each triangle into an *f*-frequency grid on the flat pentagon. Frequency 1 has 32 points: the 20 dodecahedron
298
+ corners (6 neighbours) and the 12 pentagon centres (5 neighbours), which point the same way as the
299
+ icosahedron's corners.
300
+
301
+ For these two solids the net has notches, so each vertex also stores a 6-bit `neighbour_mask` of the grid
302
+ offsets that are real edges; the search is still pure index arithmetic. The nets themselves are in
303
+ `mt.geodesicdome.grid.polyhedra` (`base_net(name)`).
304
+
305
+ ![domes and nets on the three base polyhedra](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/10_base_polyhedra.png)
306
+
307
+ **Explore them interactively.** `examples/11_interactive_base_polyhedra.py` (and the notebook of the same name)
308
+ shows one dome three ways at once: on the sphere, on its index grid and in a map projection centred on a
309
+ vertex. The vertex's neighbour rings are highlighted in all three, so you can see a neighbourhood
310
+ that is compact on the sphere split across the seams of the net. Click the net or the map to move it.
311
+ It can also colour every cell by its spherical area; compare the tetrahedron with the other two.
312
+ `ProjectionViewer(dome, colors='base')` colours any dome by the faces of its own base solid.
313
+
314
+ ![the base-polyhedron explorer](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/11_explorer.png)
315
+
316
+ ---
317
+
318
+ ## 4. API reference
319
+
320
+ ### `GeodesicDome(frequency=1, base='icosahedron')` — `mt.geodesicdome.grid.geodesicdome`
321
+
322
+ | Member | Description |
323
+ |---|---|
324
+ | `base` | `'icosahedron'`, `'tetrahedron'` or `'dodecahedron'` |
325
+ | `frequency`, `x_max`, `y_max` | current frequency and the extent of the index grid |
326
+ | `split(n)` | subdivide every edge into `n` more segments (frequency ×= n) |
327
+ | `get_all_vertices()` | list of `GeodesicVertex`, including seam copies |
328
+ | `get_vertex_at(x, y)` | vertex at a grid position, or `None` |
329
+ | `get_faces()` | flat list of vertices, 3 per triangle (also assigns `vertex.id`) |
330
+ | `get_all_triangles()` | flat `ndarray` of vertex ids; `.reshape(-1, 3)` gives the triangles |
331
+ | `get_all_xyz()` | `(N, 3)` array of coordinates, row = `vertex.id` |
332
+ | `get_number_of_vertices_per_face()` | `3` |
333
+ | `get_neighbours(v, False)` | immediate neighbours of `v` |
334
+ | `get_neighbours_in_distance(v, d)` | list of `d` rings: `[[ring 1], [ring 2], ...]` |
335
+ | `unmark_vertices()` | reset every `visited` flag (do this before each search) |
336
+
337
+ ### `GeodesicVertex`
338
+
339
+ `x`, `y` (grid position) · `coord` (xyz) · `latlon_coord` · `id` · `same_vertices` (seam copies or `None`) ·
340
+ `data` / `set_data(obj)` · `visited` · `projected_coord` (set by a projection).
341
+
342
+ ### Projections — `mt.geodesicdome.projection`
343
+
344
+ | Class | Module |
345
+ |---|---|
346
+ | `KavrayskiyVII` | `kavrayskiy` |
347
+ | `WagnerVI`, `WagnerIII` | `wagner` |
348
+ | `EqualEarth` | `equal_earth` |
349
+
350
+ ```python
351
+ from mt.geodesicdome.projection.equal_earth import EqualEarth
352
+ tri2d = EqualEarth().build(dome) # (M, 3) triangle ids; sets v.projected_coord
353
+ xy = [v.projected_coord for v in dome.get_all_vertices()]
354
+ EqualEarth().xyz_to_2d(np.array([0, 0, 1])) # project a single point
355
+ ```
356
+
357
+ `build` leaves out the few triangles that would wrap across the ±180° meridian, so M < 20f².
358
+ For a complete map, with no missing triangles and any orientation of the sphere, use
359
+ `mt.geodesicdome.interactive` (below).
360
+
361
+ Vectorised helpers on every projection: `latlong_to_2d(lat, lon)` (arrays, radians),
362
+ `xyz_to_2d_many(xyz)` for an `(N, 3)` array, and `outline()`, the map boundary as a polygon.
363
+
364
+ ![map projections](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/05_map_projections.png)
365
+
366
+ ### Interactive projection — `mt.geodesicdome.interactive`
367
+
368
+ `ProjectionViewer` shows the dome in a map projection that you can rotate. **Click and drag on the map, and the
369
+ sphere turns underneath it**: dragging left or right spins it about the map's polar axis, and dragging up or down
370
+ tilts it. The projection is recomputed on every mouse move.
371
+
372
+ ```python
373
+ from mt.geodesicdome.grid.geodesicdome import GeodesicDome
374
+ from mt.geodesicdome.interactive import ProjectionViewer
375
+
376
+ viewer = ProjectionViewer(GeodesicDome(8), 'Equal Earth')
377
+ viewer.show()
378
+ ```
379
+
380
+ ![rotating the sphere inside an Equal Earth projection](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/09_rotation.gif)
381
+
382
+ | Mouse / key | Action |
383
+ |---|---|
384
+ | drag (left button) | rotate the sphere (a fast preview is drawn while dragging; full quality returns on release) |
385
+ | arrow keys | rotate by 5° (with shift: 1°) |
386
+ | `,` / `.` | roll the view about the map centre |
387
+ | `r` / *Reset view* button | back to the starting view |
388
+ | `p` / radio buttons | switch projection: Equal Earth, Kavrayskiy VII, Wagner VI, Wagner III |
389
+ | `g` / `e` | show or hide the graticule / the triangle edges |
390
+
391
+ The black graticule shows the latitude and longitude lines *of the original sphere*, so you can see how it has
392
+ turned. The status line gives the original point now at the map centre.
393
+
394
+ **Options and methods**
395
+
396
+ ```python
397
+ viewer = ProjectionViewer(
398
+ dome, 'Wagner VI',
399
+ colors='position', # 'position' (default), 'base' (faces of the dome's base solid), 'icosahedron',
400
+ # 'data' (vertex.data), a colour name,
401
+ # or an array per stored vertex / per unique point / per face
402
+ cmap='viridis', # used when colors holds scalars (a colour bar is added)
403
+ edges=None, # triangle edges; default: on for frequency <= 12
404
+ graticule=True,
405
+ view=(-33.9, 151.2), # (lat, lon) in degrees of the original point to put at the centre
406
+ )
407
+ viewer.rotate(d_lon=30, d_lat=-10, roll=0) # degrees, same axes as dragging
408
+ viewer.set_view(lat, lon) # centre the map on an original point
409
+ viewer.set_rotation(R) # any 3x3 rotation matrix (original -> view)
410
+ viewer.set_projection('Kavrayskiy VII')
411
+ viewer.set_colors(values, vmin=0, vmax=1)
412
+ viewer.on_rotate(lambda v: print(v.centre)) # called after every change of the view
413
+ viewer.save('map.png')
414
+ ```
415
+
416
+ Colouring by `vertex.data` means a trained spherical SOM (see `examples/06_spherical_som.py`) can be explored
417
+ directly: store each node's weight vector as an RGB colour with `set_data` and pass `colors='data'`.
418
+
419
+ **Running it.** It needs a matplotlib GUI backend. `python examples/09_interactive_projection.py` from a desktop
420
+ terminal or IDE opens a window. In Jupyter, run `%matplotlib widget` first (`pip install ipympl`).
421
+
422
+ **How it works.** `SphereMap` (numpy only) rotates the de-duplicated mesh and projects it. Unlike
423
+ `Projection.build`, it keeps every triangle:
424
+ * a triangle that straddles the ±180° meridian is drawn twice, shifted by ±360° of longitude, and clipped to the
425
+ map outline;
426
+ * the triangle that contains a pole becomes a polygon running along the pole line;
427
+ * exact alignments, such as a pole lying precisely on an edge in the un-rotated view, are broken by a fixed
428
+ 10⁻⁶ rad rotation that is invisible on screen.
429
+
430
+ The tests (`tests/test_interactive.py`) render random and deliberately degenerate orientations in all four
431
+ projections and check that no pixel inside the map is left uncovered. `SphereMap.polygons(R)` is also usable on
432
+ its own, for example to draw a rotated map in your own figure.
433
+
434
+ ### Compute backends: GPU or all CPU cores — `mt.geodesicdome.backend`, `mt.geodesicdome.compute`
435
+
436
+ Heavy array work runs on a GPU when one is available and otherwise on every CPU core. Nothing extra is required:
437
+ with numpy alone you get the multi-threaded CPU backend; install torch (`pip install "geodesicdomes[gpu]"`) for an
438
+ NVIDIA GPU (CUDA) or the Apple Silicon GPU (MPS), or CuPy for CUDA.
439
+
440
+ ```python
441
+ from mt.geodesicdome import backend, compute
442
+ from mt.geodesicdome.grid.geodesicdome import GeodesicDome
443
+
444
+ print(backend.describe()) # what is available, and the default
445
+ b = backend.get_backend() # auto: CUDA -> CuPy -> MPS -> NumPy on all cores
446
+ mesh = compute.DomeArrays.from_dome(GeodesicDome(32))
447
+ rings = compute.ring_distance(mesh) # (10242, 10242) distances in neighbour spacings
448
+ idx = compute.nearest_vertex(mesh.points, queries) # nearest dome vertex of each query direction
449
+ ```
450
+
451
+ | Choose | How |
452
+ |---|---|
453
+ | a device | `get_backend('cuda')`, `'cuda:1'`, `'mps'`, `'cupy'`, `'cpu'`/`'numpy'`, `'torch:cpu'`; or pass `backend=` to any function |
454
+ | the default | `backend.set_default_backend('cpu')` or `MTGEODESIC_BACKEND=cpu` |
455
+ | CPU threads | `MTGEODESIC_NUM_THREADS=8` (default: every core) |
456
+ | GPU precision | `MTGEODESIC_DTYPE=float64` (default float32 on a GPU; the Apple GPU is float32 only; the CPU always uses float64) |
457
+ | block size | `MTGEODESIC_MEMORY=512MB` (default: from the free device memory) |
458
+
459
+ A `Backend` offers the few array operations the geodesic libraries need with the same meaning on numpy, cupy and
460
+ torch arrays, plus `block_rows()`/`map_blocks()` to cut work into blocks that fit in memory and run them on all CPU
461
+ cores. mtGeodesicSOM trains on it.
462
+
463
+ ### `Plane(x, y, lattice, topology)` — `mt.geodesicdome.grid.plane`
464
+
465
+ * `lattice`: `Lattice.Hexagonal` (6 neighbours, odd rows shifted by ½) or `Lattice.Rectilinear` (4 neighbours).
466
+ * `topology`: `Topology.Plane` (hard borders) or `Topology.Donut` (wraps around like a torus). Use an even
467
+ height with a hexagonal donut. Faces are not generated across the wrap.
468
+ * It has the same `Manifold` interface as the dome: `get_vertex_at`, `get_neighbours`,
469
+ `get_neighbours_in_distance`, `get_faces` (3 or 4 vertices per face), `get_all_xyz`, `get_all_triangles`, `unmark_vertices`.
470
+
471
+ Because both classes share the `Manifold` interface, code such as a SOM can switch between a flat
472
+ map, a torus and a sphere without changes.
473
+
474
+ ![plane grids](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/08_plane_grids.png)
475
+
476
+ ---
477
+
478
+ ## 5. Examples
479
+
480
+ Run them from the repository root, e.g. `python examples/04_neighbours.py`. They work straight
481
+ from a checkout without installing, and their images and files go to `examples/output/`.
482
+
483
+ | Script | Shows |
484
+ |---|---|
485
+ | `01_quickstart.py` | building domes, NumPy export, vertex attributes, a size table for each frequency |
486
+ | `02_plot_dome_3d.py` | shaded 3D renders of frequency 1, 2, 4 and 8 |
487
+ | `03_unfolded_net.py` | the index grid, seam copies and the 12 five-neighbour corners |
488
+ | `04_neighbours.py` | *k*-ring neighbourhoods around an interior, a seam and a corner vertex |
489
+ | `05_map_projections.py` | the four projections, coloured by parent icosahedron face |
490
+ | `06_spherical_som.py` | **showcase:** a Self-Organising Map trained on colours, living on the sphere |
491
+ | `07_export_mesh.py [freq] [radius]` | writing a de-duplicated mesh to `.obj`, `.off` and `.npz` |
492
+ | `08_plane_grids.py` | hexagonal and rectilinear `Plane`, with borders and as a torus |
493
+ | `09_interactive_projection.py` | **interactive:** drag to rotate the sphere in a map projection (`--freq`, `--projection`, `--colors`, `--view`, `--gif`) |
494
+ | `10_base_polyhedra.py` | domes and index-grid nets on the tetrahedron, icosahedron and dodecahedron |
495
+ | `11_interactive_base_polyhedra.py` | **interactive:** explore any dome type — 3-D sphere, index-grid net and a map centred on a vertex, with its neighbour rings across the seams (click to move; `--base`, `--freq`, `--rings`, `--colouring`, `--save`) |
496
+ | `dome_utils.py` | helpers used above: `unique_mesh`, `neighbour_rings`, `output_path` |
497
+
498
+ ![neighbour rings](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/04_neighbours.png)
499
+
500
+ ![spherical SOM](https://raw.githubusercontent.com/takatsuka/GeodesicDome/main/examples/output/06_spherical_som.png)
501
+
502
+ **Notebooks.** Every example is also a Jupyter notebook in
503
+ [`examples/notebooks/`](https://github.com/takatsuka/GeodesicDome/tree/main/examples/notebooks), explained step
504
+ by step and ending with sliders and buttons to explore it. With `ipympl` installed the figures are live: 3D plots
505
+ rotate with the mouse and the map viewer can be dragged.
506
+
507
+ ```bash
508
+ pip install -e ".[notebooks]" # setup_env.sh already includes it
509
+ jupyter lab examples/notebooks
510
+ ```
511
+
512
+ The older interactive viewers in `examples/legacy/` use plotly/dash (`pip install -e ".[legacy]"`).
513
+
514
+ ---
515
+
516
+ ## 6. Changelog
517
+
518
+ See [CHANGELOG.md](https://github.com/takatsuka/GeodesicDome/blob/main/CHANGELOG.md).
519
+
520
+ ---
521
+
522
+ ## 7. Citing
523
+
524
+ If you use GeodesicDome in research, please cite the paper that introduced the data structure (BibTeX in
525
+ [section 3.2](#32-the-unfolded-net-and-seam-vertices)):
526
+
527
+ > Y. Wu and M. Takatsuka, "Spherical self-organizing map using efficient indexed geodesic data structure,"
528
+ > *Neural Networks*, vol. 19, no. 6–7, pp. 900–910, 2006. [doi:10.1016/j.neunet.2006.05.021](https://doi.org/10.1016/j.neunet.2006.05.021)
529
+
530
+ The tetrahedral dome (`base='tetrahedron'`) follows the layout of
531
+
532
+ > R. M. de Sousa and R. C. L. Oliveira, "Optimization of geodesic self-organizing map using tessellated
533
+ > tetrahedron as spherical lattice," *Proc. IJCNN 2012*, Brisbane.
534
+
535
+ The repository's `CITATION.cff` also lets GitHub's "Cite this repository" button produce a reference to
536
+ the software itself.
537
+
538
+ ---
539
+
540
+ ## 8. Licence
541
+
542
+ Copyright © 2022–2026 Masahiro Takatsuka.
543
+
544
+ GeodesicDome is free software under the **GNU Affero General Public License v3.0 or later**
545
+ ([LICENSE](https://github.com/takatsuka/GeodesicDome/blob/main/LICENSE)), with an additional attribution term
546
+ ([NOTICE](https://github.com/takatsuka/GeodesicDome/blob/main/NOTICE)). In short:
547
+
548
+ * **You may** use, study, modify and share it, including commercially.
549
+ * **If you distribute it,** or a modified version, or software that includes it, **or let people use a
550
+ modified version over a network,** you must release the complete source code of that work under the
551
+ same licence.
552
+ * **You must keep the attribution** to the author and to the 2006 paper, both in the source code and in
553
+ the legal notices your software displays.
554
+ * There is no warranty.
555
+
556
+ **Commercial licence.** To use GeodesicDome in proprietary software without these obligations, contact
557
+ <masa@takatsuka.org> about a commercial licence.
558
+
559
+ **Contributing.** Contributions are welcome under the terms in
560
+ [CONTRIBUTING.md](https://github.com/takatsuka/GeodesicDome/blob/main/CONTRIBUTING.md).
@@ -0,0 +1,27 @@
1
+ geodesicdomes-1.3.3.dist-info/licenses/LICENSE,sha256=V8j_M8nAz8PvAOZQocyRDX7keai8UJ9skgmnwqETmdY,34520
2
+ geodesicdomes-1.3.3.dist-info/licenses/NOTICE,sha256=wDBPo6pMW4zRgzY7U_GUykVNEJ2ttbdbnBpZC2M3K9A,2737
3
+ mt/geodesicdome/__init__.py,sha256=cUHItV_P0nv2nDkxKhRU8UnunLpu2sj2Z1p8blT7Cn0,1325
4
+ mt/geodesicdome/backend.py,sha256=taW0GM-ibXG6vHLgqQ8qXjsfjMAZup_2wRzSzTKsCoQ,22013
5
+ mt/geodesicdome/compute.py,sha256=5PYHAj3a-t_IAawUnMzUTAn8Ibi8MwNL178owdCIyLo,5235
6
+ mt/geodesicdome/manifold.py,sha256=k3clNDN7NjtU4MQVsTOxl4-ujhQVgIQJQ76iSzhcErI,2619
7
+ mt/geodesicdome/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ mt/geodesicdome/util.py,sha256=TLpD3VJB888qcWCatRaa7jsa3MikAjIc-31ajec5bCo,2697
9
+ mt/geodesicdome/vertex.py,sha256=bOLp0TUw0p6zRUAjxwYgqbUSw066SQMl27rrDJV5pE4,890
10
+ mt/geodesicdome/grid/__init__.py,sha256=V7QLheVnjocCg06FskDoDStxz4DTENDQO9E8aX6zKME,232
11
+ mt/geodesicdome/grid/geodesicdome.py,sha256=piSk0I7iDelSO6nX6YYNgB3sn4dF6Lu5Xyk2nxwJ6QU,31439
12
+ mt/geodesicdome/grid/plane.py,sha256=Fn6LuVXLoNMmgm4LDsznOZlMx2bST53e7iZFG4OntLk,9845
13
+ mt/geodesicdome/grid/polyhedra.py,sha256=y9EJSuMn0nNdXNN-RS2QW0vf8jnJh40T8upJijqATpY,13140
14
+ mt/geodesicdome/interactive/__init__.py,sha256=0s2vTxJDRoXAl0H-PeOIqmwGcZHWGNxGQ1kJ4_cMwYU,1405
15
+ mt/geodesicdome/interactive/mesh.py,sha256=EPlyHVb5_svnEQpWwr8nPKXWes5Ous6mRcL7qeW4IcM,1275
16
+ mt/geodesicdome/interactive/rotation.py,sha256=kPiwo0AbKUl28ys0tVmVXA97Z2fpB1XKb6qL-6wHxNs,2241
17
+ mt/geodesicdome/interactive/sphere_map.py,sha256=5Re3ZfdEgagoNCUNhEbz9NbQSX_X56W0TXx5JhKavX0,13474
18
+ mt/geodesicdome/interactive/viewer.py,sha256=QsozmOD1wYbzMmoWWqLxj7JDkJu0lAHcKoa_j7KNuCg,20865
19
+ mt/geodesicdome/projection/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ mt/geodesicdome/projection/equal_earth.py,sha256=0Zb-GhlnkyqVpo_Cno4oso-R2LM1t2HWr_SQJ3FFEJE,1002
21
+ mt/geodesicdome/projection/kavrayskiy.py,sha256=XVmDTtKxbOOUyBcbT8D9JqDNQIGo_3JtBryA8ofRFwQ,605
22
+ mt/geodesicdome/projection/projection.py,sha256=eNkc9J02oD3OIvy_ViTLBpP3WWTyx36vDcVD4eUi--4,3171
23
+ mt/geodesicdome/projection/wagner.py,sha256=AaQ7QXCfM54pmCTEXycPqQPwfxujzfR0DXh2MWAkSIk,1085
24
+ geodesicdomes-1.3.3.dist-info/METADATA,sha256=zgB8HCU7P0vgZHikNRmSXaAZfD7IulhXvzJDZV-K4BE,29839
25
+ geodesicdomes-1.3.3.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
26
+ geodesicdomes-1.3.3.dist-info/top_level.txt,sha256=WcqGFu9cV7iMZg09iam8eNxUvGpLSKKF2Iubf6SJVOo,3
27
+ geodesicdomes-1.3.3.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+