spatial-graph 0.0.1__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,97 @@
1
+ // Copyright 2023 Joshua J Baker. All rights reserved.
2
+ // Use of this source code is governed by an MIT-style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ #ifndef RTREE_H
6
+ #define RTREE_H
7
+
8
+ #include <stdlib.h>
9
+ #include "config.h"
10
+
11
+ // rtree_new returns a new rtree
12
+ //
13
+ // Returns NULL if the system is out of memory.
14
+ struct rtree *rtree_new(void);
15
+
16
+
17
+ // rtree_new returns a new rtree using a custom allocator
18
+ //
19
+ // Returns NULL if the system is out of memory.
20
+ struct rtree *rtree_new_with_allocator(void *(*malloc)(size_t), void (*free)(void*));
21
+
22
+ // rtree_free frees an rtree
23
+ void rtree_free(struct rtree *tr);
24
+
25
+ // rtree_clone makes an instant copy of the btree.
26
+ //
27
+ // This operation uses shadowing / copy-on-write.
28
+ struct rtree *rtree_clone(struct rtree *tr);
29
+
30
+ // rtree_insert inserts an item into the rtree.
31
+ //
32
+ // This operation performs a copy of the data that is pointed to in the second
33
+ // and third arguments. The R-tree expects a rectangle, which is two arrays of
34
+ // coord_ts. The first N values as the minimum corner of the rect, and the next
35
+ // N values as the maximum corner of the rect, where N is the number of
36
+ // dimensions.
37
+ //
38
+ // When inserting points, the max coordinates is optional (set to NULL).
39
+ //
40
+ // Returns false if the system is out of memory.
41
+ bool rtree_insert(struct rtree *tr, const coord_t *min, const coord_t *max, const item_t item);
42
+
43
+
44
+ // rtree_search searches the rtree and iterates over each item that intersect
45
+ // the provided rectangle.
46
+ //
47
+ // Returning false from the iter will stop the search.
48
+ void rtree_search(const struct rtree *tr, const coord_t *min, const coord_t *max,
49
+ bool (*iter)(const coord_t *min, const coord_t *max, const item_t item, void *udata),
50
+ void *udata);
51
+
52
+ // Find the nearest neighbors to the given query point.
53
+ //
54
+ // Returning false from the iter will stop the search.
55
+ bool rtree_nearest(struct rtree *tr, const coord_t *point,
56
+ bool (*iter)(const item_t item, coord_t distance, void *udata),
57
+ void *udata);
58
+
59
+ // rtree_scan iterates over every item in the rtree.
60
+ //
61
+ // Returning false from the iter will stop the scan.
62
+ void rtree_scan(const struct rtree *tr,
63
+ bool (*iter)(const coord_t *min, const coord_t *max, const item_t item, void *udata),
64
+ void *udata);
65
+
66
+ // rtree_count returns the number of items in the rtree.
67
+ size_t rtree_count(const struct rtree *tr);
68
+
69
+ // query the total bounding box of the rtree
70
+ void rtree_bb(const struct rtree *tr, coord_t* min, coord_t* max);
71
+
72
+ // rtree_delete deletes an item from the rtree.
73
+ //
74
+ // This searches the tree for an item that is contained within the provided
75
+ // rectangle, and perform a binary comparison of its data to the provided
76
+ // data. The first item that is found is deleted.
77
+ //
78
+ // Returns the number of deleted items (0 or 1) or -1 if an OOM error occured.
79
+ int rtree_delete(struct rtree *tr, const coord_t *min, const coord_t *max, const item_t item);
80
+
81
+ // rtree_delete_with_comparator deletes an item from the rtree.
82
+ // This searches the tree for an item that is contained within the provided
83
+ // rectangle, and perform a comparison of its data to the provided data using
84
+ // a compare function. The first item that is found is deleted.
85
+ //
86
+ // Returns false if the system is out of memory.
87
+ bool rtree_delete_with_comparator(struct rtree *tr, const coord_t *min,
88
+ const coord_t *max, const item_t item,
89
+ int (*compare)(const item_t a, const item_t b, void *udata),
90
+ void *udata);
91
+
92
+ // rtree_opt_relaxed_atomics activates memory_order_relaxed for all atomic
93
+ // loads. This may increase performance for single-threaded programs.
94
+ // Optionally, define RTREE_NOATOMICS to disbale all atomics.
95
+ void rtree_opt_relaxed_atomics(struct rtree *tr);
96
+
97
+ #endif // RTREE_H
@@ -0,0 +1,356 @@
1
+ from libc.stdint cimport *
2
+ import numpy as np
3
+
4
+
5
+ ctypedef int bool
6
+
7
+ cdef extern from *:
8
+ """
9
+ typedef int bool;
10
+ #define false 0
11
+ #define true 1
12
+
13
+ %if $c_distance_function
14
+ #define KNN_USE_EXACT_DISTANCE
15
+ %end if
16
+ #define DIMS $dims
17
+
18
+ typedef $coord_dtype.to_pyxtype() coord_t;
19
+ typedef $item_dtype.base_c_type item_base_t;
20
+ %if $item_dtype.is_array
21
+ typedef item_base_t pyx_item_t[$item_dtype.size];
22
+ %else
23
+ typedef item_base_t pyx_item_t;
24
+ %end if
25
+ typedef pyx_item_t* pyx_items_t;
26
+
27
+ %if $c_item_t_declaration
28
+ $c_item_t_declaration
29
+ %else
30
+ %if $item_dtype.is_array
31
+ typedef struct item_t {
32
+ item_base_t data[$item_dtype.size];
33
+ } item_t;
34
+ %else
35
+ typedef item_base_t item_t;
36
+ %end if
37
+ %end if
38
+
39
+ %if $c_equal_function
40
+ $c_equal_function
41
+ %else
42
+ inline bool equal(const item_t a, const item_t b) {
43
+ %if $item_dtype.is_array
44
+ return memcmp(&a, &b, sizeof(item_t));
45
+ %else
46
+ return a == b;
47
+ %end if
48
+ }
49
+ %end if
50
+
51
+ #include "src/rtree.h"
52
+ #include "src/rtree.c"
53
+
54
+ %if $c_converter_functions
55
+ $c_converter_functions
56
+ %else
57
+ %if $item_dtype.is_array
58
+ inline item_t convert_pyx_to_c_item(pyx_item_t *pyx_item, coord_t *min, coord_t *max) {
59
+ item_t c_item;
60
+ memcpy(&c_item, *pyx_item, sizeof(item_t));
61
+ return c_item;
62
+ }
63
+ inline void copy_c_to_pyx_item(const item_t c_item, pyx_item_t *pyx_item) {
64
+ memcpy(pyx_item, &c_item, sizeof(item_t));
65
+ }
66
+ %else
67
+ // default PYX<->C converters, just casting
68
+ inline item_t convert_pyx_to_c_item(pyx_item_t *pyx_item, coord_t *min, coord_t *max) {
69
+ return (item_t)*pyx_item;
70
+ }
71
+ inline void copy_c_to_pyx_item(const item_t c_item, pyx_item_t *pyx_item) {
72
+ memcpy(pyx_item, &c_item, sizeof(item_t));
73
+ }
74
+ %end if
75
+ %end if
76
+
77
+ %if $c_distance_function
78
+ $c_distance_function
79
+ %end if
80
+ """
81
+ cdef enum:
82
+ DIMS = $dims
83
+ ctypedef $coord_dtype.to_pyxtype() coord_t
84
+ ctypedef $item_dtype.base_c_type item_base_t
85
+ %if $item_dtype.is_array
86
+ ctypedef item_base_t pyx_item_t[$item_dtype.size]
87
+ %else
88
+ ctypedef item_base_t pyx_item_t
89
+ %end if
90
+ ctypedef pyx_item_t* pyx_items_t
91
+
92
+ %if $pyx_item_t_declaration
93
+ $pyx_item_t_declaration
94
+ %else
95
+ %if $item_dtype.is_array
96
+ # item_t can't be an array in rtree, arrays can't be assigned to (and this
97
+ # is needed inside rtree). So we make item_t a struct with field `data` to
98
+ # hold the array.
99
+ cdef struct item_t:
100
+ item_base_t data[$item_dtype.size]
101
+ %else
102
+ ctypedef item_base_t item_t
103
+ %end if
104
+ %end if
105
+
106
+ # PYX <-> C converters
107
+ cdef item_t convert_pyx_to_c_item(pyx_item_t *pyx_item, coord_t *min, coord_t* max)
108
+ cdef void copy_c_to_pyx_item(const item_t c_item, pyx_item_t *pyx_item)
109
+
110
+ # rtree API
111
+ cdef struct rtree
112
+ cdef rtree *rtree_new()
113
+ cdef void rtree_free(rtree *tr)
114
+ cdef bool rtree_insert(
115
+ rtree *tr,
116
+ const coord_t *min,
117
+ const coord_t *max,
118
+ const item_t item)
119
+ cdef void rtree_search(
120
+ const rtree *tr,
121
+ const coord_t *min,
122
+ const coord_t *max,
123
+ bool (*iter)(
124
+ const coord_t *min,
125
+ const coord_t *max,
126
+ const item_t item,
127
+ void *udata),
128
+ void *udata)
129
+ cdef bool rtree_nearest(
130
+ rtree *tr,
131
+ const coord_t *point,
132
+ bool (*iter)(
133
+ const item_t item,
134
+ coord_t distance,
135
+ void *udata),
136
+ void *udata)
137
+ cdef int rtree_delete(
138
+ rtree *tr,
139
+ const coord_t *min,
140
+ const coord_t *max,
141
+ const item_t item)
142
+ cdef size_t rtree_count(const rtree *tr)
143
+ cdef void rtree_bb(const rtree *tr, coord_t *min, coord_t *max)
144
+
145
+
146
+ cdef pyx_items_t memview_to_pyx_items_t($item_dtype.to_pyxtype(add_dim=True) items):
147
+ # implementation depends on dimension of item
148
+ %if $item_dtype.is_array
149
+ return <pyx_items_t>&items[0, 0]
150
+ %else
151
+ return <pyx_items_t>&items[0]
152
+ %end if
153
+
154
+
155
+ cdef bint count_iterator(
156
+ const coord_t* bb_min,
157
+ const coord_t* bb_max,
158
+ const item_t item,
159
+ void* udata
160
+ ) noexcept:
161
+
162
+ cdef size_t* count = <size_t*>udata
163
+ count[0] = count[0] + 1
164
+ return True
165
+
166
+
167
+ cdef struct search_results:
168
+ size_t size
169
+ pyx_items_t items
170
+
171
+
172
+ cdef init_search_results_from_memview(search_results* r, $item_dtype.to_pyxtype(add_dim=True) items):
173
+ r.size = 0
174
+ r.items = memview_to_pyx_items_t(items)
175
+
176
+
177
+ cdef bint search_iterator(
178
+ const coord_t* bb_min,
179
+ const coord_t* bb_max,
180
+ const item_t item,
181
+ void* udata
182
+ ) noexcept:
183
+
184
+ cdef search_results* results = <search_results*>udata
185
+ copy_c_to_pyx_item(item, &results.items[results.size])
186
+ results.size += 1
187
+ return True
188
+
189
+
190
+ cdef struct nearest_results:
191
+ size_t size
192
+ size_t max_size
193
+ pyx_items_t items
194
+ coord_t *distances
195
+
196
+
197
+ cdef init_nearest_results_from_memview(nearest_results* r,
198
+ $item_dtype.to_pyxtype(add_dim=True) items,
199
+ coord_t[::1] distances):
200
+ r.size = 0
201
+ r.max_size = len(items)
202
+ r.items = memview_to_pyx_items_t(items)
203
+ r.distances = &distances[0] if distances is not None else NULL
204
+
205
+
206
+ cdef bint nearest_iterator(
207
+ const item_t item,
208
+ coord_t distance,
209
+ void* udata
210
+ ) noexcept:
211
+
212
+ cdef nearest_results* results = <nearest_results*>udata
213
+ copy_c_to_pyx_item(item, &results.items[results.size])
214
+ if results.distances != NULL:
215
+ results.distances[results.size] = distance
216
+ results.size += 1
217
+ return results.size < results.max_size
218
+
219
+
220
+ cdef class RTree:
221
+
222
+ cdef rtree* _rtree
223
+
224
+ def __cinit__(self):
225
+ self._rtree = rtree_new()
226
+
227
+ def __dealloc__(self):
228
+ rtree_free(self._rtree)
229
+
230
+ def insert_point_items(
231
+ self,
232
+ $item_dtype.to_pyxtype(add_dim=True) items,
233
+ coord_t[:, ::1] points
234
+ ):
235
+
236
+ cdef pyx_items_t pyx_items = memview_to_pyx_items_t(items)
237
+
238
+ for i in range(len(items)):
239
+ rtree_insert(
240
+ self._rtree,
241
+ &points[i, 0],
242
+ NULL,
243
+ convert_pyx_to_c_item(&pyx_items[i], &points[i, 0], NULL))
244
+
245
+ def insert_bb_items(
246
+ self,
247
+ $item_dtype.to_pyxtype(add_dim=True) items,
248
+ coord_t[:, ::1] bb_mins,
249
+ coord_t[:, ::1] bb_maxs
250
+ ):
251
+
252
+ cdef pyx_items_t pyx_items = memview_to_pyx_items_t(items)
253
+
254
+ for i in range(len(items)):
255
+ rtree_insert(
256
+ self._rtree,
257
+ &bb_mins[i, 0],
258
+ &bb_maxs[i, 0],
259
+ convert_pyx_to_c_item(&pyx_items[i], &bb_mins[i, 0], &bb_maxs[i, 0]))
260
+
261
+ def count(self, coord_t[::1] bb_min, coord_t[::1] bb_max):
262
+
263
+ cdef size_t num = 0
264
+ rtree_search(
265
+ self._rtree,
266
+ &bb_min[0],
267
+ &bb_max[0],
268
+ &count_iterator,
269
+ &num)
270
+
271
+ return num
272
+
273
+ def bounding_box(self):
274
+ bb_min = np.empty(($dims,), dtype="$coord_dtype.base")
275
+ bb_max = np.empty(($dims,), dtype="$coord_dtype.base")
276
+ cdef coord_t[::1] _bb_min = bb_min
277
+ cdef coord_t[::1] _bb_max = bb_max
278
+ rtree_bb(self._rtree, &_bb_min[0], &_bb_max[0])
279
+ return (bb_min, bb_max)
280
+
281
+ def search(self, coord_t[::1] bb_min, coord_t[::1] bb_max):
282
+
283
+ cdef search_results results
284
+ cdef size_t num_results = self.count(bb_min, bb_max)
285
+
286
+ items = np.zeros((num_results, $item_dtype.size), dtype="$item_dtype.base")
287
+ if num_results == 0:
288
+ return items
289
+ init_search_results_from_memview(&results, items)
290
+
291
+ rtree_search(
292
+ self._rtree,
293
+ &bb_min[0],
294
+ &bb_max[0],
295
+ &search_iterator,
296
+ &results)
297
+
298
+ return items
299
+
300
+ def nearest(self, coord_t[::1] point, size_t k, return_distances=False):
301
+
302
+ cdef nearest_results results
303
+
304
+ items = np.zeros((k, $item_dtype.size), dtype="$item_dtype.base")
305
+ if return_distances:
306
+ distances = np.zeros((k,), dtype="$coord_dtype.base")
307
+ else:
308
+ distances = None
309
+ if k == 0:
310
+ return items
311
+ init_nearest_results_from_memview(&results, items, distances)
312
+
313
+ all_good = rtree_nearest(
314
+ self._rtree,
315
+ &point[0],
316
+ &nearest_iterator,
317
+ &results)
318
+
319
+ if not all_good:
320
+ raise RuntimeError("RTree nearest neighbor search ran out of memory.")
321
+
322
+ if return_distances:
323
+ return items[:results.size], distances[:results.size]
324
+ else:
325
+ return items[:results.size]
326
+
327
+ def delete_items(
328
+ self,
329
+ $item_dtype.to_pyxtype(add_dim=True) items,
330
+ coord_t[:, ::1] bb_mins,
331
+ coord_t[:, ::1] bb_maxs=None
332
+ ):
333
+
334
+ if bb_maxs is None:
335
+ bb_maxs = bb_mins
336
+
337
+ cdef pyx_items_t pyx_items = memview_to_pyx_items_t(items)
338
+
339
+ total_deleted = 0
340
+ for i in range(len(items)):
341
+ num_deleted = rtree_delete(
342
+ self._rtree,
343
+ &bb_mins[i, 0],
344
+ &bb_maxs[i, 0],
345
+ convert_pyx_to_c_item(&pyx_items[i], &bb_mins[i, 0], &bb_maxs[i, 0]))
346
+ if num_deleted == -1:
347
+ raise RuntimeError("RTree delete ran out of memory.")
348
+ if num_deleted == 0:
349
+ print(f"Item {pyx_items[i]} not deleted!")
350
+ total_deleted += num_deleted
351
+
352
+ return total_deleted
353
+
354
+ def __len__(self):
355
+
356
+ return rtree_count(self._rtree)
@@ -0,0 +1,95 @@
1
+ from .graph import Graph
2
+ from .rtree import PointRTree, LineRTree
3
+ from .dtypes import DType
4
+ import numpy as np
5
+
6
+
7
+ class SpatialGraph(Graph):
8
+ edge_inclusion_values = ["incident", "leaving", "entering"]
9
+
10
+ def __init__(
11
+ self,
12
+ ndims,
13
+ node_dtype,
14
+ node_attr_dtypes,
15
+ edge_attr_dtypes,
16
+ position_attr,
17
+ directed=False,
18
+ ):
19
+ if position_attr not in node_attr_dtypes:
20
+ raise ValueError(
21
+ f"position attribute {position_attr!r} not defined in 'node_attr_dtypes'"
22
+ )
23
+ super().__init__(node_dtype, node_attr_dtypes, edge_attr_dtypes, directed)
24
+
25
+ self.ndims = ndims
26
+ self.position_attr = position_attr
27
+ self.coord_dtype = DType(node_attr_dtypes[position_attr]).base
28
+ self._node_rtree = PointRTree(node_dtype, self.coord_dtype, ndims)
29
+ self._edge_rtree = LineRTree(f"{node_dtype}[2]", self.coord_dtype, ndims)
30
+
31
+ def add_node(self, node, **kwargs):
32
+ position = self._get_position(kwargs)
33
+ self._node_rtree.insert_point_item(node, position)
34
+ super().add_node(node, **kwargs)
35
+
36
+ def add_nodes(self, nodes, **kwargs):
37
+ positions = self._get_position(kwargs)
38
+ self._node_rtree.insert_point_items(nodes, positions)
39
+ super().add_nodes(nodes, **kwargs)
40
+
41
+ def add_edge(self, edge, **kwargs):
42
+ edge = np.array(edge, dtype=self.node_dtype)
43
+ position_u = getattr(self.node_attrs[edge[0]], self.position_attr)
44
+ position_v = getattr(self.node_attrs[edge[1]], self.position_attr)
45
+ self._edge_rtree.insert_line(edge, position_u, position_v)
46
+ super().add_edge(edge, **kwargs)
47
+
48
+ def add_edges(self, edges, **kwargs):
49
+ starts = getattr(self.node_attrs[edges[:, 0]], self.position_attr)
50
+ ends = getattr(self.node_attrs[edges[:, 1]], self.position_attr)
51
+ self._edge_rtree.insert_lines(edges, starts, ends)
52
+ super().add_edges(edges, **kwargs)
53
+
54
+ @property
55
+ def roi(self):
56
+ return self._node_rtree.bounding_box()
57
+
58
+ def query_nodes_in_roi(self, roi):
59
+ return self._node_rtree.search(roi[0], roi[1])
60
+
61
+ def query_edges_in_roi(self, roi):
62
+ return self._edge_rtree.search(roi[0], roi[1])
63
+
64
+ def query_nearest_nodes(self, point, k, return_distances=False):
65
+ return self._node_rtree.nearest(point, k, return_distances)
66
+
67
+ def query_nearest_edges(self, point, k, return_distances=False):
68
+ return self._edge_rtree.nearest(point, k, return_distances)
69
+
70
+ @property
71
+ def nodes(self):
72
+ return super().nodes()
73
+
74
+ @property
75
+ def edges(self):
76
+ return self.query_edges_in_roi(self.roi)
77
+
78
+ def remove_nodes(self, nodes):
79
+ positions = getattr(self.node_attrs[nodes], self.position_attr)
80
+ self._node_rtree.delete_items(nodes, positions)
81
+ if not self.directed:
82
+ edges = self.edges_by_nodes(nodes)
83
+ else:
84
+ edges = np.concatenate(
85
+ self.in_edges_by_nodes(nodes), self.out_edges_by_nodes(nodes)
86
+ )
87
+ positions_u = getattr(self.node_attrs[edges[:, 0]], self.position_attr)
88
+ positions_v = getattr(self.node_attrs[edges[:, 1]], self.position_attr)
89
+ self._edge_rtree.delete_items(edges, positions_u, positions_v)
90
+ super().remove_nodes(nodes)
91
+
92
+ def _get_position(self, kwargs):
93
+ if self.position_attr in kwargs:
94
+ return kwargs[self.position_attr]
95
+ raise RuntimeError(f"position attribute '{self.position_attr}' not given")
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: spatial-graph
3
+ Version: 0.0.1
4
+ Summary: A spatial graph datastructure for python.
5
+ Project-URL: homepage, https://github.com/funkelab/spatial_graph
6
+ Project-URL: repository, https://github.com/funkelab/spatial_graph
7
+ Author-email: Jan Funke <funkej@janelia.hhmi.org>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.9
11
+ Requires-Dist: cheetah3
12
+ Requires-Dist: numpy
13
+ Requires-Dist: setuptools>=75.8.0
14
+ Requires-Dist: witty>=v0.2.0
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.3.4; extra == 'dev'
17
+ Description-Content-Type: text/markdown
18
+
19
+ # spatial_graph
20
+
21
+ [![CI](https://github.com/funkelab/spatial_graph/actions/workflows/ci.yaml/badge.svg)](https://github.com/funkelab/spatial_graph/actions/workflows/ci.yaml)
22
+
23
+ `spatial_graph` provides a data structure for directed and undirected graphs,
24
+ where each node has an nD position (in time or space).
25
+
26
+ Design Principles
27
+ =================
28
+
29
+ Goals
30
+ -----
31
+
32
+ * support for arbitrary number of dimensions
33
+ * typed node identifiers and attributes
34
+ * any fixed-length type that is supported by `numpy`
35
+ * efficient node/edge queries by
36
+ * ROI
37
+ * kNN (by points / lines)
38
+ * numpy-like interface for efficient:
39
+ * graph population and manipulation
40
+ * query results
41
+ * attribute access
42
+ * minimal memory footprint
43
+ * minimal dependencies
44
+ * `cython` / `witty` / `cheetah3` for runtime compilation
45
+ * numpy for array interfaces
46
+ * PYX API for graph algorithms in C/C++
47
+
48
+ Non-Goals
49
+ ---------
50
+
51
+ * graph algorithms
52
+ * I/O
53
+ * non-typed arguments
54
+ * non-spatial graphs
55
+ * out-of-memory support
56
+ * networkx compatibility
57
+
58
+ Python API
59
+ ==========
60
+
61
+ Graph creation:
62
+
63
+ ```python
64
+ graph = sg.SpatialGraph(
65
+ ndims=3,
66
+ node_dtype="uint64",
67
+ node_attr_dtypes={"position": "double[3]"},
68
+ edge_attr_dtypes={"score": "float32"},
69
+ position_attr="position",
70
+ directed=False,
71
+ )
72
+ ```
73
+
74
+ Adding nodes/edges:
75
+
76
+ ```python
77
+ graph.add_nodes(
78
+ np.array([1, 2, 3, 4, 5], dtype="uint64"),
79
+ position=np.array(
80
+ [
81
+ [0.1, 0.1, 0.1],
82
+ [0.2, 0.2, 0.2],
83
+ [0.3, 0.3, 0.3],
84
+ [0.4, 0.4, 0.4],
85
+ [0.5, 0.5, 0.5],
86
+ ],
87
+ dtype="double",
88
+ ),
89
+ )
90
+
91
+ graph.add_edges(
92
+ np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
93
+ score=np.array([0.2, 0.3, 0.4], dtype="float32"),
94
+ )
95
+ ```
96
+
97
+ Query nodes/edges in ROI:
98
+
99
+ ```python
100
+ # nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)
101
+ nodes = graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
102
+ edges = graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
103
+ ```
104
+
105
+ Query nodes/edges by position:
106
+
107
+ ```python
108
+ nodes = graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
109
+ edges = graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)
110
+ ```
111
+
112
+ Access node/edge attributes:
113
+
114
+ ```python
115
+ node_positions = graph.node_attrs[nodes].position
116
+ edge_scores = graph.edge_attrs[edges].score
117
+ ```
118
+
119
+ Delete nodes/edges:
120
+
121
+ ```python
122
+ graph.remove_nodes(nodes[:1000])
123
+ ```
124
+
125
+ Implementation Details
126
+ ======================
127
+
128
+ A `SpatialGraph` consists of three data structures:
129
+ * The `Graph` itself, holding nodes, edges, and their attributes ([graphlite](https://github.com/haasdo95/graphlite)).
130
+ * Two R-trees for spatial node and edge queries (based on [rtree.c](https://github.com/tidwall/rtree.c)).
@@ -0,0 +1,22 @@
1
+ spatial_graph/__init__.py,sha256=pxk-5VDpsCozjvu7hxBODsfOktnslCoHz2ZDFkMjgj4,358
2
+ spatial_graph/dtypes.py,sha256=doDBC6ytT0maH8xL4mxDGhZj_MaTGWDoismuKA7zxDw,4110
3
+ spatial_graph/spatial_graph.py,sha256=yFf_7fucdwPBSVY0NvRZQAbK4hr8q3Gqzx16spl3Qzg,3528
4
+ spatial_graph/graph/__init__.py,sha256=IiYvwf3SFD6a8CM-EuVPf-ljAAQp-iFyjIx3bMxvuAs,46
5
+ spatial_graph/graph/graph.py,sha256=yrHiWu_MwAjksQQ_JNg5wz32RUig-uDfPDfrr0jE_Ho,8050
6
+ spatial_graph/graph/wrapper_template.pyx,sha256=P9yXWFKOm0cF8Gyc_arMaGHIPHKItOmWf3SLymY2sMo,20065
7
+ spatial_graph/graph/src/LICENSE.txt,sha256=VGv2QnX5wQ9uezPVb1HoLKPGpwSa3gN3TnCH16moz6k,1050
8
+ spatial_graph/graph/src/graph_lite.h,sha256=DYQbILPOyfHRJK8Bo5uuQ2ST1o99axWHLZLhqcXuxQQ,67913
9
+ spatial_graph/rtree/__init__.py,sha256=WQlRnGUdl0PqBuce0tetIRHAEjegH3QW9ZUTD7420KQ,110
10
+ spatial_graph/rtree/line_rtree.py,sha256=tx31eCzULcJ35N1XEu4448p8qLylv_zuNFs8bn3kFEQ,4260
11
+ spatial_graph/rtree/point_rtree.py,sha256=o5acJoSFYWNoJGTBFhGs24zIK4bVHUgRgKxmTVs_RxQ,102
12
+ spatial_graph/rtree/rtree.py,sha256=sR2L7f0hhpk9HLJ8xLMGM6FmYGENfyd13RuhOq77NDE,4238
13
+ spatial_graph/rtree/wrapper_template.pyx,sha256=FVooO-EE8oxDvnhTtDOkvYH3FYGJuAQNpl_KArYabhs,9683
14
+ spatial_graph/rtree/src/ARCHITECTURE.md,sha256=TwQruizoeGjjcdxhz_H-a9jh3XvvRG7QfouSvdEKt6o,4712
15
+ spatial_graph/rtree/src/LICENSE,sha256=5eZvRGXb-y4p7Wev7djIMtc_q-5j_SdL6pjbNNF-lUE,1081
16
+ spatial_graph/rtree/src/config.h,sha256=65s3NLcvpBXGgnRYjigWs4cExN5T6gm7Vu7CsxDvD08,348
17
+ spatial_graph/rtree/src/rtree.c,sha256=ykXltnXOVx4Rqxgu0Yq2ATZWVk_4k6RZNgp417vQN9g,25135
18
+ spatial_graph/rtree/src/rtree.h,sha256=melC2Fl6o-72c0I6S4Z22op2U6NpnA6hgGK81GT1hI0,3663
19
+ spatial_graph-0.0.1.dist-info/METADATA,sha256=G6lm2a7WnRjmBabfPJ66Oh4Qvfho8uUEokovWuafalk,3284
20
+ spatial_graph-0.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
21
+ spatial_graph-0.0.1.dist-info/licenses/LICENSE,sha256=51KA-SVg7xhkZxV512EmrwJninM0t0h_UqqDf653NSk,1066
22
+ spatial_graph-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any