spatial-graph 0.0.1__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.
Files changed (31) hide show
  1. spatial_graph-0.0.1/.cruft.json +20 -0
  2. spatial_graph-0.0.1/.github/workflows/ci.yaml +80 -0
  3. spatial_graph-0.0.1/.gitignore +13 -0
  4. spatial_graph-0.0.1/.pre-commit-config.yaml +27 -0
  5. spatial_graph-0.0.1/LICENSE +21 -0
  6. spatial_graph-0.0.1/PKG-INFO +130 -0
  7. spatial_graph-0.0.1/README.md +112 -0
  8. spatial_graph-0.0.1/pyproject.toml +32 -0
  9. spatial_graph-0.0.1/spatial_graph/__init__.py +15 -0
  10. spatial_graph-0.0.1/spatial_graph/dtypes.py +130 -0
  11. spatial_graph-0.0.1/spatial_graph/graph/__init__.py +3 -0
  12. spatial_graph-0.0.1/spatial_graph/graph/graph.py +226 -0
  13. spatial_graph-0.0.1/spatial_graph/graph/src/LICENSE.txt +7 -0
  14. spatial_graph-0.0.1/spatial_graph/graph/src/graph_lite.h +1357 -0
  15. spatial_graph-0.0.1/spatial_graph/graph/wrapper_template.pyx +654 -0
  16. spatial_graph-0.0.1/spatial_graph/rtree/__init__.py +5 -0
  17. spatial_graph-0.0.1/spatial_graph/rtree/line_rtree.py +156 -0
  18. spatial_graph-0.0.1/spatial_graph/rtree/point_rtree.py +6 -0
  19. spatial_graph-0.0.1/spatial_graph/rtree/rtree.py +125 -0
  20. spatial_graph-0.0.1/spatial_graph/rtree/src/ARCHITECTURE.md +162 -0
  21. spatial_graph-0.0.1/spatial_graph/rtree/src/LICENSE +20 -0
  22. spatial_graph-0.0.1/spatial_graph/rtree/src/config.h +13 -0
  23. spatial_graph-0.0.1/spatial_graph/rtree/src/rtree.c +1021 -0
  24. spatial_graph-0.0.1/spatial_graph/rtree/src/rtree.h +97 -0
  25. spatial_graph-0.0.1/spatial_graph/rtree/wrapper_template.pyx +356 -0
  26. spatial_graph-0.0.1/spatial_graph/spatial_graph.py +95 -0
  27. spatial_graph-0.0.1/tests/test_assert.py +2 -0
  28. spatial_graph-0.0.1/tests/test_attributes.py +97 -0
  29. spatial_graph-0.0.1/tests/test_graph.py +244 -0
  30. spatial_graph-0.0.1/tests/test_rtree.py +262 -0
  31. spatial_graph-0.0.1/tests/test_spatial_graph.py +98 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "template": "https://github.com/adjavon/pycookie/",
3
+ "commit": "d7bd6b8521627b130001b270383b72bfed608ab3",
4
+ "checkout": null,
5
+ "context": {
6
+ "cookiecutter": {
7
+ "full_name": "Jan Funke",
8
+ "email": "funkej@janelia.hhmi.org",
9
+ "github_username": "funkelab",
10
+ "project_name": "spatial_graph",
11
+ "project_slug": "spatial_graph",
12
+ "project_short_description": "A spatial graph datastructure for python.",
13
+ "_copy_without_render": [
14
+ ".github/workflows/*"
15
+ ],
16
+ "_template": "https://github.com/adjavon/pycookie/"
17
+ }
18
+ },
19
+ "directory": null
20
+ }
@@ -0,0 +1,80 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+ push:
8
+ branches:
9
+ - main
10
+ tags: [v*]
11
+ workflow_dispatch:
12
+
13
+ concurrency:
14
+ group: ${{ github.workflow }}-${{ github.ref }}
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ lint:
19
+ name: Lint
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - name: Run pre-commit
24
+ run: pipx run pre-commit run --all-files
25
+
26
+ test:
27
+ runs-on: ${{ matrix.os }}
28
+ strategy:
29
+ fail-fast: false
30
+ matrix:
31
+ os: [ubuntu-latest, windows-latest, macos-latest]
32
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
33
+
34
+ steps:
35
+ - uses: actions/checkout@v2
36
+ - name: Set up Python ${{ matrix.python-version }}
37
+ uses: actions/setup-python@v4
38
+ with:
39
+ python-version: ${{ matrix.python-version }}
40
+ - name: Install dependencies
41
+ run: |
42
+ pip install ".[dev]"
43
+ - name: Test with pytest
44
+ run: |
45
+ pytest --color=yes -v tests
46
+
47
+ deploy:
48
+ name: Deploy
49
+ needs: test
50
+ if: success() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'schedule'
51
+ runs-on: ubuntu-latest
52
+
53
+ permissions:
54
+ id-token: write
55
+ contents: write
56
+
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ with:
60
+ fetch-depth: 0
61
+
62
+ - name: 🐍 Set up Python
63
+ uses: actions/setup-python@v5
64
+ with:
65
+ python-version: "3.x"
66
+
67
+ - name: 👷 Build
68
+ run: |
69
+ python -m pip install build
70
+ python -m build
71
+
72
+ - name: 🚢 Publish to PyPI
73
+ uses: pypa/gh-action-pypi-publish@release/v1
74
+ with:
75
+ password: ${{ secrets.PYPI_API_TOKEN }}
76
+
77
+ - uses: softprops/action-gh-release@v2
78
+ with:
79
+ generate_release_notes: true
80
+ files: "./dist/*"
@@ -0,0 +1,13 @@
1
+ *.sw[pmno]
2
+ *.pyc
3
+ *.egg-info
4
+ *.dat
5
+ *.lock
6
+ *.so
7
+ *_wrapper.cpp
8
+ *_wrapper.c
9
+ .coverage
10
+ build
11
+ dist
12
+ .vscode
13
+ docs/_build/
@@ -0,0 +1,27 @@
1
+ ci:
2
+ autoupdate_schedule: monthly
3
+ autofix_commit_msg: "style(pre-commit.ci): auto fixes [...]"
4
+ autoupdate_commit_msg: "ci(pre-commit.ci): autoupdate"
5
+
6
+ default_install_hook_types: [pre-commit, commit-msg]
7
+
8
+ repos:
9
+ - repo: https://github.com/pre-commit/pre-commit-hooks
10
+ rev: v5.0.0
11
+ hooks:
12
+ - id: trailing-whitespace
13
+ - id: end-of-file-fixer
14
+ - id: check-yaml
15
+ - id: check-added-large-files
16
+
17
+ - repo: https://github.com/astral-sh/ruff-pre-commit
18
+ rev: v0.7.2
19
+ hooks:
20
+ - id: ruff
21
+ args: [--fix, --unsafe-fixes]
22
+ - id: ruff-format
23
+
24
+ - repo: https://github.com/pre-commit/mirrors-mypy
25
+ rev: v1.13.0
26
+ hooks:
27
+ - id: mypy
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Funke lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -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,112 @@
1
+ # spatial_graph
2
+
3
+ [![CI](https://github.com/funkelab/spatial_graph/actions/workflows/ci.yaml/badge.svg)](https://github.com/funkelab/spatial_graph/actions/workflows/ci.yaml)
4
+
5
+ `spatial_graph` provides a data structure for directed and undirected graphs,
6
+ where each node has an nD position (in time or space).
7
+
8
+ Design Principles
9
+ =================
10
+
11
+ Goals
12
+ -----
13
+
14
+ * support for arbitrary number of dimensions
15
+ * typed node identifiers and attributes
16
+ * any fixed-length type that is supported by `numpy`
17
+ * efficient node/edge queries by
18
+ * ROI
19
+ * kNN (by points / lines)
20
+ * numpy-like interface for efficient:
21
+ * graph population and manipulation
22
+ * query results
23
+ * attribute access
24
+ * minimal memory footprint
25
+ * minimal dependencies
26
+ * `cython` / `witty` / `cheetah3` for runtime compilation
27
+ * numpy for array interfaces
28
+ * PYX API for graph algorithms in C/C++
29
+
30
+ Non-Goals
31
+ ---------
32
+
33
+ * graph algorithms
34
+ * I/O
35
+ * non-typed arguments
36
+ * non-spatial graphs
37
+ * out-of-memory support
38
+ * networkx compatibility
39
+
40
+ Python API
41
+ ==========
42
+
43
+ Graph creation:
44
+
45
+ ```python
46
+ graph = sg.SpatialGraph(
47
+ ndims=3,
48
+ node_dtype="uint64",
49
+ node_attr_dtypes={"position": "double[3]"},
50
+ edge_attr_dtypes={"score": "float32"},
51
+ position_attr="position",
52
+ directed=False,
53
+ )
54
+ ```
55
+
56
+ Adding nodes/edges:
57
+
58
+ ```python
59
+ graph.add_nodes(
60
+ np.array([1, 2, 3, 4, 5], dtype="uint64"),
61
+ position=np.array(
62
+ [
63
+ [0.1, 0.1, 0.1],
64
+ [0.2, 0.2, 0.2],
65
+ [0.3, 0.3, 0.3],
66
+ [0.4, 0.4, 0.4],
67
+ [0.5, 0.5, 0.5],
68
+ ],
69
+ dtype="double",
70
+ ),
71
+ )
72
+
73
+ graph.add_edges(
74
+ np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
75
+ score=np.array([0.2, 0.3, 0.4], dtype="float32"),
76
+ )
77
+ ```
78
+
79
+ Query nodes/edges in ROI:
80
+
81
+ ```python
82
+ # nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)
83
+ nodes = graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
84
+ edges = graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
85
+ ```
86
+
87
+ Query nodes/edges by position:
88
+
89
+ ```python
90
+ nodes = graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
91
+ edges = graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)
92
+ ```
93
+
94
+ Access node/edge attributes:
95
+
96
+ ```python
97
+ node_positions = graph.node_attrs[nodes].position
98
+ edge_scores = graph.edge_attrs[edges].score
99
+ ```
100
+
101
+ Delete nodes/edges:
102
+
103
+ ```python
104
+ graph.remove_nodes(nodes[:1000])
105
+ ```
106
+
107
+ Implementation Details
108
+ ======================
109
+
110
+ A `SpatialGraph` consists of three data structures:
111
+ * The `Graph` itself, holding nodes, edges, and their attributes ([graphlite](https://github.com/haasdo95/graphlite)).
112
+ * Two R-trees for spatial node and edge queries (based on [rtree.c](https://github.com/tidwall/rtree.c)).
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "spatial-graph"
3
+ version = "0.0.1"
4
+ description = "A spatial graph datastructure for python."
5
+ readme = "README.md"
6
+ requires-python = ">=3.9"
7
+ license = { text = "MIT" }
8
+ authors = [
9
+ { email = "funkej@janelia.hhmi.org", name = "Jan Funke" },
10
+ ]
11
+ dependencies = [
12
+ "witty>=v0.2.0",
13
+ "cheetah3",
14
+ "numpy",
15
+ "setuptools>=75.8.0",
16
+ ]
17
+
18
+ [project.urls]
19
+ homepage = "https://github.com/funkelab/spatial_graph"
20
+ repository = "https://github.com/funkelab/spatial_graph"
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ "pytest>=8.3.4",
25
+ ]
26
+
27
+ [tool.hatch.metadata]
28
+ allow-direct-references = true
29
+
30
+ [build-system]
31
+ requires = ["hatchling"]
32
+ build-backend = "hatchling.build"
@@ -0,0 +1,15 @@
1
+ from importlib.metadata import version, PackageNotFoundError
2
+
3
+ try:
4
+ __version__ = version("spatial_graph")
5
+ except PackageNotFoundError:
6
+ __version__ = "unknown"
7
+
8
+
9
+ from .rtree import PointRTree
10
+ from .rtree import LineRTree
11
+ from .graph import Graph
12
+ from .spatial_graph import SpatialGraph
13
+
14
+
15
+ __all__ = ["PointRTree", "LineRTree", "Graph", "SpatialGraph"]
@@ -0,0 +1,130 @@
1
+ import numpy as np
2
+
3
+
4
+ class DType:
5
+ def __init__(self, dtype_str):
6
+ self.as_string = dtype_str
7
+ self.is_array = self.__is_array(dtype_str)
8
+
9
+ if self.is_array:
10
+ self.base, self.size = self.__parse_array_dtype(dtype_str)
11
+ self.shape = (self.size,)
12
+ else:
13
+ self.base = dtype_str
14
+ self.size = None
15
+ self.shape = ()
16
+
17
+ def __is_array(self, dtype):
18
+ if "[" in dtype:
19
+ if "]" not in dtype:
20
+ raise RuntimeError(f"invalid array(?) dtype {dtype}")
21
+ return True
22
+ return False
23
+
24
+ def __parse_array_dtype(self, dtype):
25
+ dtype, size = dtype.split("[")
26
+ size = int(size.split("]")[0])
27
+
28
+ return dtype, size
29
+
30
+ @property
31
+ def base_c_type(self):
32
+ """Convert the base of this DType into the equivalent C/C++ type."""
33
+
34
+ if self.base == "float32" or self.base == "float":
35
+ return "float"
36
+ elif self.base == "float64" or self.base == "double":
37
+ return "double"
38
+ else:
39
+ # this might not work for all of them, this is just a fallback
40
+ return np.dtype(self.base).name + "_t"
41
+
42
+ def to_c_decl(self, name):
43
+ """Convert this dtype to the equivalent C/C++ declaration with the
44
+ given name:
45
+
46
+ "base_c_type name" if not an array
47
+ "base_c_type name[size]" if an array type
48
+ """
49
+ # is this an array type?
50
+ if self.is_array:
51
+ suffix = f"[{self.size}]"
52
+ else:
53
+ suffix = ""
54
+
55
+ return self.base_c_type + " " + name + suffix
56
+
57
+ def to_pyxtype(self, use_memory_view=False, add_dim=False):
58
+ """Convert this dtype to the equivalent PYX type.
59
+
60
+ "base_c_type"
61
+ "base_c_type[size]" if an array type
62
+ "base_c_type[::1]" if an array type and use_memory_view
63
+ "base_c_type[::1]" if not an array type and add_dim
64
+ "base_c_type[:, ::1]" if an array type and add_dim
65
+
66
+ Args:
67
+
68
+ use_memory_view:
69
+
70
+ If set, will produce "dtype[::1]" instead of "dtype[dim]" for
71
+ array types.
72
+
73
+ add_dim:
74
+
75
+ Append a dim to the type, e.g., "int32_t[::1]" instead of
76
+ "int32_t" for dtype "int32". If this DType is already an array,
77
+ will create a 2D array, e.g., "int32_t[:, ::1]".
78
+ """
79
+
80
+ # is this an array type?
81
+ if self.is_array:
82
+ if add_dim:
83
+ suffix = "[:, ::1]"
84
+ else:
85
+ if use_memory_view:
86
+ suffix = "[::1]"
87
+ else:
88
+ suffix = f"[{self.size}]"
89
+ else:
90
+ suffix = "[::1]" if add_dim else ""
91
+
92
+ return self.base_c_type + suffix
93
+
94
+ def to_rvalue(self, name, array_index=None):
95
+ """Convert this dtype into an r-value to be used in PYX files for
96
+ assignments.
97
+
98
+ "name" default
99
+ "name[array_index]" if array_index is given
100
+ "{name[0], ..., name[size-1]}"
101
+ if an array type
102
+ "{name[array_index, 0], ..., name[array_index, size-1]}"
103
+ if an array type and array_index is given
104
+ """
105
+
106
+ if self.is_array:
107
+ if array_index:
108
+ return (
109
+ "{"
110
+ + ", ".join(
111
+ [name + f"[{array_index}, {i}]" for i in range(self.size)]
112
+ )
113
+ + "}"
114
+ )
115
+ else:
116
+ return (
117
+ "{" + ", ".join([name + f"[{i}]" for i in range(self.size)]) + "}"
118
+ )
119
+ else:
120
+ if array_index:
121
+ return f"{name}[{array_index}]"
122
+ else:
123
+ return name
124
+
125
+
126
+ def dtypes_to_struct(struct_name, dtypes):
127
+ pyx_code = f"cdef struct {struct_name}:\n"
128
+ for name, dtype in dtypes.items():
129
+ pyx_code += f" {dtype.to_pyxtype()} {name}\n"
130
+ return pyx_code
@@ -0,0 +1,3 @@
1
+ from .graph import Graph
2
+
3
+ __all__ = ["Graph"]