spatial-graph 0.0.1__tar.gz → 0.0.3__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.
- spatial_graph-0.0.3/.github/workflows/ci.yml +107 -0
- spatial_graph-0.0.3/.github/workflows/docs.yaml +29 -0
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/.gitignore +5 -0
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/.pre-commit-config.yaml +3 -3
- spatial_graph-0.0.3/Dockerfile +38 -0
- spatial_graph-0.0.3/PKG-INFO +187 -0
- spatial_graph-0.0.3/README.md +162 -0
- spatial_graph-0.0.1/README.md → spatial_graph-0.0.3/docs/index.md +23 -45
- spatial_graph-0.0.3/examples/basic_usage.py +141 -0
- spatial_graph-0.0.3/examples/query_nearest_vispy.py +122 -0
- spatial_graph-0.0.3/mkdocs.yml +44 -0
- spatial_graph-0.0.3/pyproject.toml +104 -0
- spatial_graph-0.0.3/src/spatial_graph/__init__.py +24 -0
- spatial_graph-0.0.3/src/spatial_graph/_dtypes.py +141 -0
- spatial_graph-0.0.3/src/spatial_graph/_graph/__init__.py +3 -0
- spatial_graph-0.0.3/src/spatial_graph/_graph/cgraph.pyi +388 -0
- spatial_graph-0.0.3/src/spatial_graph/_graph/graph.py +391 -0
- {spatial_graph-0.0.1/spatial_graph/graph → spatial_graph-0.0.3/src/spatial_graph/_graph}/wrapper_template.pyx +6 -3
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/__init__.py +2 -3
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/line_rtree.py +28 -32
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/rtree.py +117 -5
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/wrapper_template.pyx +2 -2
- spatial_graph-0.0.1/spatial_graph/spatial_graph.py → spatial_graph-0.0.3/src/spatial_graph/_spatial_graph.py +47 -27
- spatial_graph-0.0.3/src/spatial_graph/_util.py +101 -0
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/tests/test_attributes.py +6 -6
- spatial_graph-0.0.3/tests/test_bench.py +85 -0
- spatial_graph-0.0.3/tests/test_dtype.py +39 -0
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/tests/test_graph.py +21 -32
- spatial_graph-0.0.3/tests/test_invalid_inputs.py +50 -0
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/tests/test_rtree.py +2 -1
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/tests/test_spatial_graph.py +18 -25
- spatial_graph-0.0.1/.cruft.json +0 -20
- spatial_graph-0.0.1/.github/workflows/ci.yaml +0 -80
- spatial_graph-0.0.1/PKG-INFO +0 -130
- spatial_graph-0.0.1/pyproject.toml +0 -32
- spatial_graph-0.0.1/spatial_graph/__init__.py +0 -15
- spatial_graph-0.0.1/spatial_graph/dtypes.py +0 -130
- spatial_graph-0.0.1/spatial_graph/graph/__init__.py +0 -3
- spatial_graph-0.0.1/spatial_graph/graph/graph.py +0 -226
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/LICENSE +0 -0
- {spatial_graph-0.0.1/spatial_graph/graph → spatial_graph-0.0.3/src/spatial_graph/_graph}/src/LICENSE.txt +0 -0
- {spatial_graph-0.0.1/spatial_graph/graph → spatial_graph-0.0.3/src/spatial_graph/_graph}/src/graph_lite.h +0 -0
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/point_rtree.py +0 -0
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/src/ARCHITECTURE.md +0 -0
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/src/LICENSE +0 -0
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/src/config.h +0 -0
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/src/rtree.c +0 -0
- {spatial_graph-0.0.1/spatial_graph/rtree → spatial_graph-0.0.3/src/spatial_graph/_rtree}/src/rtree.h +0 -0
- {spatial_graph-0.0.1 → spatial_graph-0.0.3}/tests/test_assert.py +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main]
|
|
6
|
+
push:
|
|
7
|
+
branches: [main]
|
|
8
|
+
tags: [v*]
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
lint:
|
|
17
|
+
name: Lint
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- name: Run pre-commit
|
|
22
|
+
run: pipx run pre-commit run --all-files
|
|
23
|
+
|
|
24
|
+
test:
|
|
25
|
+
runs-on: ${{ matrix.os }}
|
|
26
|
+
strategy:
|
|
27
|
+
fail-fast: false
|
|
28
|
+
matrix:
|
|
29
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
30
|
+
python-version: ["3.9", "3.11", "3.13"]
|
|
31
|
+
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v2
|
|
34
|
+
- uses: astral-sh/setup-uv@v6
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python-version }}
|
|
37
|
+
enable-cache: true
|
|
38
|
+
cache-dependency-glob: "**/pyproject.toml"
|
|
39
|
+
- name: Test with coverage
|
|
40
|
+
run: uv run pytest -v --cov=spatial_graph --cov-report=xml
|
|
41
|
+
- uses: codecov/codecov-action@v5
|
|
42
|
+
with:
|
|
43
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
44
|
+
|
|
45
|
+
benchmarks:
|
|
46
|
+
runs-on: ubuntu-latest
|
|
47
|
+
env:
|
|
48
|
+
UV_NO_SYNC: "1"
|
|
49
|
+
steps:
|
|
50
|
+
- uses: actions/checkout@v4
|
|
51
|
+
- uses: astral-sh/setup-uv@v6
|
|
52
|
+
with:
|
|
53
|
+
python-version: "3.13"
|
|
54
|
+
enable-cache: true
|
|
55
|
+
|
|
56
|
+
- name: install
|
|
57
|
+
run: uv sync --no-dev --group test-codspeed
|
|
58
|
+
|
|
59
|
+
- name: Run benchmarks
|
|
60
|
+
uses: CodSpeedHQ/action@v3
|
|
61
|
+
with:
|
|
62
|
+
run: uv run pytest -W ignore --codspeed -v --color=yes
|
|
63
|
+
|
|
64
|
+
deploy:
|
|
65
|
+
name: Deploy
|
|
66
|
+
needs: test
|
|
67
|
+
if: success() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'schedule'
|
|
68
|
+
runs-on: ubuntu-latest
|
|
69
|
+
|
|
70
|
+
permissions:
|
|
71
|
+
id-token: write
|
|
72
|
+
contents: write
|
|
73
|
+
|
|
74
|
+
steps:
|
|
75
|
+
- uses: actions/checkout@v4
|
|
76
|
+
with:
|
|
77
|
+
fetch-depth: 0
|
|
78
|
+
- uses: astral-sh/setup-uv@v6
|
|
79
|
+
with:
|
|
80
|
+
python-version: ${{ matrix.python-version }}
|
|
81
|
+
enable-cache: true
|
|
82
|
+
cache-dependency-glob: "**/pyproject.toml"
|
|
83
|
+
|
|
84
|
+
- name: 👷 Build
|
|
85
|
+
run: uv build
|
|
86
|
+
|
|
87
|
+
- name: 🚢 Publish to PyPI
|
|
88
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
89
|
+
with:
|
|
90
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
91
|
+
|
|
92
|
+
- uses: softprops/action-gh-release@v2
|
|
93
|
+
with:
|
|
94
|
+
generate_release_notes: true
|
|
95
|
+
files: "./dist/*"
|
|
96
|
+
|
|
97
|
+
docker-test:
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
|
|
100
|
+
steps:
|
|
101
|
+
- name: Checkout code
|
|
102
|
+
uses: actions/checkout@v4
|
|
103
|
+
|
|
104
|
+
- name: Build and test Docker image
|
|
105
|
+
run: |
|
|
106
|
+
docker build -t spatial_graph .
|
|
107
|
+
docker run --rm spatial_graph
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Deploy Documentation
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
concurrency:
|
|
9
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
10
|
+
cancel-in-progress: true
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: write
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
docs:
|
|
17
|
+
name: Deploy Documentation
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
- name: Configure Git Credentials
|
|
24
|
+
run: |
|
|
25
|
+
git config user.name github-actions[bot]
|
|
26
|
+
git config user.email github-actions[bot]@users.noreply.github.com
|
|
27
|
+
- uses: astral-sh/setup-uv@v6
|
|
28
|
+
- name: Deploy documentation
|
|
29
|
+
run: uv run --group docs mkdocs gh-deploy --strict --force
|
|
@@ -15,13 +15,13 @@ repos:
|
|
|
15
15
|
- id: check-added-large-files
|
|
16
16
|
|
|
17
17
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
18
|
-
rev: v0.
|
|
18
|
+
rev: v0.12.2
|
|
19
19
|
hooks:
|
|
20
|
-
- id: ruff
|
|
20
|
+
- id: ruff-check
|
|
21
21
|
args: [--fix, --unsafe-fixes]
|
|
22
22
|
- id: ruff-format
|
|
23
23
|
|
|
24
24
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
25
|
-
rev: v1.
|
|
25
|
+
rev: v1.16.1
|
|
26
26
|
hooks:
|
|
27
27
|
- id: mypy
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
FROM ubuntu:24.04
|
|
2
|
+
|
|
3
|
+
ENV DEBIAN_FRONTEND=noninteractive \
|
|
4
|
+
MAMBA_ROOT_PREFIX=/opt/conda \
|
|
5
|
+
PATH=/opt/conda/bin:$PATH
|
|
6
|
+
|
|
7
|
+
RUN apt-get update && \
|
|
8
|
+
apt-get install -y --no-install-recommends \
|
|
9
|
+
git curl tar bzip2 ca-certificates && \
|
|
10
|
+
rm -rf /var/lib/apt/lists/*
|
|
11
|
+
|
|
12
|
+
# auto-detect arch and grab the matching micromamba binary
|
|
13
|
+
RUN set -eux; \
|
|
14
|
+
arch="$(uname -m)"; \
|
|
15
|
+
case "$arch" in \
|
|
16
|
+
x86_64) url_arch=linux-64 ;; \
|
|
17
|
+
aarch64|arm64) url_arch=linux-aarch64 ;; \
|
|
18
|
+
ppc64le) url_arch=linux-ppc64le ;; \
|
|
19
|
+
*) echo "Unsupported arch: $arch"; exit 1 ;; \
|
|
20
|
+
esac; \
|
|
21
|
+
curl -Ls "https://micro.mamba.pm/api/micromamba/$url_arch/latest" \
|
|
22
|
+
| tar -xvj -C /usr/local/bin bin/micromamba; \
|
|
23
|
+
chmod +x /usr/local/bin/bin/micromamba; \
|
|
24
|
+
mv /usr/local/bin/bin/micromamba /usr/local/bin/micromamba; \
|
|
25
|
+
rmdir /usr/local/bin/bin
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
WORKDIR /app
|
|
29
|
+
COPY . /app
|
|
30
|
+
|
|
31
|
+
RUN micromamba create -y -n test-env -c conda-forge python=3.12 pip compilers
|
|
32
|
+
RUN micromamba run -n test-env pip install -e . --group test
|
|
33
|
+
|
|
34
|
+
SHELL ["micromamba", "run", "-n", "test-env", "/bin/bash", "-o", "pipefail", "-c"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# when you docker run, this will invoke pytest from inside test-env
|
|
38
|
+
CMD ["micromamba", "run", "-n", "test-env", "pytest", "-q", "-x"]
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spatial-graph
|
|
3
|
+
Version: 0.0.3
|
|
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>, Talley Lambert <talley.lambert@gmail.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Requires-Dist: ct3>=3.3.3
|
|
21
|
+
Requires-Dist: numpy
|
|
22
|
+
Requires-Dist: setuptools>=75.8.0
|
|
23
|
+
Requires-Dist: witty>=v0.2.1
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# spatial-graph
|
|
27
|
+
|
|
28
|
+
[](https://github.com/funkelab/spatial_graph/raw/main/LICENSE)
|
|
29
|
+
[](https://pypi.org/project/spatial-graph)
|
|
30
|
+
[](https://python.org)
|
|
31
|
+
[](https://github.com/funkelab/spatial_graph/actions/workflows/ci.yml)
|
|
32
|
+
[](https://codecov.io/gh/funkelab/spatial_graph)
|
|
33
|
+
[](https://codspeed.io/funkelab/spatial_graph)
|
|
34
|
+
|
|
35
|
+
`spatial_graph` provides a data structure for directed and undirected graphs,
|
|
36
|
+
where each node has an nD position (in time or space).
|
|
37
|
+
|
|
38
|
+
## Design Principles
|
|
39
|
+
|
|
40
|
+
### Goals
|
|
41
|
+
|
|
42
|
+
* support for arbitrary number of dimensions
|
|
43
|
+
* typed node identifiers and attributes
|
|
44
|
+
* any fixed-length type that is supported by `numpy`
|
|
45
|
+
* efficient node/edge queries by
|
|
46
|
+
* ROI
|
|
47
|
+
* kNN (by points / lines)
|
|
48
|
+
* numpy-like interface for efficient:
|
|
49
|
+
* graph population and manipulation
|
|
50
|
+
* query results
|
|
51
|
+
* attribute access
|
|
52
|
+
* minimal memory footprint
|
|
53
|
+
* minimal dependencies
|
|
54
|
+
* `cython` / `witty` / `cheetah3` for runtime compilation
|
|
55
|
+
* numpy for array interfaces
|
|
56
|
+
* PYX API for graph algorithms in C/C++
|
|
57
|
+
|
|
58
|
+
### Non-Goals
|
|
59
|
+
|
|
60
|
+
* graph algorithms
|
|
61
|
+
* I/O
|
|
62
|
+
* non-typed arguments
|
|
63
|
+
* non-spatial graphs
|
|
64
|
+
* out-of-memory support
|
|
65
|
+
* networkx compatibility
|
|
66
|
+
|
|
67
|
+
## Python API
|
|
68
|
+
|
|
69
|
+
Graph creation:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
graph = sg.SpatialGraph(
|
|
73
|
+
ndims=3,
|
|
74
|
+
node_dtype="uint64",
|
|
75
|
+
node_attr_dtypes={"position": "double[3]"},
|
|
76
|
+
edge_attr_dtypes={"score": "float32"},
|
|
77
|
+
position_attr="position",
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Adding nodes/edges:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
graph.add_nodes(
|
|
85
|
+
np.array([1, 2, 3, 4, 5], dtype="uint64"),
|
|
86
|
+
position=np.array(
|
|
87
|
+
[
|
|
88
|
+
[0.1, 0.1, 0.1],
|
|
89
|
+
[0.2, 0.2, 0.2],
|
|
90
|
+
[0.3, 0.3, 0.3],
|
|
91
|
+
[0.4, 0.4, 0.4],
|
|
92
|
+
[0.5, 0.5, 0.5],
|
|
93
|
+
],
|
|
94
|
+
dtype="double",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
graph.add_edges(
|
|
99
|
+
np.array([[1, 2], [3, 4], [5, 1]], dtype="uint64"),
|
|
100
|
+
score=np.array([0.2, 0.3, 0.4], dtype="float32"),
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Query nodes/edges in ROI:
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
# nodes/edges will be numpy arrays of dtype uint64 and shape (n,)/(n, 2)
|
|
108
|
+
nodes = graph.query_nodes_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
|
|
109
|
+
edges = graph.query_edges_in_roi(np.array([[0.0, 0.0, 0.0], [0.25, 0.25, 0.25]]))
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Query nodes/edges by position:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
nodes = graph.query_nearest_nodes(np.array([0.3, 0.3, 0.3]), k=3)
|
|
116
|
+
edges = graph.query_nearest_edges(np.array([0.3, 0.3, 0.3]), k=3)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Access node/edge attributes:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
node_positions = graph.node_attrs[nodes].position
|
|
123
|
+
edge_scores = graph.edge_attrs[edges].score
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Delete nodes/edges:
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
graph.remove_nodes(nodes[:1000])
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Implementation Details
|
|
133
|
+
|
|
134
|
+
A `SpatialGraph` consists of three data structures:
|
|
135
|
+
|
|
136
|
+
* The `Graph` itself, holding nodes, edges, and their attributes
|
|
137
|
+
([graphlite](https://github.com/haasdo95/graphlite)).
|
|
138
|
+
* Two R-trees for spatial node and edge queries (based on
|
|
139
|
+
[rtree.c](https://github.com/tidwall/rtree.c)). We modified the original code
|
|
140
|
+
to also include a fast kNN search.
|
|
141
|
+
|
|
142
|
+
## Cross-Platform Support
|
|
143
|
+
|
|
144
|
+
`spatial_graph` compiles C/C++ code at runtime, and as such needs access to a
|
|
145
|
+
compiler. If you already have one, great! You can use the PyPI package.
|
|
146
|
+
|
|
147
|
+
If you (or your users) don't have a compiler installed, you either need to
|
|
148
|
+
|
|
149
|
+
1. Install a compiler. This might be weird for non-technical users.
|
|
150
|
+
2. Install `spatial_graph` from `conda-forge`, where we include a compiler
|
|
151
|
+
(`clang`) in its dependencies.
|
|
152
|
+
|
|
153
|
+
### Why is this so complicated?
|
|
154
|
+
|
|
155
|
+
There is no cross-platform C/C++ compiler that we can install using `pip`.
|
|
156
|
+
[`numba`](https://github.com/numba/numba) is maybe the closest to having solved
|
|
157
|
+
that problem: `numba` does compile during runtime even if you don't have a
|
|
158
|
+
compiler locally installed. This works because `numba` is generating LLVM IR,
|
|
159
|
+
an intermediate representation language that LLVM can compile into machine
|
|
160
|
+
code. `numba` depends on [`llvmlite`](https://github.com/numba/llvmlite), which
|
|
161
|
+
provides a subset of the LLVM API, statically linked into the binaries in that
|
|
162
|
+
package. This is just enough to compile the `numba` generated LLVM IR into
|
|
163
|
+
machine code. We can't use this strategy, because we compile general C/C++
|
|
164
|
+
code. Converting that into LLVM IR is exactly what we need a compiler for.
|
|
165
|
+
|
|
166
|
+
## For Developers
|
|
167
|
+
|
|
168
|
+
To create a new release, tag the current commit with a
|
|
169
|
+
version number and push it to the `upstream` remote:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
git tag -a "vX.Y.Z" -m "vX.Y.Z"
|
|
173
|
+
git push upstream --follow-tags
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
This will trigger the CI workflow, which will build the package and upload it to PyPI.
|
|
177
|
+
|
|
178
|
+
### Testing in a conda environment
|
|
179
|
+
|
|
180
|
+
To simulate a naive user environment, with *no* assumptions made about the
|
|
181
|
+
availability of a C/C++ compiler, you can run the included Dockerfile
|
|
182
|
+
(where the key part of the conda env is the `compilers` package):
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
docker build -t spatial_graph .
|
|
186
|
+
docker run --rm spatial_graph
|
|
187
|
+
```
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# spatial-graph
|
|
2
|
+
|
|
3
|
+
[](https://github.com/funkelab/spatial_graph/raw/main/LICENSE)
|
|
4
|
+
[](https://pypi.org/project/spatial-graph)
|
|
5
|
+
[](https://python.org)
|
|
6
|
+
[](https://github.com/funkelab/spatial_graph/actions/workflows/ci.yml)
|
|
7
|
+
[](https://codecov.io/gh/funkelab/spatial_graph)
|
|
8
|
+
[](https://codspeed.io/funkelab/spatial_graph)
|
|
9
|
+
|
|
10
|
+
`spatial_graph` provides a data structure for directed and undirected graphs,
|
|
11
|
+
where each node has an nD position (in time or space).
|
|
12
|
+
|
|
13
|
+
## Design Principles
|
|
14
|
+
|
|
15
|
+
### Goals
|
|
16
|
+
|
|
17
|
+
* support for arbitrary number of dimensions
|
|
18
|
+
* typed node identifiers and attributes
|
|
19
|
+
* any fixed-length type that is supported by `numpy`
|
|
20
|
+
* efficient node/edge queries by
|
|
21
|
+
* ROI
|
|
22
|
+
* kNN (by points / lines)
|
|
23
|
+
* numpy-like interface for efficient:
|
|
24
|
+
* graph population and manipulation
|
|
25
|
+
* query results
|
|
26
|
+
* attribute access
|
|
27
|
+
* minimal memory footprint
|
|
28
|
+
* minimal dependencies
|
|
29
|
+
* `cython` / `witty` / `cheetah3` for runtime compilation
|
|
30
|
+
* numpy for array interfaces
|
|
31
|
+
* PYX API for graph algorithms in C/C++
|
|
32
|
+
|
|
33
|
+
### Non-Goals
|
|
34
|
+
|
|
35
|
+
* graph algorithms
|
|
36
|
+
* I/O
|
|
37
|
+
* non-typed arguments
|
|
38
|
+
* non-spatial graphs
|
|
39
|
+
* out-of-memory support
|
|
40
|
+
* networkx compatibility
|
|
41
|
+
|
|
42
|
+
## Python API
|
|
43
|
+
|
|
44
|
+
Graph creation:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
graph = sg.SpatialGraph(
|
|
48
|
+
ndims=3,
|
|
49
|
+
node_dtype="uint64",
|
|
50
|
+
node_attr_dtypes={"position": "double[3]"},
|
|
51
|
+
edge_attr_dtypes={"score": "float32"},
|
|
52
|
+
position_attr="position",
|
|
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
|
+
A `SpatialGraph` consists of three data structures:
|
|
110
|
+
|
|
111
|
+
* The `Graph` itself, holding nodes, edges, and their attributes
|
|
112
|
+
([graphlite](https://github.com/haasdo95/graphlite)).
|
|
113
|
+
* Two R-trees for spatial node and edge queries (based on
|
|
114
|
+
[rtree.c](https://github.com/tidwall/rtree.c)). We modified the original code
|
|
115
|
+
to also include a fast kNN search.
|
|
116
|
+
|
|
117
|
+
## Cross-Platform Support
|
|
118
|
+
|
|
119
|
+
`spatial_graph` compiles C/C++ code at runtime, and as such needs access to a
|
|
120
|
+
compiler. If you already have one, great! You can use the PyPI package.
|
|
121
|
+
|
|
122
|
+
If you (or your users) don't have a compiler installed, you either need to
|
|
123
|
+
|
|
124
|
+
1. Install a compiler. This might be weird for non-technical users.
|
|
125
|
+
2. Install `spatial_graph` from `conda-forge`, where we include a compiler
|
|
126
|
+
(`clang`) in its dependencies.
|
|
127
|
+
|
|
128
|
+
### Why is this so complicated?
|
|
129
|
+
|
|
130
|
+
There is no cross-platform C/C++ compiler that we can install using `pip`.
|
|
131
|
+
[`numba`](https://github.com/numba/numba) is maybe the closest to having solved
|
|
132
|
+
that problem: `numba` does compile during runtime even if you don't have a
|
|
133
|
+
compiler locally installed. This works because `numba` is generating LLVM IR,
|
|
134
|
+
an intermediate representation language that LLVM can compile into machine
|
|
135
|
+
code. `numba` depends on [`llvmlite`](https://github.com/numba/llvmlite), which
|
|
136
|
+
provides a subset of the LLVM API, statically linked into the binaries in that
|
|
137
|
+
package. This is just enough to compile the `numba` generated LLVM IR into
|
|
138
|
+
machine code. We can't use this strategy, because we compile general C/C++
|
|
139
|
+
code. Converting that into LLVM IR is exactly what we need a compiler for.
|
|
140
|
+
|
|
141
|
+
## For Developers
|
|
142
|
+
|
|
143
|
+
To create a new release, tag the current commit with a
|
|
144
|
+
version number and push it to the `upstream` remote:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git tag -a "vX.Y.Z" -m "vX.Y.Z"
|
|
148
|
+
git push upstream --follow-tags
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This will trigger the CI workflow, which will build the package and upload it to PyPI.
|
|
152
|
+
|
|
153
|
+
### Testing in a conda environment
|
|
154
|
+
|
|
155
|
+
To simulate a naive user environment, with *no* assumptions made about the
|
|
156
|
+
availability of a C/C++ compiler, you can run the included Dockerfile
|
|
157
|
+
(where the key part of the conda env is the `compilers` package):
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
docker build -t spatial_graph .
|
|
161
|
+
docker run --rm spatial_graph
|
|
162
|
+
```
|
|
@@ -1,44 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# spatial-graph
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`spatial_graph` provides a data structure for directed and undirected graphs,
|
|
3
|
+
`spatial-graph` provides a data structure for directed and undirected graphs,
|
|
6
4
|
where each node has an nD position (in time or space).
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Goals
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
==========
|
|
6
|
+
It leverages well-in-time compiled C++ code for efficient graph operations,
|
|
7
|
+
coupled with an rtree implementation for fast spatial queries.
|
|
8
|
+
|
|
9
|
+
## Goals
|
|
10
|
+
|
|
11
|
+
- Support for arbitrary number of dimensions
|
|
12
|
+
- Typed node identifiers and attributes
|
|
13
|
+
- Any fixed-length type that is supported by `numpy`
|
|
14
|
+
- Efficient node/edge queries by
|
|
15
|
+
- ROI
|
|
16
|
+
- kNN (by points / lines)
|
|
17
|
+
- numpy-like interface for efficient:
|
|
18
|
+
- Graph population and manipulation
|
|
19
|
+
- Query results
|
|
20
|
+
- Attribute access
|
|
21
|
+
- Minimal memory footprint
|
|
22
|
+
- Minimal dependencies
|
|
23
|
+
- PYX API for graph algorithms in C/C++
|
|
24
|
+
|
|
25
|
+
## Basic Usage
|
|
42
26
|
|
|
43
27
|
Graph creation:
|
|
44
28
|
|
|
@@ -49,7 +33,6 @@ graph = sg.SpatialGraph(
|
|
|
49
33
|
node_attr_dtypes={"position": "double[3]"},
|
|
50
34
|
edge_attr_dtypes={"score": "float32"},
|
|
51
35
|
position_attr="position",
|
|
52
|
-
directed=False,
|
|
53
36
|
)
|
|
54
37
|
```
|
|
55
38
|
|
|
@@ -104,9 +87,4 @@ Delete nodes/edges:
|
|
|
104
87
|
graph.remove_nodes(nodes[:1000])
|
|
105
88
|
```
|
|
106
89
|
|
|
107
|
-
|
|
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)).
|
|
90
|
+
See the [API documentation](./reference) for more details.
|