gridpoints 1.1.0__tar.gz → 1.1.2__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.
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.4
2
+ Name: gridpoints
3
+ Version: 1.1.2
4
+ Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
+ Author: Armand de Cacqueray
6
+ License: MIT
7
+ Project-URL: Homepage, https://neighborhood-grid.github.io/gridpoints/
8
+ Project-URL: Repository, https://github.com/Neighborhood-Grid/gridpoints
9
+ Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Provides-Extra: demo
14
+ Requires-Dist: squarenet; extra == "demo"
15
+ Dynamic: license-file
16
+
17
+ # Gridpoints
18
+
19
+ [![PyPI](https://img.shields.io/pypi/v/gridpoints?logo=pypi&logoColor=white)](https://pypi.org/project/gridpoints/)
20
+ [![GitHub](https://img.shields.io/badge/GitHub-gridpoints-181717?logo=github)](https://github.com/Neighborhood-Grid/gridpoints)
21
+
22
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/plots/table(1).gif">
23
+
24
+ **Reorder an unstructured point cloud into a regular grid, so that neighbor-based operations become simple grid processing.**
25
+
26
+ ```python
27
+ import gridpoints as grid
28
+ points = your_dataset_of_points(1000000, 3) # standard (N, D) array
29
+ order = grid.argsort(points, gridshape=(100, 100, 100)) # set gridshape: (I, J, K, ...) = (100, 100, 100)
30
+ Pgrid = points[order].reshape(100, 100, 100, 3) # now an (I, J, K, ..., D) version of your dataset
31
+ # Ready for convolution, neighbor look-ups or geometrical analysis.
32
+ # Note that grid.argsort also supports a batch mode (B, N, D) -> (B, I, J, K, ..., D)
33
+ ```
34
+
35
+ Gridpoints supports NumPy, PyTorch and CuPy arrays.
36
+ The `argsort()` reordering is a one-to-one assignment of your points to the cells of a grid with the given `gridshape`. This is related to Monge optimal transport, but `argsort()` trades optimality for scalability, by combining greedy axial operations which exploit the specific structure of grids. One million 2D/3D points are sorted in about 0.5 s on a GPU (PyTorch) or 10 s on a CPU (NumPy).
37
+
38
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/plots/ballexemple.png">
39
+
40
+ ---
41
+
42
+ ## TL;DR
43
+
44
+ - **Problem:** point clouds are stored as a flat list `[P1, ..., PN]`. Finding the neighbors of a point usually needs a tree or a graph (KD-tree, radius graph...), which is hard to use with standard tensor-based machine learning, especially on GPU.
45
+ - **Idea:** assign every point to a cell of a D-dimensional grid, one point per cell, such that the grid is sorted along each of its axes. Then the neighbors of a point are *approximately* the points in nearby cells, found by plain indexing.
46
+ - **Payoff:** local operations (convolution, smoothing, clustering, interpolation...) that are usually hard to implement on irregular point clouds can be applied on the grid: `points -> grid`. To recover the result on the points, one just applies the inverse assignment: `grid -> points`.
47
+ - **Catch:** a plain grid gives *approximate* neighborhoods (about 99% of true nearest neighbors within a small radius). Exact neighborhoods are recoverable with tiling (see [Neighborhood radius](#neighborhood-radius)). It works best in 2D/3D, and stays reasonable up to about 6D.
48
+
49
+ ---
50
+
51
+ ## The problem
52
+
53
+ Points are almost always stored in a one-dimensional order. You would like points that are close in space to also be close in memory, so that the nearest neighbor of `P[n]` is often `P[n±1]`, `P[n±2]`, or a bit further. This is a well-studied problem, and space-filling curves such as the Morton curve are a common solution.
54
+
55
+ Gridpoints takes a different route. Instead of a 1D order, it gives each point a **multi-index** `[i, j, ...]` in a D-dimensional grid. This matters because a grid supports stencil operations ("look at the cells within a few steps in each direction", `[i±di, j±dj, k±dk]`).
56
+
57
+ ## What Gridpoints does
58
+
59
+ Input: a point cloud `P` of shape `(N, D)` and a target grid shape `(I, J, ...)`.
60
+
61
+ Output: a permutation `order` of the indices such that
62
+
63
+ ```python
64
+ Pgrid = P[order].reshape(I, J, ..., D)
65
+ ```
66
+
67
+ is a bijection between points and grid cells (one point, one cell), and is **sorted along every axis**. In 3D, with xyz notation `P = (x, y, z)`:
68
+
69
+ ```text
70
+ x[i+1, j, k] >= x[i, j, k] # x increases along axis 0
71
+ y[i, j+1, k] >= y[i, j, k] # y increases along axis 1
72
+ z[i, j, k+1] >= z[i, j, k] # z increases along axis 2
73
+ ```
74
+
75
+ This property (*grid monotonicity*) is the only thing guaranteed. See [Neighborhood radius](#neighborhood-radius) for what it does and does not imply.
76
+
77
+ On `Pgrid`, a neighbor query becomes a stencil lookup:
78
+
79
+ ```text
80
+ neighborhood(Pgrid[i, j, k]) ≈ { Pgrid[i±di, j±dj, k±dk] : (di, dj, dk) within radius R }
81
+ ```
82
+
83
+ where `R` is a cutoff you choose for your task. Operations built on this run in **O(N)** on the grid view.
84
+
85
+ ---
86
+
87
+ ## Internal algorithm
88
+
89
+ To get the main ideas of the sorting algorithm, the core of `gridpoints` is:
90
+ 1. initialisation: a good starting assignement is important both for quick convergence, and to converge on an assignement with good neighborhood properties. The initialiser build a kdtree on the points, and convert the tree cuts to a grid structure
91
+ 2. iterative 1d sorts: once the initialiser built the starting assignement, gridpoints further optimise it iteratively by looking at 1d slices of the grid (axial slices, but also diagonal ones), and apply standard 1d sort on the points, projected along the direction of the slice.
92
+
93
+ For interested readers, the full sorting procedure and motivation behind is explained in more details [here](https://github.com/Neighborhood-Grid/Cartesian-Grid-Sort).
94
+
95
+ ## Installation
96
+
97
+ ```bash
98
+ pip install gridpoints # core only
99
+ pip install gridpoints[demo] # adds visualisation tools for the demo
100
+ ```
101
+
102
+ ## Demo 1. modelnet40:
103
+ To play with real life objects from the Modelnet40 dataset, first get modelnet40 from [princeton.edu](http://modelnet.cs.princeton.edu) (~2 GB), then:
104
+ ```
105
+ python modelnet40_demo.py --obj table --num 6
106
+ python modelnet40_demo.py --obj airplane --num 1
107
+ ...
108
+ ```
109
+ Animated figures (morphing from the raw point cloud to a cubic grid) will be saved to sqrnet\plot.gif
110
+
111
+ ## Demo 2. big point clouds:
112
+ To test gridpoints on various synthetic datasets, see the notebook: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
113
+
114
+ ## Quickstart
115
+
116
+ ```python
117
+ import gridpoints as grid
118
+ import numpy as np
119
+
120
+ N = 1_000_000
121
+ A = np.random.rand(N, 3) # raw point cloud (NumPy, PyTorch or CuPy)
122
+
123
+ # 1. Compute the permutation and view the points as a grid
124
+ order = grid.argsort(A, gridshape=(100, 100, 100))
125
+ Bflat = A[order] # sorted, flat (C-order)
126
+ Bgrid = Bflat.reshape(100, 100, 100, 3) # grid view
127
+
128
+ # 2. Work on the grid
129
+ Cgrid = apply_something(Bgrid) # e.g. a convolution; shape (100, 100, 100, *)
130
+
131
+ # 3. Go back to the original point order
132
+ Cflat = Cgrid.reshape(N, -1)
133
+ orderinv = grid.invert_permutation(order)
134
+ C = Cflat[orderinv] # back to the original point indexing
135
+ ```
136
+
137
+ `apply_something` stands for whatever you want to compute on the grid.
138
+
139
+ `gridpoints.sort()` is also available.
140
+
141
+ ---
142
+
143
+ ## Choosing the parameters
144
+
145
+ ### Grid shape
146
+
147
+ The grid shape must be roughly tuned to the *kind* of data, with the constraint that the number of points `N` has to be factorized as `I⋅J⋅K⋅…`
148
+
149
+ - The exact factorization hardly matters: `(18, 20, 16)` and `(16, 15, 24)` behave similarly.
150
+ - The **orders of magnitude** of each dimension matter a lot: `(18, 20, 16)` and `(36, 40, 4)` can give substantially different results.
151
+ - For a thin surface (e.g. an eggshell), use a flat grid such as `(128, 128, 2)`. A naive `(32, 32, 32)` grid on the same data produces a poor representation of the geometry.
152
+
153
+ **Padding:** if the number of cells `I⋅J⋅K⋅…` exceeds the number of points `N`, the point cloud can be padded with special values (see [Padding](#padding-with-nan-and-inf)). This lets you sort a prime or variable number of points, and removes the integer-factorization constraint on the grid shape.
154
+
155
+ ### Neighborhood radius
156
+
157
+ There is no theoretical guarantee on the radius `R` needed for a given task. Nearest neighbors are not guaranteed to land in exactly adjacent cells.
158
+
159
+ Empirically, in 2D, `R = 5` captures about 99% of nearest neighbors. Some outliers might sit further away, mostly on complex geometries with sharp peaks, holes or other non-smooth features.
160
+
161
+ If you need stricter neighborhoods, or work in higher dimension, there are two options:
162
+
163
+ - **Ensemble of grids:** build several grids, each on a rotated or projected view of the points, and combine them (see https://arxiv.org/abs/1505.00880 for the concept of multi view pooling). Projecting the data randomly onto one or several lower-dimensional subspaces before sorting also helps reduce topology-specific effects, since random projections approximately preserve pairwise distances (Johnson–Lindenstrauss lemma).
164
+ - **Tiling:** if you need *exact* neighbors, tiling gives them. Split the grid into tiles, attach metadata to each tile (e.g. a bounding box), and skip pairs of tiles that are certifiably too far apart for your criterion. The grid does the bulk of the work; the tiles only certify or correct the rest.
165
+
166
+ ### Padding with NaN and Inf
167
+
168
+ NaN and Inf values are supported in a consistent way by the package, so that padding is natural:
169
+
170
+ - `NaN` → placed at a random position
171
+ - `±Inf` → placed on the border of the grid
172
+
173
+ This lets you complete the N real points with ΔN fictitious points so that N + ΔN = I⋅J⋅K⋅… , using `gridpoints.pad`
174
+
175
+ ---
176
+
177
+ ## Performance
178
+
179
+ Sorting 1 million points (2D/3D), measured on Google Colab:
180
+
181
+ | Backend | Time |
182
+ |---|---|
183
+ | NumPy (CPU) | ~10 s |
184
+ | PyTorch (T4 GPU) | ~0.5 s |
185
+ | CuPy (T4 GPU) | ~1 s (the first, cold call takes ~10 s because of compilation) |
186
+
187
+ ### Running stencil operations efficiently
188
+
189
+ A typical use looks like:
190
+
191
+ ```text
192
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] ) for (di, dj, dk) in a local window
193
+ ```
194
+
195
+ To compute this kind of kernel efficiently, avoid pure Python loops, which are slow. Prefer:
196
+
197
+ - Native grid convolutions of standard libraries, whenever your operation can be expressed that way.
198
+ - `pystencils` or `taichi` for complex or non-linear kernels.
199
+ - On GPU, tiling the grid with a custom `triton` kernel can be particularly efficient.
200
+
201
+ ---
202
+
203
+ ## Limits
204
+
205
+ **Good fit**
206
+
207
+ - **Low-dimensional point clouds.** 2D and 3D are the sweet spot, with enough points for O(N) grid operations to pay off.
208
+ - **Pipelines that benefit from regular arrays:** GPU processing, convolutions, repeated local operations.
209
+ - **Nice geometries.** "Nice" is deliberately informal. The question is: can you mentally spread your point distribution onto a regular box with the specified dimensions without your brain blowing up in the process? Remarkably, this requires neither convexity (a map of France, a sponge, a donut, an elephant, an eggshell are valid) nor connectedness (clusters and blobs are valid, Gridpoints just glues them together). Examples are in the [plots folder](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots).
210
+
211
+ **Poor fit**
212
+
213
+ - **High dimension.** Arbitrary dimension is supported, but the sweet spot is 2D/3D. Dimensions 4 to 6 are still reasonable. Above ~8D, Gridpoints is generally not appropriate. Note that many powerful dimension reduction technique exists beyond random projections ensemble or principal component analysis. Modern ml tools such as auto-encoders or parametric umap could maybe help to convert high dimensional data to the low dimensional framework of gridpoints.
214
+ - **Small point clouds.** Below a few hundred points, the overhead is not worth it: a naive quadratic implementation of your kernels will be simpler and probably faster.
215
+ - **Anti-grid geometries.** A spider web, a wind turbine... The sort still runs, but a single grid sort gives a poor representation of the structure. For such distributions, you currently need a projection + grid ensemble procedure, which must be set up manually. That may make Gridpoints less attractive if you were looking for a light, out-of-the-box gridification framework. I am working on making this more automatic, and I hope that at some point Gridpoints will be easily applicable to a much wider range of geometries. Updates will be posted here!
216
+
217
+ ## Should you use it?
218
+
219
+ Try it if you process 2D/3D point clouds with local operations and you want regular array layouts (especially on GPU). Approximate neighborhoods work out of the box, and exact ones are available through tiling.
220
+
221
+ Stay with a KD-tree or a graph method if you work in high dimension or have only a few hundred points. If you need exact nearest neighbors, Gridpoints can still work, via tiling, but expect some extra implementation effort.
222
+
223
+ A quick way to decide is to run the [demo notebook](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb) on a sample of your own data and check how much of your true nearest neighbors fall within your radius `R`.
@@ -0,0 +1,207 @@
1
+ # Gridpoints
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/gridpoints?logo=pypi&logoColor=white)](https://pypi.org/project/gridpoints/)
4
+ [![GitHub](https://img.shields.io/badge/GitHub-gridpoints-181717?logo=github)](https://github.com/Neighborhood-Grid/gridpoints)
5
+
6
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/plots/table(1).gif">
7
+
8
+ **Reorder an unstructured point cloud into a regular grid, so that neighbor-based operations become simple grid processing.**
9
+
10
+ ```python
11
+ import gridpoints as grid
12
+ points = your_dataset_of_points(1000000, 3) # standard (N, D) array
13
+ order = grid.argsort(points, gridshape=(100, 100, 100)) # set gridshape: (I, J, K, ...) = (100, 100, 100)
14
+ Pgrid = points[order].reshape(100, 100, 100, 3) # now an (I, J, K, ..., D) version of your dataset
15
+ # Ready for convolution, neighbor look-ups or geometrical analysis.
16
+ # Note that grid.argsort also supports a batch mode (B, N, D) -> (B, I, J, K, ..., D)
17
+ ```
18
+
19
+ Gridpoints supports NumPy, PyTorch and CuPy arrays.
20
+ The `argsort()` reordering is a one-to-one assignment of your points to the cells of a grid with the given `gridshape`. This is related to Monge optimal transport, but `argsort()` trades optimality for scalability, by combining greedy axial operations which exploit the specific structure of grids. One million 2D/3D points are sorted in about 0.5 s on a GPU (PyTorch) or 10 s on a CPU (NumPy).
21
+
22
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/plots/ballexemple.png">
23
+
24
+ ---
25
+
26
+ ## TL;DR
27
+
28
+ - **Problem:** point clouds are stored as a flat list `[P1, ..., PN]`. Finding the neighbors of a point usually needs a tree or a graph (KD-tree, radius graph...), which is hard to use with standard tensor-based machine learning, especially on GPU.
29
+ - **Idea:** assign every point to a cell of a D-dimensional grid, one point per cell, such that the grid is sorted along each of its axes. Then the neighbors of a point are *approximately* the points in nearby cells, found by plain indexing.
30
+ - **Payoff:** local operations (convolution, smoothing, clustering, interpolation...) that are usually hard to implement on irregular point clouds can be applied on the grid: `points -> grid`. To recover the result on the points, one just applies the inverse assignment: `grid -> points`.
31
+ - **Catch:** a plain grid gives *approximate* neighborhoods (about 99% of true nearest neighbors within a small radius). Exact neighborhoods are recoverable with tiling (see [Neighborhood radius](#neighborhood-radius)). It works best in 2D/3D, and stays reasonable up to about 6D.
32
+
33
+ ---
34
+
35
+ ## The problem
36
+
37
+ Points are almost always stored in a one-dimensional order. You would like points that are close in space to also be close in memory, so that the nearest neighbor of `P[n]` is often `P[n±1]`, `P[n±2]`, or a bit further. This is a well-studied problem, and space-filling curves such as the Morton curve are a common solution.
38
+
39
+ Gridpoints takes a different route. Instead of a 1D order, it gives each point a **multi-index** `[i, j, ...]` in a D-dimensional grid. This matters because a grid supports stencil operations ("look at the cells within a few steps in each direction", `[i±di, j±dj, k±dk]`).
40
+
41
+ ## What Gridpoints does
42
+
43
+ Input: a point cloud `P` of shape `(N, D)` and a target grid shape `(I, J, ...)`.
44
+
45
+ Output: a permutation `order` of the indices such that
46
+
47
+ ```python
48
+ Pgrid = P[order].reshape(I, J, ..., D)
49
+ ```
50
+
51
+ is a bijection between points and grid cells (one point, one cell), and is **sorted along every axis**. In 3D, with xyz notation `P = (x, y, z)`:
52
+
53
+ ```text
54
+ x[i+1, j, k] >= x[i, j, k] # x increases along axis 0
55
+ y[i, j+1, k] >= y[i, j, k] # y increases along axis 1
56
+ z[i, j, k+1] >= z[i, j, k] # z increases along axis 2
57
+ ```
58
+
59
+ This property (*grid monotonicity*) is the only thing guaranteed. See [Neighborhood radius](#neighborhood-radius) for what it does and does not imply.
60
+
61
+ On `Pgrid`, a neighbor query becomes a stencil lookup:
62
+
63
+ ```text
64
+ neighborhood(Pgrid[i, j, k]) ≈ { Pgrid[i±di, j±dj, k±dk] : (di, dj, dk) within radius R }
65
+ ```
66
+
67
+ where `R` is a cutoff you choose for your task. Operations built on this run in **O(N)** on the grid view.
68
+
69
+ ---
70
+
71
+ ## Internal algorithm
72
+
73
+ To get the main ideas of the sorting algorithm, the core of `gridpoints` is:
74
+ 1. initialisation: a good starting assignement is important both for quick convergence, and to converge on an assignement with good neighborhood properties. The initialiser build a kdtree on the points, and convert the tree cuts to a grid structure
75
+ 2. iterative 1d sorts: once the initialiser built the starting assignement, gridpoints further optimise it iteratively by looking at 1d slices of the grid (axial slices, but also diagonal ones), and apply standard 1d sort on the points, projected along the direction of the slice.
76
+
77
+ For interested readers, the full sorting procedure and motivation behind is explained in more details [here](https://github.com/Neighborhood-Grid/Cartesian-Grid-Sort).
78
+
79
+ ## Installation
80
+
81
+ ```bash
82
+ pip install gridpoints # core only
83
+ pip install gridpoints[demo] # adds visualisation tools for the demo
84
+ ```
85
+
86
+ ## Demo 1. modelnet40:
87
+ To play with real life objects from the Modelnet40 dataset, first get modelnet40 from [princeton.edu](http://modelnet.cs.princeton.edu) (~2 GB), then:
88
+ ```
89
+ python modelnet40_demo.py --obj table --num 6
90
+ python modelnet40_demo.py --obj airplane --num 1
91
+ ...
92
+ ```
93
+ Animated figures (morphing from the raw point cloud to a cubic grid) will be saved to sqrnet\plot.gif
94
+
95
+ ## Demo 2. big point clouds:
96
+ To test gridpoints on various synthetic datasets, see the notebook: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
97
+
98
+ ## Quickstart
99
+
100
+ ```python
101
+ import gridpoints as grid
102
+ import numpy as np
103
+
104
+ N = 1_000_000
105
+ A = np.random.rand(N, 3) # raw point cloud (NumPy, PyTorch or CuPy)
106
+
107
+ # 1. Compute the permutation and view the points as a grid
108
+ order = grid.argsort(A, gridshape=(100, 100, 100))
109
+ Bflat = A[order] # sorted, flat (C-order)
110
+ Bgrid = Bflat.reshape(100, 100, 100, 3) # grid view
111
+
112
+ # 2. Work on the grid
113
+ Cgrid = apply_something(Bgrid) # e.g. a convolution; shape (100, 100, 100, *)
114
+
115
+ # 3. Go back to the original point order
116
+ Cflat = Cgrid.reshape(N, -1)
117
+ orderinv = grid.invert_permutation(order)
118
+ C = Cflat[orderinv] # back to the original point indexing
119
+ ```
120
+
121
+ `apply_something` stands for whatever you want to compute on the grid.
122
+
123
+ `gridpoints.sort()` is also available.
124
+
125
+ ---
126
+
127
+ ## Choosing the parameters
128
+
129
+ ### Grid shape
130
+
131
+ The grid shape must be roughly tuned to the *kind* of data, with the constraint that the number of points `N` has to be factorized as `I⋅J⋅K⋅…`
132
+
133
+ - The exact factorization hardly matters: `(18, 20, 16)` and `(16, 15, 24)` behave similarly.
134
+ - The **orders of magnitude** of each dimension matter a lot: `(18, 20, 16)` and `(36, 40, 4)` can give substantially different results.
135
+ - For a thin surface (e.g. an eggshell), use a flat grid such as `(128, 128, 2)`. A naive `(32, 32, 32)` grid on the same data produces a poor representation of the geometry.
136
+
137
+ **Padding:** if the number of cells `I⋅J⋅K⋅…` exceeds the number of points `N`, the point cloud can be padded with special values (see [Padding](#padding-with-nan-and-inf)). This lets you sort a prime or variable number of points, and removes the integer-factorization constraint on the grid shape.
138
+
139
+ ### Neighborhood radius
140
+
141
+ There is no theoretical guarantee on the radius `R` needed for a given task. Nearest neighbors are not guaranteed to land in exactly adjacent cells.
142
+
143
+ Empirically, in 2D, `R = 5` captures about 99% of nearest neighbors. Some outliers might sit further away, mostly on complex geometries with sharp peaks, holes or other non-smooth features.
144
+
145
+ If you need stricter neighborhoods, or work in higher dimension, there are two options:
146
+
147
+ - **Ensemble of grids:** build several grids, each on a rotated or projected view of the points, and combine them (see https://arxiv.org/abs/1505.00880 for the concept of multi view pooling). Projecting the data randomly onto one or several lower-dimensional subspaces before sorting also helps reduce topology-specific effects, since random projections approximately preserve pairwise distances (Johnson–Lindenstrauss lemma).
148
+ - **Tiling:** if you need *exact* neighbors, tiling gives them. Split the grid into tiles, attach metadata to each tile (e.g. a bounding box), and skip pairs of tiles that are certifiably too far apart for your criterion. The grid does the bulk of the work; the tiles only certify or correct the rest.
149
+
150
+ ### Padding with NaN and Inf
151
+
152
+ NaN and Inf values are supported in a consistent way by the package, so that padding is natural:
153
+
154
+ - `NaN` → placed at a random position
155
+ - `±Inf` → placed on the border of the grid
156
+
157
+ This lets you complete the N real points with ΔN fictitious points so that N + ΔN = I⋅J⋅K⋅… , using `gridpoints.pad`
158
+
159
+ ---
160
+
161
+ ## Performance
162
+
163
+ Sorting 1 million points (2D/3D), measured on Google Colab:
164
+
165
+ | Backend | Time |
166
+ |---|---|
167
+ | NumPy (CPU) | ~10 s |
168
+ | PyTorch (T4 GPU) | ~0.5 s |
169
+ | CuPy (T4 GPU) | ~1 s (the first, cold call takes ~10 s because of compilation) |
170
+
171
+ ### Running stencil operations efficiently
172
+
173
+ A typical use looks like:
174
+
175
+ ```text
176
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] ) for (di, dj, dk) in a local window
177
+ ```
178
+
179
+ To compute this kind of kernel efficiently, avoid pure Python loops, which are slow. Prefer:
180
+
181
+ - Native grid convolutions of standard libraries, whenever your operation can be expressed that way.
182
+ - `pystencils` or `taichi` for complex or non-linear kernels.
183
+ - On GPU, tiling the grid with a custom `triton` kernel can be particularly efficient.
184
+
185
+ ---
186
+
187
+ ## Limits
188
+
189
+ **Good fit**
190
+
191
+ - **Low-dimensional point clouds.** 2D and 3D are the sweet spot, with enough points for O(N) grid operations to pay off.
192
+ - **Pipelines that benefit from regular arrays:** GPU processing, convolutions, repeated local operations.
193
+ - **Nice geometries.** "Nice" is deliberately informal. The question is: can you mentally spread your point distribution onto a regular box with the specified dimensions without your brain blowing up in the process? Remarkably, this requires neither convexity (a map of France, a sponge, a donut, an elephant, an eggshell are valid) nor connectedness (clusters and blobs are valid, Gridpoints just glues them together). Examples are in the [plots folder](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots).
194
+
195
+ **Poor fit**
196
+
197
+ - **High dimension.** Arbitrary dimension is supported, but the sweet spot is 2D/3D. Dimensions 4 to 6 are still reasonable. Above ~8D, Gridpoints is generally not appropriate. Note that many powerful dimension reduction technique exists beyond random projections ensemble or principal component analysis. Modern ml tools such as auto-encoders or parametric umap could maybe help to convert high dimensional data to the low dimensional framework of gridpoints.
198
+ - **Small point clouds.** Below a few hundred points, the overhead is not worth it: a naive quadratic implementation of your kernels will be simpler and probably faster.
199
+ - **Anti-grid geometries.** A spider web, a wind turbine... The sort still runs, but a single grid sort gives a poor representation of the structure. For such distributions, you currently need a projection + grid ensemble procedure, which must be set up manually. That may make Gridpoints less attractive if you were looking for a light, out-of-the-box gridification framework. I am working on making this more automatic, and I hope that at some point Gridpoints will be easily applicable to a much wider range of geometries. Updates will be posted here!
200
+
201
+ ## Should you use it?
202
+
203
+ Try it if you process 2D/3D point clouds with local operations and you want regular array layouts (especially on GPU). Approximate neighborhoods work out of the box, and exact ones are available through tiling.
204
+
205
+ Stay with a KD-tree or a graph method if you work in high dimension or have only a few hundred points. If you need exact nearest neighbors, Gridpoints can still work, via tiling, but expect some extra implementation effort.
206
+
207
+ A quick way to decide is to run the [demo notebook](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb) on a sample of your own data and check how much of your true nearest neighbors fall within your radius `R`.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "gridpoints"
7
- version = "1.1.0"
7
+ version = "1.1.2"
8
8
  description = "fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid "
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -10,4 +10,4 @@ __all__ = (
10
10
  "pad",
11
11
  )
12
12
 
13
- __version__ = "1.1.0"
13
+ __version__ = "1.1.2"
@@ -2,7 +2,7 @@
2
2
  Helper to complete point clouds so that they fit a grid.
3
3
  """
4
4
 
5
- from .array_libs import arange, astype, empty
5
+ from .array_libs import arange, astype, empty, get_backend
6
6
 
7
7
 
8
8
  def pad(X, n_total, value="nan"):
@@ -26,23 +26,31 @@ def pad(X, n_total, value="nan"):
26
26
  X_padded : array (n_total, D) or (B, n_total, D)
27
27
  """
28
28
  assert value in ("nan", "inf"), f"value must be 'nan' or 'inf', got {value!r}"
29
- single = hasattr(X, 'ndim') and X.ndim == 2
30
- clouds = [X] if single else list(X)
31
- assert max(len(c) for c in clouds) <= n_total, "more points than n_total"
29
+ is_array = hasattr(X, 'ndim')
30
+ ref = X if is_array else X[0]
31
+ assert (X.shape[-2] if is_array else max(len(c) for c in X)) <= n_total, "more points than n_total"
32
32
 
33
- ref = clouds[0]
34
33
  dtype = str(ref.dtype).replace('torch.', '')
35
34
  if 'float' not in dtype: # integers cannot hold nan / inf
36
35
  dtype = 'float64'
37
36
  D = ref.shape[-1]
38
- out = empty((len(clouds), n_total, D), dtype, ref)
39
- out[...] = float(value)
37
+ shape = (n_total, D) if is_array and X.ndim == 2 else (len(X), n_total, D)
40
38
 
41
- for b, cloud in enumerate(clouds):
42
- out[b, :len(cloud)] = astype(cloud, dtype)
43
- if value == "inf": # point k gets the signs given by the bits of k, one per coordinate
44
- k = arange(n_total - len(cloud), 'int64', ref)[:, None]
45
- bits = (k >> (D - 1 - arange(D, 'int64', ref))[None, :]) & 1
46
- out[b, len(cloud):] = astype(1 - 2 * bits, dtype) * float("inf")
39
+ # 1. Prefill with nan, then copy the true points in.
40
+ out = empty(shape, dtype, ref)
41
+ out[...] = float('nan')
42
+ if is_array:
43
+ out[..., :X.shape[-2], :] = astype(X, dtype)
44
+ else:
45
+ for b, cloud in enumerate(X):
46
+ out[b, :len(cloud)] = astype(cloud, dtype)
47
47
 
48
- return out[0] if single else out
48
+ # 2. Turn the nans into +/-inf: point k, coordinate d gets the sign of bit d of k.
49
+ if value == "inf":
50
+ xp, _ = get_backend(out)
51
+ k = arange(n_total, 'int64', ref)[:, None]
52
+ d = arange(D, 'int64', ref)[None, :]
53
+ signs = astype(1 - 2 * ((k // 2 ** (D - 1 - d)) % 2), dtype) # (n_total, D)
54
+ out = xp.where(xp.isnan(out), signs * float('inf'), out)
55
+
56
+ return out
@@ -0,0 +1,223 @@
1
+ Metadata-Version: 2.4
2
+ Name: gridpoints
3
+ Version: 1.1.2
4
+ Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
+ Author: Armand de Cacqueray
6
+ License: MIT
7
+ Project-URL: Homepage, https://neighborhood-grid.github.io/gridpoints/
8
+ Project-URL: Repository, https://github.com/Neighborhood-Grid/gridpoints
9
+ Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
+ Requires-Python: >=3.9
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Provides-Extra: demo
14
+ Requires-Dist: squarenet; extra == "demo"
15
+ Dynamic: license-file
16
+
17
+ # Gridpoints
18
+
19
+ [![PyPI](https://img.shields.io/pypi/v/gridpoints?logo=pypi&logoColor=white)](https://pypi.org/project/gridpoints/)
20
+ [![GitHub](https://img.shields.io/badge/GitHub-gridpoints-181717?logo=github)](https://github.com/Neighborhood-Grid/gridpoints)
21
+
22
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/plots/table(1).gif">
23
+
24
+ **Reorder an unstructured point cloud into a regular grid, so that neighbor-based operations become simple grid processing.**
25
+
26
+ ```python
27
+ import gridpoints as grid
28
+ points = your_dataset_of_points(1000000, 3) # standard (N, D) array
29
+ order = grid.argsort(points, gridshape=(100, 100, 100)) # set gridshape: (I, J, K, ...) = (100, 100, 100)
30
+ Pgrid = points[order].reshape(100, 100, 100, 3) # now an (I, J, K, ..., D) version of your dataset
31
+ # Ready for convolution, neighbor look-ups or geometrical analysis.
32
+ # Note that grid.argsort also supports a batch mode (B, N, D) -> (B, I, J, K, ..., D)
33
+ ```
34
+
35
+ Gridpoints supports NumPy, PyTorch and CuPy arrays.
36
+ The `argsort()` reordering is a one-to-one assignment of your points to the cells of a grid with the given `gridshape`. This is related to Monge optimal transport, but `argsort()` trades optimality for scalability, by combining greedy axial operations which exploit the specific structure of grids. One million 2D/3D points are sorted in about 0.5 s on a GPU (PyTorch) or 10 s on a CPU (NumPy).
37
+
38
+ <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/plots/ballexemple.png">
39
+
40
+ ---
41
+
42
+ ## TL;DR
43
+
44
+ - **Problem:** point clouds are stored as a flat list `[P1, ..., PN]`. Finding the neighbors of a point usually needs a tree or a graph (KD-tree, radius graph...), which is hard to use with standard tensor-based machine learning, especially on GPU.
45
+ - **Idea:** assign every point to a cell of a D-dimensional grid, one point per cell, such that the grid is sorted along each of its axes. Then the neighbors of a point are *approximately* the points in nearby cells, found by plain indexing.
46
+ - **Payoff:** local operations (convolution, smoothing, clustering, interpolation...) that are usually hard to implement on irregular point clouds can be applied on the grid: `points -> grid`. To recover the result on the points, one just applies the inverse assignment: `grid -> points`.
47
+ - **Catch:** a plain grid gives *approximate* neighborhoods (about 99% of true nearest neighbors within a small radius). Exact neighborhoods are recoverable with tiling (see [Neighborhood radius](#neighborhood-radius)). It works best in 2D/3D, and stays reasonable up to about 6D.
48
+
49
+ ---
50
+
51
+ ## The problem
52
+
53
+ Points are almost always stored in a one-dimensional order. You would like points that are close in space to also be close in memory, so that the nearest neighbor of `P[n]` is often `P[n±1]`, `P[n±2]`, or a bit further. This is a well-studied problem, and space-filling curves such as the Morton curve are a common solution.
54
+
55
+ Gridpoints takes a different route. Instead of a 1D order, it gives each point a **multi-index** `[i, j, ...]` in a D-dimensional grid. This matters because a grid supports stencil operations ("look at the cells within a few steps in each direction", `[i±di, j±dj, k±dk]`).
56
+
57
+ ## What Gridpoints does
58
+
59
+ Input: a point cloud `P` of shape `(N, D)` and a target grid shape `(I, J, ...)`.
60
+
61
+ Output: a permutation `order` of the indices such that
62
+
63
+ ```python
64
+ Pgrid = P[order].reshape(I, J, ..., D)
65
+ ```
66
+
67
+ is a bijection between points and grid cells (one point, one cell), and is **sorted along every axis**. In 3D, with xyz notation `P = (x, y, z)`:
68
+
69
+ ```text
70
+ x[i+1, j, k] >= x[i, j, k] # x increases along axis 0
71
+ y[i, j+1, k] >= y[i, j, k] # y increases along axis 1
72
+ z[i, j, k+1] >= z[i, j, k] # z increases along axis 2
73
+ ```
74
+
75
+ This property (*grid monotonicity*) is the only thing guaranteed. See [Neighborhood radius](#neighborhood-radius) for what it does and does not imply.
76
+
77
+ On `Pgrid`, a neighbor query becomes a stencil lookup:
78
+
79
+ ```text
80
+ neighborhood(Pgrid[i, j, k]) ≈ { Pgrid[i±di, j±dj, k±dk] : (di, dj, dk) within radius R }
81
+ ```
82
+
83
+ where `R` is a cutoff you choose for your task. Operations built on this run in **O(N)** on the grid view.
84
+
85
+ ---
86
+
87
+ ## Internal algorithm
88
+
89
+ To get the main ideas of the sorting algorithm, the core of `gridpoints` is:
90
+ 1. initialisation: a good starting assignement is important both for quick convergence, and to converge on an assignement with good neighborhood properties. The initialiser build a kdtree on the points, and convert the tree cuts to a grid structure
91
+ 2. iterative 1d sorts: once the initialiser built the starting assignement, gridpoints further optimise it iteratively by looking at 1d slices of the grid (axial slices, but also diagonal ones), and apply standard 1d sort on the points, projected along the direction of the slice.
92
+
93
+ For interested readers, the full sorting procedure and motivation behind is explained in more details [here](https://github.com/Neighborhood-Grid/Cartesian-Grid-Sort).
94
+
95
+ ## Installation
96
+
97
+ ```bash
98
+ pip install gridpoints # core only
99
+ pip install gridpoints[demo] # adds visualisation tools for the demo
100
+ ```
101
+
102
+ ## Demo 1. modelnet40:
103
+ To play with real life objects from the Modelnet40 dataset, first get modelnet40 from [princeton.edu](http://modelnet.cs.princeton.edu) (~2 GB), then:
104
+ ```
105
+ python modelnet40_demo.py --obj table --num 6
106
+ python modelnet40_demo.py --obj airplane --num 1
107
+ ...
108
+ ```
109
+ Animated figures (morphing from the raw point cloud to a cubic grid) will be saved to sqrnet\plot.gif
110
+
111
+ ## Demo 2. big point clouds:
112
+ To test gridpoints on various synthetic datasets, see the notebook: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
113
+
114
+ ## Quickstart
115
+
116
+ ```python
117
+ import gridpoints as grid
118
+ import numpy as np
119
+
120
+ N = 1_000_000
121
+ A = np.random.rand(N, 3) # raw point cloud (NumPy, PyTorch or CuPy)
122
+
123
+ # 1. Compute the permutation and view the points as a grid
124
+ order = grid.argsort(A, gridshape=(100, 100, 100))
125
+ Bflat = A[order] # sorted, flat (C-order)
126
+ Bgrid = Bflat.reshape(100, 100, 100, 3) # grid view
127
+
128
+ # 2. Work on the grid
129
+ Cgrid = apply_something(Bgrid) # e.g. a convolution; shape (100, 100, 100, *)
130
+
131
+ # 3. Go back to the original point order
132
+ Cflat = Cgrid.reshape(N, -1)
133
+ orderinv = grid.invert_permutation(order)
134
+ C = Cflat[orderinv] # back to the original point indexing
135
+ ```
136
+
137
+ `apply_something` stands for whatever you want to compute on the grid.
138
+
139
+ `gridpoints.sort()` is also available.
140
+
141
+ ---
142
+
143
+ ## Choosing the parameters
144
+
145
+ ### Grid shape
146
+
147
+ The grid shape must be roughly tuned to the *kind* of data, with the constraint that the number of points `N` has to be factorized as `I⋅J⋅K⋅…`
148
+
149
+ - The exact factorization hardly matters: `(18, 20, 16)` and `(16, 15, 24)` behave similarly.
150
+ - The **orders of magnitude** of each dimension matter a lot: `(18, 20, 16)` and `(36, 40, 4)` can give substantially different results.
151
+ - For a thin surface (e.g. an eggshell), use a flat grid such as `(128, 128, 2)`. A naive `(32, 32, 32)` grid on the same data produces a poor representation of the geometry.
152
+
153
+ **Padding:** if the number of cells `I⋅J⋅K⋅…` exceeds the number of points `N`, the point cloud can be padded with special values (see [Padding](#padding-with-nan-and-inf)). This lets you sort a prime or variable number of points, and removes the integer-factorization constraint on the grid shape.
154
+
155
+ ### Neighborhood radius
156
+
157
+ There is no theoretical guarantee on the radius `R` needed for a given task. Nearest neighbors are not guaranteed to land in exactly adjacent cells.
158
+
159
+ Empirically, in 2D, `R = 5` captures about 99% of nearest neighbors. Some outliers might sit further away, mostly on complex geometries with sharp peaks, holes or other non-smooth features.
160
+
161
+ If you need stricter neighborhoods, or work in higher dimension, there are two options:
162
+
163
+ - **Ensemble of grids:** build several grids, each on a rotated or projected view of the points, and combine them (see https://arxiv.org/abs/1505.00880 for the concept of multi view pooling). Projecting the data randomly onto one or several lower-dimensional subspaces before sorting also helps reduce topology-specific effects, since random projections approximately preserve pairwise distances (Johnson–Lindenstrauss lemma).
164
+ - **Tiling:** if you need *exact* neighbors, tiling gives them. Split the grid into tiles, attach metadata to each tile (e.g. a bounding box), and skip pairs of tiles that are certifiably too far apart for your criterion. The grid does the bulk of the work; the tiles only certify or correct the rest.
165
+
166
+ ### Padding with NaN and Inf
167
+
168
+ NaN and Inf values are supported in a consistent way by the package, so that padding is natural:
169
+
170
+ - `NaN` → placed at a random position
171
+ - `±Inf` → placed on the border of the grid
172
+
173
+ This lets you complete the N real points with ΔN fictitious points so that N + ΔN = I⋅J⋅K⋅… , using `gridpoints.pad`
174
+
175
+ ---
176
+
177
+ ## Performance
178
+
179
+ Sorting 1 million points (2D/3D), measured on Google Colab:
180
+
181
+ | Backend | Time |
182
+ |---|---|
183
+ | NumPy (CPU) | ~10 s |
184
+ | PyTorch (T4 GPU) | ~0.5 s |
185
+ | CuPy (T4 GPU) | ~1 s (the first, cold call takes ~10 s because of compilation) |
186
+
187
+ ### Running stencil operations efficiently
188
+
189
+ A typical use looks like:
190
+
191
+ ```text
192
+ output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] ) for (di, dj, dk) in a local window
193
+ ```
194
+
195
+ To compute this kind of kernel efficiently, avoid pure Python loops, which are slow. Prefer:
196
+
197
+ - Native grid convolutions of standard libraries, whenever your operation can be expressed that way.
198
+ - `pystencils` or `taichi` for complex or non-linear kernels.
199
+ - On GPU, tiling the grid with a custom `triton` kernel can be particularly efficient.
200
+
201
+ ---
202
+
203
+ ## Limits
204
+
205
+ **Good fit**
206
+
207
+ - **Low-dimensional point clouds.** 2D and 3D are the sweet spot, with enough points for O(N) grid operations to pay off.
208
+ - **Pipelines that benefit from regular arrays:** GPU processing, convolutions, repeated local operations.
209
+ - **Nice geometries.** "Nice" is deliberately informal. The question is: can you mentally spread your point distribution onto a regular box with the specified dimensions without your brain blowing up in the process? Remarkably, this requires neither convexity (a map of France, a sponge, a donut, an elephant, an eggshell are valid) nor connectedness (clusters and blobs are valid, Gridpoints just glues them together). Examples are in the [plots folder](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots).
210
+
211
+ **Poor fit**
212
+
213
+ - **High dimension.** Arbitrary dimension is supported, but the sweet spot is 2D/3D. Dimensions 4 to 6 are still reasonable. Above ~8D, Gridpoints is generally not appropriate. Note that many powerful dimension reduction technique exists beyond random projections ensemble or principal component analysis. Modern ml tools such as auto-encoders or parametric umap could maybe help to convert high dimensional data to the low dimensional framework of gridpoints.
214
+ - **Small point clouds.** Below a few hundred points, the overhead is not worth it: a naive quadratic implementation of your kernels will be simpler and probably faster.
215
+ - **Anti-grid geometries.** A spider web, a wind turbine... The sort still runs, but a single grid sort gives a poor representation of the structure. For such distributions, you currently need a projection + grid ensemble procedure, which must be set up manually. That may make Gridpoints less attractive if you were looking for a light, out-of-the-box gridification framework. I am working on making this more automatic, and I hope that at some point Gridpoints will be easily applicable to a much wider range of geometries. Updates will be posted here!
216
+
217
+ ## Should you use it?
218
+
219
+ Try it if you process 2D/3D point clouds with local operations and you want regular array layouts (especially on GPU). Approximate neighborhoods work out of the box, and exact ones are available through tiling.
220
+
221
+ Stay with a KD-tree or a graph method if you work in high dimension or have only a few hundred points. If you need exact nearest neighbors, Gridpoints can still work, via tiling, but expect some extra implementation effort.
222
+
223
+ A quick way to decide is to run the [demo notebook](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb) on a sample of your own data and check how much of your true nearest neighbors fall within your radius `R`.
gridpoints-1.1.0/PKG-INFO DELETED
@@ -1,116 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gridpoints
3
- Version: 1.1.0
4
- Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
- Author: Armand de Cacqueray
6
- License: MIT
7
- Project-URL: Homepage, https://neighborhood-grid.github.io/gridpoints/
8
- Project-URL: Repository, https://github.com/Neighborhood-Grid/gridpoints
9
- Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Provides-Extra: demo
14
- Requires-Dist: squarenet; extra == "demo"
15
- Dynamic: license-file
16
-
17
- ## ❒ CubeNet: Multi dimensional sort for point clouds
18
- # with the gridpoints python package
19
-
20
- `Gridpoints` maps raw indexes of unstructured **point clouds** to structured grids multi-index through a **bijective transformation**:
21
- One point, one cell,
22
- ```text
23
- n <-> [i,j,…](n).
24
- ```
25
- It replaces and enhances the [squarenet](https://github.com/Neighborhood-Grid/SquareNet) project with a more robust points sorting algorithm. The goal is
26
- to build a spatialy coherent multi index to later store and query the point cloud efficiently, based on adaptative axes (lines, columns, and more in 3D+) as showed on following exemple :
27
-
28
- <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
29
-
30
- What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that `Pgrid(I, J, …, D)` = P[order].reshape(*gridshape, D) is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
31
- ```text
32
- x[i+1, j, k] >= x[i, j, k]
33
- y[i, j+1, k] >= y[i, j, k]
34
- z[i, j, k+1] >= z[i, j, k]
35
- ```
36
-
37
- → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up
38
- ```text
39
- neighborhood(Pgrid[i, j, k]) = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
40
- ````
41
- where `R` is a radius cutoff to determine, allowing local operations in O(N) time.
42
- → Geometric operations (convolution, clustering, interpolation …) can then be applied **in O(N) time** directly on the `Pgrid` view instead of relying on complex graph or point-cloud methods
43
-
44
- `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D). To allow natural padding when the grid has more cells than they are points in the cloud, NaNs and Infs are supported in a consistent manner:
45
- - nans → random position
46
- - (+-) infs → border of the grid
47
-
48
- This allow to gridsort prime or variable number of points, as long as one is ready to deal with void/special grid cells.
49
-
50
- Expected runtime for sorting 1 million points, tested on google colab GPU T4 for 2D/3D point clouds:
51
- - Numpy: 10s
52
- - Torch (gpu): 500 ms
53
- - Cupy (gpu): 1 s (after compilation e.g. not on the first cold call, which take 10 s)
54
-
55
- `When not to use gridpoints ?`
56
- - high dimension: the package is implemented to support arbitrary dimension, but sweetspot is really 2D/3D. dimensions 4-6 might still be reasonable depending on the task, but anything above 8D is generally too high dimensional for gridpoints.
57
- - weird geometries. Supported datasets goes beyond smooth convex distributions: map of Indonesia, a sponge, a donuts, an elephant, an eggshell (by specifying a suitable gridshape e.g. (128,128,2) to `gridpoints.sort()`). You can see various exemples in the [plot](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots) folder But with some limits. Bad fits: a spider web, a wind turbine, same eggshell with a naive 3d sort (gridshape = (32,32,32)) ... the issue is not that gridpoints cannot sort these distributions, but that it will produce a poor representation of the geometry.
58
- - small point clouds: beyond a few hundred points, local operations in O(N) time is not worth the overhead, because naive quadratic implementations will probably be simultaneously simpler and faster.
59
-
60
- ---
61
-
62
- ### Installation
63
-
64
- ```bash
65
- pip install gridpoints # core only
66
- pip install gridpoints[demo] # for the demonstration notebook.
67
- ```
68
- Full Demo: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
69
-
70
- ### Quickstart
71
-
72
- ```python
73
- import gridpoints as grid
74
- import numpy as np
75
-
76
- # Raw point cloud (numpy, pytorch or cupy)
77
- A = np.random.rand(1_000_000, 3)
78
-
79
- # Sorted view: place the points inside the grid
80
- order = grid.argsort(A, gridshape=(100, 100, 100))
81
- Bflat = A[order] #flat but sorted with a grid layout (C-order)
82
- Bgrid = Bflat.reshape(100, 100, 100, 3) #reshaped as a grid
83
-
84
- # Rest of your pipeline, working with grids
85
- Cgrid = apply_something(Bgrid) #(100, 100, 100, *)
86
-
87
- # Back to the original points indexing
88
- Cflat = Cgrid.reshape(1_000_000, *)
89
- orderinv = grid.invert_permutation(order)
90
- C = Cflat[orderinv] # matches the initial points order
91
- ```
92
-
93
- ---
94
-
95
- ### Note on rectangular gridshapes
96
-
97
- The eggshell example illustrates the importance of selecting a suitable grid-shape decomposition. This can itself be challenging when the point cloud is assumed to follow a complex topological pattern, such as a thin surface rather than a full volumetric domain. While the precise decomposition is not particularly important (e.g. (18,20,16) vs. (16,15,24) will generally make little difference), the orders of magnitude of the different dimensions do matter and should be tuned to the dataset: (18,20,16) and (36,40,4) can lead to substantially different results. As mentioned, nan/inf padding can help accomodate integer-factorization constraints
98
-
99
- Another possibility for smoothing out topology-specific effects is to perform the sort after randomly projecting the dataset onto a lower-dimensional subspace. Random projections are known to approximately preserve pairwise distances, as formalized by the Johnson–Lindenstrauss lemma.
100
-
101
-
102
- ### Note on the cutoff radius R
103
-
104
- There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
105
-
106
- As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the solution might be to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417). An other possibility would be to tile the grid: each tile is enhanced with location metadata (e.g. a bounding box), allowing for pruning pairs of tiles with a distance certified to be far enough for a given criterion.
107
-
108
-
109
- ### Note on efficient stencil operations
110
-
111
- The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
112
-
113
- ```text
114
- output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
115
- ```
116
- To go beyond standard (slow) python loops, this kind of kernel computation can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non O(N) grid kernels. On GPU, tiling the grid and leveraging a custom `triton` kernel might be particularly efficient.
@@ -1,100 +0,0 @@
1
- ## ❒ CubeNet: Multi dimensional sort for point clouds
2
- # with the gridpoints python package
3
-
4
- `Gridpoints` maps raw indexes of unstructured **point clouds** to structured grids multi-index through a **bijective transformation**:
5
- One point, one cell,
6
- ```text
7
- n <-> [i,j,…](n).
8
- ```
9
- It replaces and enhances the [squarenet](https://github.com/Neighborhood-Grid/SquareNet) project with a more robust points sorting algorithm. The goal is
10
- to build a spatialy coherent multi index to later store and query the point cloud efficiently, based on adaptative axes (lines, columns, and more in 3D+) as showed on following exemple :
11
-
12
- <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
13
-
14
- What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that `Pgrid(I, J, …, D)` = P[order].reshape(*gridshape, D) is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
15
- ```text
16
- x[i+1, j, k] >= x[i, j, k]
17
- y[i, j+1, k] >= y[i, j, k]
18
- z[i, j, k+1] >= z[i, j, k]
19
- ```
20
-
21
- → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up
22
- ```text
23
- neighborhood(Pgrid[i, j, k]) = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
24
- ````
25
- where `R` is a radius cutoff to determine, allowing local operations in O(N) time.
26
- → Geometric operations (convolution, clustering, interpolation …) can then be applied **in O(N) time** directly on the `Pgrid` view instead of relying on complex graph or point-cloud methods
27
-
28
- `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D). To allow natural padding when the grid has more cells than they are points in the cloud, NaNs and Infs are supported in a consistent manner:
29
- - nans → random position
30
- - (+-) infs → border of the grid
31
-
32
- This allow to gridsort prime or variable number of points, as long as one is ready to deal with void/special grid cells.
33
-
34
- Expected runtime for sorting 1 million points, tested on google colab GPU T4 for 2D/3D point clouds:
35
- - Numpy: 10s
36
- - Torch (gpu): 500 ms
37
- - Cupy (gpu): 1 s (after compilation e.g. not on the first cold call, which take 10 s)
38
-
39
- `When not to use gridpoints ?`
40
- - high dimension: the package is implemented to support arbitrary dimension, but sweetspot is really 2D/3D. dimensions 4-6 might still be reasonable depending on the task, but anything above 8D is generally too high dimensional for gridpoints.
41
- - weird geometries. Supported datasets goes beyond smooth convex distributions: map of Indonesia, a sponge, a donuts, an elephant, an eggshell (by specifying a suitable gridshape e.g. (128,128,2) to `gridpoints.sort()`). You can see various exemples in the [plot](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots) folder But with some limits. Bad fits: a spider web, a wind turbine, same eggshell with a naive 3d sort (gridshape = (32,32,32)) ... the issue is not that gridpoints cannot sort these distributions, but that it will produce a poor representation of the geometry.
42
- - small point clouds: beyond a few hundred points, local operations in O(N) time is not worth the overhead, because naive quadratic implementations will probably be simultaneously simpler and faster.
43
-
44
- ---
45
-
46
- ### Installation
47
-
48
- ```bash
49
- pip install gridpoints # core only
50
- pip install gridpoints[demo] # for the demonstration notebook.
51
- ```
52
- Full Demo: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
53
-
54
- ### Quickstart
55
-
56
- ```python
57
- import gridpoints as grid
58
- import numpy as np
59
-
60
- # Raw point cloud (numpy, pytorch or cupy)
61
- A = np.random.rand(1_000_000, 3)
62
-
63
- # Sorted view: place the points inside the grid
64
- order = grid.argsort(A, gridshape=(100, 100, 100))
65
- Bflat = A[order] #flat but sorted with a grid layout (C-order)
66
- Bgrid = Bflat.reshape(100, 100, 100, 3) #reshaped as a grid
67
-
68
- # Rest of your pipeline, working with grids
69
- Cgrid = apply_something(Bgrid) #(100, 100, 100, *)
70
-
71
- # Back to the original points indexing
72
- Cflat = Cgrid.reshape(1_000_000, *)
73
- orderinv = grid.invert_permutation(order)
74
- C = Cflat[orderinv] # matches the initial points order
75
- ```
76
-
77
- ---
78
-
79
- ### Note on rectangular gridshapes
80
-
81
- The eggshell example illustrates the importance of selecting a suitable grid-shape decomposition. This can itself be challenging when the point cloud is assumed to follow a complex topological pattern, such as a thin surface rather than a full volumetric domain. While the precise decomposition is not particularly important (e.g. (18,20,16) vs. (16,15,24) will generally make little difference), the orders of magnitude of the different dimensions do matter and should be tuned to the dataset: (18,20,16) and (36,40,4) can lead to substantially different results. As mentioned, nan/inf padding can help accomodate integer-factorization constraints
82
-
83
- Another possibility for smoothing out topology-specific effects is to perform the sort after randomly projecting the dataset onto a lower-dimensional subspace. Random projections are known to approximately preserve pairwise distances, as formalized by the Johnson–Lindenstrauss lemma.
84
-
85
-
86
- ### Note on the cutoff radius R
87
-
88
- There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
89
-
90
- As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the solution might be to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417). An other possibility would be to tile the grid: each tile is enhanced with location metadata (e.g. a bounding box), allowing for pruning pairs of tiles with a distance certified to be far enough for a given criterion.
91
-
92
-
93
- ### Note on efficient stencil operations
94
-
95
- The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
96
-
97
- ```text
98
- output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
99
- ```
100
- To go beyond standard (slow) python loops, this kind of kernel computation can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non O(N) grid kernels. On GPU, tiling the grid and leveraging a custom `triton` kernel might be particularly efficient.
@@ -1,116 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: gridpoints
3
- Version: 1.1.0
4
- Summary: fast multidimensional sorting tool for point clouds using the monotonic lagrangian grid
5
- Author: Armand de Cacqueray
6
- License: MIT
7
- Project-URL: Homepage, https://neighborhood-grid.github.io/gridpoints/
8
- Project-URL: Repository, https://github.com/Neighborhood-Grid/gridpoints
9
- Keywords: sorting,point-clouds,multi-indexing,grid,neighborhood-grid,monotonic-lagrangian-grid,squarenet,nearest-neighbor
10
- Requires-Python: >=3.9
11
- Description-Content-Type: text/markdown
12
- License-File: LICENSE
13
- Provides-Extra: demo
14
- Requires-Dist: squarenet; extra == "demo"
15
- Dynamic: license-file
16
-
17
- ## ❒ CubeNet: Multi dimensional sort for point clouds
18
- # with the gridpoints python package
19
-
20
- `Gridpoints` maps raw indexes of unstructured **point clouds** to structured grids multi-index through a **bijective transformation**:
21
- One point, one cell,
22
- ```text
23
- n <-> [i,j,…](n).
24
- ```
25
- It replaces and enhances the [squarenet](https://github.com/Neighborhood-Grid/SquareNet) project with a more robust points sorting algorithm. The goal is
26
- to build a spatialy coherent multi index to later store and query the point cloud efficiently, based on adaptative axes (lines, columns, and more in 3D+) as showed on following exemple :
27
-
28
- <img src="https://raw.githubusercontent.com/Neighborhood-Grid/CubeNet/main/ballexemple.png">
29
-
30
- What it does: Take raw point cloud `P(N, D)` and find a grid shape and an index permutation `order` such that `Pgrid(I, J, …, D)` = P[order].reshape(*gridshape, D) is sorted along every axis of the grid. E.g. in 3D, for Pgrid = (x, y, z):
31
- ```text
32
- x[i+1, j, k] >= x[i, j, k]
33
- y[i, j+1, k] >= y[i, j, k]
34
- z[i, j, k+1] >= z[i, j, k]
35
- ```
36
-
37
- → On the `Pgrid` view of `P`, neighbor queries become a simple stencil look-up
38
- ```text
39
- neighborhood(Pgrid[i, j, k]) = {Pgrid[i±di, j±dj, k±dk] | (di, dj, dk) ≤ R},
40
- ````
41
- where `R` is a radius cutoff to determine, allowing local operations in O(N) time.
42
- → Geometric operations (convolution, clustering, interpolation …) can then be applied **in O(N) time** directly on the `Pgrid` view instead of relying on complex graph or point-cloud methods
43
-
44
- `P` can be a NumPy, PyTorch or CuPy array of any dimension (N, D). To allow natural padding when the grid has more cells than they are points in the cloud, NaNs and Infs are supported in a consistent manner:
45
- - nans → random position
46
- - (+-) infs → border of the grid
47
-
48
- This allow to gridsort prime or variable number of points, as long as one is ready to deal with void/special grid cells.
49
-
50
- Expected runtime for sorting 1 million points, tested on google colab GPU T4 for 2D/3D point clouds:
51
- - Numpy: 10s
52
- - Torch (gpu): 500 ms
53
- - Cupy (gpu): 1 s (after compilation e.g. not on the first cold call, which take 10 s)
54
-
55
- `When not to use gridpoints ?`
56
- - high dimension: the package is implemented to support arbitrary dimension, but sweetspot is really 2D/3D. dimensions 4-6 might still be reasonable depending on the task, but anything above 8D is generally too high dimensional for gridpoints.
57
- - weird geometries. Supported datasets goes beyond smooth convex distributions: map of Indonesia, a sponge, a donuts, an elephant, an eggshell (by specifying a suitable gridshape e.g. (128,128,2) to `gridpoints.sort()`). You can see various exemples in the [plot](https://github.com/Neighborhood-Grid/CubeNet/blob/main/plots) folder But with some limits. Bad fits: a spider web, a wind turbine, same eggshell with a naive 3d sort (gridshape = (32,32,32)) ... the issue is not that gridpoints cannot sort these distributions, but that it will produce a poor representation of the geometry.
58
- - small point clouds: beyond a few hundred points, local operations in O(N) time is not worth the overhead, because naive quadratic implementations will probably be simultaneously simpler and faster.
59
-
60
- ---
61
-
62
- ### Installation
63
-
64
- ```bash
65
- pip install gridpoints # core only
66
- pip install gridpoints[demo] # for the demonstration notebook.
67
- ```
68
- Full Demo: [notebook.ipynb](https://github.com/Neighborhood-Grid/CubeNet/blob/main/notebook.ipynb)
69
-
70
- ### Quickstart
71
-
72
- ```python
73
- import gridpoints as grid
74
- import numpy as np
75
-
76
- # Raw point cloud (numpy, pytorch or cupy)
77
- A = np.random.rand(1_000_000, 3)
78
-
79
- # Sorted view: place the points inside the grid
80
- order = grid.argsort(A, gridshape=(100, 100, 100))
81
- Bflat = A[order] #flat but sorted with a grid layout (C-order)
82
- Bgrid = Bflat.reshape(100, 100, 100, 3) #reshaped as a grid
83
-
84
- # Rest of your pipeline, working with grids
85
- Cgrid = apply_something(Bgrid) #(100, 100, 100, *)
86
-
87
- # Back to the original points indexing
88
- Cflat = Cgrid.reshape(1_000_000, *)
89
- orderinv = grid.invert_permutation(order)
90
- C = Cflat[orderinv] # matches the initial points order
91
- ```
92
-
93
- ---
94
-
95
- ### Note on rectangular gridshapes
96
-
97
- The eggshell example illustrates the importance of selecting a suitable grid-shape decomposition. This can itself be challenging when the point cloud is assumed to follow a complex topological pattern, such as a thin surface rather than a full volumetric domain. While the precise decomposition is not particularly important (e.g. (18,20,16) vs. (16,15,24) will generally make little difference), the orders of magnitude of the different dimensions do matter and should be tuned to the dataset: (18,20,16) and (36,40,4) can lead to substantially different results. As mentioned, nan/inf padding can help accomodate integer-factorization constraints
98
-
99
- Another possibility for smoothing out topology-specific effects is to perform the sort after randomly projecting the dataset onto a lower-dimensional subspace. Random projections are known to approximately preserve pairwise distances, as formalized by the Johnson–Lindenstrauss lemma.
100
-
101
-
102
- ### Note on the cutoff radius R
103
-
104
- There is no strict theoretical guarantee about what the cutof radius R should be for a given task. E.g the relative grid position between a point and its nearest neighbors can't be garanted to be in the exact adjacent grid cells. What is guaranteed from the sorted ordering is only **grid monotonicity**: *x* coordinates increase along rows, *y* coordinates along columns, and so on.
105
-
106
- As an example, empirical results in 2-D show that `R = 5` is enough for ~99 % of the nearest neighbors; some outlier neighbors will sit further apart for complex geometries with pronounced peaks, holes or any non-smoothness. When a stricter neighborhood is required, or in high dimensional setting, the solution might be to build an assembly of grid experts, each working on a rotated / projected view of the points, as discussed in [this topic](https://github.com/glotzerlab/freud/discussions/1417). An other possibility would be to tile the grid: each tile is enhanced with location metadata (e.g. a bounding box), allowing for pruning pairs of tiles with a distance certified to be far enough for a given criterion.
107
-
108
-
109
- ### Note on efficient stencil operations
110
-
111
- The typical use-case of `Gridpoints` is to allow fast local operations on arbitrary point clouds using stencil kernels:
112
-
113
- ```text
114
- output(i, j, k) = f( Pgrid[i±di, j±dj, k±dk] | di, dj, dk in local window )
115
- ```
116
- To go beyond standard (slow) python loops, this kind of kernel computation can be accelerated with native grid convolution operations of standard libraries whenever possible, or with `pystencils` or `taichi` compilers for complex/non O(N) grid kernels. On GPU, tiling the grid and leveraging a custom `triton` kernel might be particularly efficient.
File without changes
File without changes