spsheaf 0.2.0__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.
- spsheaf-0.2.0/LICENSE +21 -0
- spsheaf-0.2.0/MANIFEST.in +14 -0
- spsheaf-0.2.0/PKG-INFO +202 -0
- spsheaf-0.2.0/README.md +163 -0
- spsheaf-0.2.0/examples/quickstart.py +91 -0
- spsheaf-0.2.0/examples/tutorial.ipynb +595 -0
- spsheaf-0.2.0/pyproject.toml +74 -0
- spsheaf-0.2.0/setup.cfg +4 -0
- spsheaf-0.2.0/src/spsheaf/__init__.py +16 -0
- spsheaf-0.2.0/src/spsheaf/boundary_metrics.py +142 -0
- spsheaf-0.2.0/src/spsheaf/clustering.py +332 -0
- spsheaf-0.2.0/src/spsheaf/dimensionality.py +424 -0
- spsheaf-0.2.0/src/spsheaf/edge_scores.py +115 -0
- spsheaf-0.2.0/src/spsheaf/evaluation.py +609 -0
- spsheaf-0.2.0/src/spsheaf/fibers.py +326 -0
- spsheaf-0.2.0/src/spsheaf/graph.py +53 -0
- spsheaf-0.2.0/src/spsheaf/io.py +169 -0
- spsheaf-0.2.0/src/spsheaf/paths.py +105 -0
- spsheaf-0.2.0/src/spsheaf/pipeline.py +622 -0
- spsheaf-0.2.0/src/spsheaf/sheaf.py +345 -0
- spsheaf-0.2.0/src/spsheaf/viz/__init__.py +30 -0
- spsheaf-0.2.0/src/spsheaf/viz/benchmark.py +132 -0
- spsheaf-0.2.0/src/spsheaf/viz/boundaries.py +119 -0
- spsheaf-0.2.0/src/spsheaf/viz/dimensionality.py +424 -0
- spsheaf-0.2.0/src/spsheaf.egg-info/PKG-INFO +202 -0
- spsheaf-0.2.0/src/spsheaf.egg-info/SOURCES.txt +27 -0
- spsheaf-0.2.0/src/spsheaf.egg-info/dependency_links.txt +1 -0
- spsheaf-0.2.0/src/spsheaf.egg-info/requires.txt +25 -0
- spsheaf-0.2.0/src/spsheaf.egg-info/top_level.txt +1 -0
spsheaf-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025–2026 Eduardo Paluzo-Hidalgo, Yusuke Imoto
|
|
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,14 @@
|
|
|
1
|
+
include LICENSE README.md pyproject.toml
|
|
2
|
+
recursive-include src/spsheaf *.py
|
|
3
|
+
recursive-include examples quickstart.py tutorial.ipynb
|
|
4
|
+
prune experiments
|
|
5
|
+
prune benchmarks
|
|
6
|
+
prune figures
|
|
7
|
+
prune reported
|
|
8
|
+
prune tests
|
|
9
|
+
prune tools
|
|
10
|
+
prune docs
|
|
11
|
+
prune data
|
|
12
|
+
prune _staging
|
|
13
|
+
prune _to_delete
|
|
14
|
+
global-exclude __pycache__ *.py[cod] .DS_Store
|
spsheaf-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spsheaf
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: spSheaf: sheaf-theoretic boundary detection for segmentation-free spatial domain inference
|
|
5
|
+
Author: Eduardo Paluzo-Hidalgo, Yusuke Imoto
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/EduPH/spSheaf
|
|
8
|
+
Keywords: spatial transcriptomics,sheaf theory,domain inference,computational topology,MERFISH
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.21
|
|
19
|
+
Requires-Dist: pandas>=1.3
|
|
20
|
+
Requires-Dist: scipy>=1.7
|
|
21
|
+
Requires-Dist: scikit-learn>=1.0
|
|
22
|
+
Requires-Dist: matplotlib>=3.4
|
|
23
|
+
Requires-Dist: pyarrow>=10
|
|
24
|
+
Requires-Dist: anndata>=0.9
|
|
25
|
+
Requires-Dist: h5py>=3.0
|
|
26
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: flake8>=5.0; extra == "dev"
|
|
30
|
+
Provides-Extra: figures
|
|
31
|
+
Requires-Dist: pypdf>=3.9; extra == "figures"
|
|
32
|
+
Requires-Dist: pdfminer.six>=20221105; extra == "figures"
|
|
33
|
+
Requires-Dist: pillow>=9.0; extra == "figures"
|
|
34
|
+
Requires-Dist: pytest>=7.0; extra == "figures"
|
|
35
|
+
Provides-Extra: benchmark
|
|
36
|
+
Requires-Dist: ssam>=1.0; extra == "benchmark"
|
|
37
|
+
Requires-Dist: points2regions>=0.1; extra == "benchmark"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# spSheaf
|
|
41
|
+
|
|
42
|
+
Sheaf-theoretic spatial domain inference from raw molecules, without cell
|
|
43
|
+
segmentation. spSheaf places kernel-smoothed expression fibers on a grid,
|
|
44
|
+
gives each vertex a local principal-subspace stalk, scores every edge by its
|
|
45
|
+
*sheaf disagreement* — the residual mismatch of neighbouring local coordinates
|
|
46
|
+
after the stalks are aligned — and attenuates those transitions during
|
|
47
|
+
clustering. Paluzo-Hidalgo, Maehara & Imoto (2026), manuscript in preparation for publication.
|
|
48
|
+
|
|
49
|
+

|
|
50
|
+
|
|
51
|
+
*The construction on real data (MERFISH slice 0.19 of Moffitt et al. 2018, a
|
|
52
|
+
530 × 400 µm window; every quantity computed by spSheaf at the paper's
|
|
53
|
+
settings). Molecules and the 15 µm grid; the Gaussian-kernel fiber of one grid
|
|
54
|
+
point and one of its components as a smoothed field; the kNN graph; the stalks
|
|
55
|
+
standing over the vertices; one edge across an interface, its two stalks
|
|
56
|
+
aligned by the SVD of their overlap and the disagreement D(e) between the
|
|
57
|
+
transported coordinates; every edge scored; the smoothed boundary field over
|
|
58
|
+
the tissue; the domains.*
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install spsheaf # Python >= 3.9; numpy, scipy, scikit-learn, pandas
|
|
64
|
+
pip install -e . # from a clone, for the experiments and figures
|
|
65
|
+
pip install -r requirements-lock.txt # exact versions the results were produced on
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Quickstart
|
|
69
|
+
|
|
70
|
+
`examples/quickstart.py` downloads one public section — the SpaceTx BaristaSeq mouse visual cortex,
|
|
71
|
+
0.32 million molecules, 81 genes — runs the eight steps at the paper's
|
|
72
|
+
settings for it, and draws the domains next to the authors' layer
|
|
73
|
+
annotation. One minute on a laptop, no other data needed:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pip install spsheaf matplotlib
|
|
77
|
+
python examples/quickstart.py # writes examples/quickstart_domains.png; ARI 0.77 against the six layers
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+

|
|
81
|
+
|
|
82
|
+
`examples/tutorial.ipynb` is the same section step by step, with every
|
|
83
|
+
intermediate drawn (fibers, stalks, the disagreement field), what each
|
|
84
|
+
parameter does, the comparison with the annotation per layer, and how to
|
|
85
|
+
choose the number of domains with the silhouette when there is no
|
|
86
|
+
annotation. It runs in under a minute; the executed outputs are kept in the
|
|
87
|
+
notebook.
|
|
88
|
+
|
|
89
|
+
The whole method is eight function calls on a molecule table with columns
|
|
90
|
+
`x`, `y`, `gene` (any units; the parameters are in those units):
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from spsheaf import fibers, graph, sheaf, clustering
|
|
94
|
+
|
|
95
|
+
grid, raw, genes = fibers.build_kernel_fibers(mol, grid_spacing=15, bandwidth=25, x_col="x", y_col="y")
|
|
96
|
+
fib = fibers.normalize_fibers(raw) # 1 fibers on a grid
|
|
97
|
+
edges = graph.build_spatial_graph(grid, k=6) # 2 spatial graph
|
|
98
|
+
stalks = sheaf.compute_stalks(fib, grid, uniform_dim=6, k_nbrs=50) # 3 stalks: local principal subspaces
|
|
99
|
+
rs = sheaf.compute_restriction_maps(stalks, edges, 6) # 4 restriction maps by SVD alignment
|
|
100
|
+
b_raw, b_smooth = sheaf.compute_boundary_scores(fib, grid, stalks, edges, rs, smooth_radius=60) # 5-6
|
|
101
|
+
labels = clustering.boundary_aware_clustering(fib, grid, b_smooth, edges, n_clusters=8) # 7
|
|
102
|
+
labels, _ = clustering.spatial_cleanup(labels, edges, grid_coords=grid) # 8
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`spsheaf.pipeline.run_single_slice` wraps the eight steps and the evaluation
|
|
106
|
+
against an annotation. Parameters: grid spacing `δ`, kernel bandwidth `h`,
|
|
107
|
+
stalk dimension `d`, smoothing radius `σ_s`, boundary weight `α_b`, spatial
|
|
108
|
+
weight `α_s`, and the coordinate reference of the stalk sections
|
|
109
|
+
(`SPSHEAF_REFERENCE=tissue|local`, default `tissue`: coordinates referenced
|
|
110
|
+
to the section mean; `local` references them to the neighbourhood mean, and
|
|
111
|
+
is what the tuning grid selects on the MERFISH benchmark). The number of
|
|
112
|
+
domains `k` is an input; `experiments/merfish/k_selection.py` computes the
|
|
113
|
+
silhouette over a range of `k`, and the paper recommends selecting within the
|
|
114
|
+
range where it lies within 0.002 of its maximum. `docs/package_api.md` lists
|
|
115
|
+
the modules.
|
|
116
|
+
|
|
117
|
+
## Reproduce the paper
|
|
118
|
+
|
|
119
|
+
Everything below this line is the paper's reproduction material; `pip install spsheaf` ships only `src/spsheaf/`.
|
|
120
|
+
|
|
121
|
+
Point the code at the data (`data/README.md` lists every input and where it
|
|
122
|
+
comes from):
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
export SPSHEAF_DATA=/path/to/data # or copy paths.example.toml to paths.toml
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Rebuild the figures from the stored tables (minutes, no raw data needed
|
|
129
|
+
except for the panel dumps under `data/results`):
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
make figures && make figures-verify # 17 PDFs into figures/out/, checked against the 180 mm / 6-8 pt spec
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Check a number without running anything: every spSheaf table in the paper is
|
|
136
|
+
under `reported/` (`reported/README.md` names the claim and the command behind
|
|
137
|
+
each file; `reported/tissue/` holds the runs repeated at the tissue reference);
|
|
138
|
+
the competitors' tables and per-cell labels are under
|
|
139
|
+
`benchmarks/segfree/results/` (`results_union/` for the selection over both
|
|
140
|
+
references). `make reported-check` and `make reported-tissue-check` fail if
|
|
141
|
+
either has drifted from its results tree.
|
|
142
|
+
|
|
143
|
+
Re-run the experiments (hours; raw inputs required):
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
SPSHEAF_REFERENCE=local make merfish merfish-seeds merfish-loo-cv # Table 1, Fig 2b: five slices, 20 seeds, the 27-configuration grid
|
|
147
|
+
make merfish merfish-seeds merfish-loo-cv # the same at the default (tissue) reference; the two grids together are the 54-row union
|
|
148
|
+
make revision # ablation, boundary evaluation, k sweep (Fig 2c/d)
|
|
149
|
+
make osmfish-all osmfish-insample osmfish-insample-tissue # Fig 3, the 2 x 108 grid
|
|
150
|
+
make spacetx-all spacetx-ablation # both SpaceTx sections, 162-configuration grids, ED 3, Supplementary Fig 3
|
|
151
|
+
make matched-selection-union benchmark-table # one selection rule for all four methods (Table 1)
|
|
152
|
+
make k-selection merfish-k-selection merfish-k-split # choosing k: synthetic calibration, the five slices, the divisions at k = 9 (ED 4, Fig 4)
|
|
153
|
+
make help # everything else
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The grids are resumable: interrupt and re-run, only missing configurations
|
|
157
|
+
are computed. The competitor sweeps (SSAM, Points2Regions, FICTURE) are
|
|
158
|
+
reproducible from the tracked metric tables; re-running them needs the
|
|
159
|
+
competitors' inputs, which `python -m benchmarks.segfree.inputs` names.
|
|
160
|
+
|
|
161
|
+
## Main results
|
|
162
|
+
|
|
163
|
+
ARI against the published annotation, every method tuned on its own grid on
|
|
164
|
+
the section it is scored on (Table 1 of the paper); the coordinate reference
|
|
165
|
+
is part of spSheaf's grid. MERFISH: one configuration per method over the
|
|
166
|
+
five slices.
|
|
167
|
+
|
|
168
|
+
| | MERFISH (5 slices) | osmFISH | Allen prototype MERFISH | BaristaSeq |
|
|
169
|
+
|---|---|---|---|---|
|
|
170
|
+
| **spSheaf** (selected reference) | **0.571** (local) | 0.673 (tissue) | **0.754** (tissue) | **0.757** (tissue) |
|
|
171
|
+
| spSheaf at the default, tissue | 0.548 | 0.673 | 0.754 | 0.757 |
|
|
172
|
+
| Points2Regions | 0.457 | 0.432 | 0.684 | 0.733 |
|
|
173
|
+
| SSAM | 0.401 | **0.729** | 0.410 | 0.738 |
|
|
174
|
+
| FICTURE | 0.342 | 0.465 | 0.475 | 0.631 |
|
|
175
|
+
|
|
176
|
+
On MERFISH the ranking holds under all three selection rules the five slices
|
|
177
|
+
allow — per-slice argmax 0.584 vs 0.510, one configuration 0.571 vs 0.457,
|
|
178
|
+
leave-one-slice-out 0.552 vs 0.388 — and spSheaf ranks first of twelve on
|
|
179
|
+
NMI against the SDMBench baselines. On osmFISH two of eleven layers are
|
|
180
|
+
recovered at no configuration; see `experiments/osmfish/`. On the MERFISH
|
|
181
|
+
benchmark at the local reference, ten ablation arms attribute +0.102 ARI to
|
|
182
|
+
the sheaf term over no weighting and +0.046 over a kernel baseline; at the
|
|
183
|
+
tissue reference the gain over the kernel baseline is 0.006, and on the two
|
|
184
|
+
SpaceTx sections no edge score separates from it (`reported/tissue/`).
|
|
185
|
+
|
|
186
|
+
## Layout
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
src/spsheaf/ the method (fibers, graph, sheaf, clustering, evaluation, pipeline); what `pip install spsheaf` ships
|
|
190
|
+
examples/ quickstart.py and tutorial.ipynb: one public section from molecules to domains
|
|
191
|
+
experiments/ one folder per dataset (synthetic/ for the k calibration); each README gives commands, inputs and outputs
|
|
192
|
+
benchmarks/segfree/ SSAM, Points2Regions and FICTURE under a matched protocol; results/, results_union/, results_tissue/ tracked
|
|
193
|
+
figures/ figure pipeline; out/ is generated, assets/ holds the few placed rasters
|
|
194
|
+
reported/ spSheaf's own result tables, one row per configuration (tracked); tissue/ for the runs at the default reference
|
|
195
|
+
tests/ tools/ unit tests; provenance and requirements scripts
|
|
196
|
+
docs/ package API; dev-notes/ are working notes
|
|
197
|
+
data/ untracked; data/README.md
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Licence
|
|
201
|
+
|
|
202
|
+
MIT.
|
spsheaf-0.2.0/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# spSheaf
|
|
2
|
+
|
|
3
|
+
Sheaf-theoretic spatial domain inference from raw molecules, without cell
|
|
4
|
+
segmentation. spSheaf places kernel-smoothed expression fibers on a grid,
|
|
5
|
+
gives each vertex a local principal-subspace stalk, scores every edge by its
|
|
6
|
+
*sheaf disagreement* — the residual mismatch of neighbouring local coordinates
|
|
7
|
+
after the stalks are aligned — and attenuates those transitions during
|
|
8
|
+
clustering. Paluzo-Hidalgo, Maehara & Imoto (2026), manuscript in preparation for publication.
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
*The construction on real data (MERFISH slice 0.19 of Moffitt et al. 2018, a
|
|
13
|
+
530 × 400 µm window; every quantity computed by spSheaf at the paper's
|
|
14
|
+
settings). Molecules and the 15 µm grid; the Gaussian-kernel fiber of one grid
|
|
15
|
+
point and one of its components as a smoothed field; the kNN graph; the stalks
|
|
16
|
+
standing over the vertices; one edge across an interface, its two stalks
|
|
17
|
+
aligned by the SVD of their overlap and the disagreement D(e) between the
|
|
18
|
+
transported coordinates; every edge scored; the smoothed boundary field over
|
|
19
|
+
the tissue; the domains.*
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install spsheaf # Python >= 3.9; numpy, scipy, scikit-learn, pandas
|
|
25
|
+
pip install -e . # from a clone, for the experiments and figures
|
|
26
|
+
pip install -r requirements-lock.txt # exact versions the results were produced on
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quickstart
|
|
30
|
+
|
|
31
|
+
`examples/quickstart.py` downloads one public section — the SpaceTx BaristaSeq mouse visual cortex,
|
|
32
|
+
0.32 million molecules, 81 genes — runs the eight steps at the paper's
|
|
33
|
+
settings for it, and draws the domains next to the authors' layer
|
|
34
|
+
annotation. One minute on a laptop, no other data needed:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install spsheaf matplotlib
|
|
38
|
+
python examples/quickstart.py # writes examples/quickstart_domains.png; ARI 0.77 against the six layers
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
`examples/tutorial.ipynb` is the same section step by step, with every
|
|
44
|
+
intermediate drawn (fibers, stalks, the disagreement field), what each
|
|
45
|
+
parameter does, the comparison with the annotation per layer, and how to
|
|
46
|
+
choose the number of domains with the silhouette when there is no
|
|
47
|
+
annotation. It runs in under a minute; the executed outputs are kept in the
|
|
48
|
+
notebook.
|
|
49
|
+
|
|
50
|
+
The whole method is eight function calls on a molecule table with columns
|
|
51
|
+
`x`, `y`, `gene` (any units; the parameters are in those units):
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from spsheaf import fibers, graph, sheaf, clustering
|
|
55
|
+
|
|
56
|
+
grid, raw, genes = fibers.build_kernel_fibers(mol, grid_spacing=15, bandwidth=25, x_col="x", y_col="y")
|
|
57
|
+
fib = fibers.normalize_fibers(raw) # 1 fibers on a grid
|
|
58
|
+
edges = graph.build_spatial_graph(grid, k=6) # 2 spatial graph
|
|
59
|
+
stalks = sheaf.compute_stalks(fib, grid, uniform_dim=6, k_nbrs=50) # 3 stalks: local principal subspaces
|
|
60
|
+
rs = sheaf.compute_restriction_maps(stalks, edges, 6) # 4 restriction maps by SVD alignment
|
|
61
|
+
b_raw, b_smooth = sheaf.compute_boundary_scores(fib, grid, stalks, edges, rs, smooth_radius=60) # 5-6
|
|
62
|
+
labels = clustering.boundary_aware_clustering(fib, grid, b_smooth, edges, n_clusters=8) # 7
|
|
63
|
+
labels, _ = clustering.spatial_cleanup(labels, edges, grid_coords=grid) # 8
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`spsheaf.pipeline.run_single_slice` wraps the eight steps and the evaluation
|
|
67
|
+
against an annotation. Parameters: grid spacing `δ`, kernel bandwidth `h`,
|
|
68
|
+
stalk dimension `d`, smoothing radius `σ_s`, boundary weight `α_b`, spatial
|
|
69
|
+
weight `α_s`, and the coordinate reference of the stalk sections
|
|
70
|
+
(`SPSHEAF_REFERENCE=tissue|local`, default `tissue`: coordinates referenced
|
|
71
|
+
to the section mean; `local` references them to the neighbourhood mean, and
|
|
72
|
+
is what the tuning grid selects on the MERFISH benchmark). The number of
|
|
73
|
+
domains `k` is an input; `experiments/merfish/k_selection.py` computes the
|
|
74
|
+
silhouette over a range of `k`, and the paper recommends selecting within the
|
|
75
|
+
range where it lies within 0.002 of its maximum. `docs/package_api.md` lists
|
|
76
|
+
the modules.
|
|
77
|
+
|
|
78
|
+
## Reproduce the paper
|
|
79
|
+
|
|
80
|
+
Everything below this line is the paper's reproduction material; `pip install spsheaf` ships only `src/spsheaf/`.
|
|
81
|
+
|
|
82
|
+
Point the code at the data (`data/README.md` lists every input and where it
|
|
83
|
+
comes from):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
export SPSHEAF_DATA=/path/to/data # or copy paths.example.toml to paths.toml
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Rebuild the figures from the stored tables (minutes, no raw data needed
|
|
90
|
+
except for the panel dumps under `data/results`):
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
make figures && make figures-verify # 17 PDFs into figures/out/, checked against the 180 mm / 6-8 pt spec
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Check a number without running anything: every spSheaf table in the paper is
|
|
97
|
+
under `reported/` (`reported/README.md` names the claim and the command behind
|
|
98
|
+
each file; `reported/tissue/` holds the runs repeated at the tissue reference);
|
|
99
|
+
the competitors' tables and per-cell labels are under
|
|
100
|
+
`benchmarks/segfree/results/` (`results_union/` for the selection over both
|
|
101
|
+
references). `make reported-check` and `make reported-tissue-check` fail if
|
|
102
|
+
either has drifted from its results tree.
|
|
103
|
+
|
|
104
|
+
Re-run the experiments (hours; raw inputs required):
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
SPSHEAF_REFERENCE=local make merfish merfish-seeds merfish-loo-cv # Table 1, Fig 2b: five slices, 20 seeds, the 27-configuration grid
|
|
108
|
+
make merfish merfish-seeds merfish-loo-cv # the same at the default (tissue) reference; the two grids together are the 54-row union
|
|
109
|
+
make revision # ablation, boundary evaluation, k sweep (Fig 2c/d)
|
|
110
|
+
make osmfish-all osmfish-insample osmfish-insample-tissue # Fig 3, the 2 x 108 grid
|
|
111
|
+
make spacetx-all spacetx-ablation # both SpaceTx sections, 162-configuration grids, ED 3, Supplementary Fig 3
|
|
112
|
+
make matched-selection-union benchmark-table # one selection rule for all four methods (Table 1)
|
|
113
|
+
make k-selection merfish-k-selection merfish-k-split # choosing k: synthetic calibration, the five slices, the divisions at k = 9 (ED 4, Fig 4)
|
|
114
|
+
make help # everything else
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The grids are resumable: interrupt and re-run, only missing configurations
|
|
118
|
+
are computed. The competitor sweeps (SSAM, Points2Regions, FICTURE) are
|
|
119
|
+
reproducible from the tracked metric tables; re-running them needs the
|
|
120
|
+
competitors' inputs, which `python -m benchmarks.segfree.inputs` names.
|
|
121
|
+
|
|
122
|
+
## Main results
|
|
123
|
+
|
|
124
|
+
ARI against the published annotation, every method tuned on its own grid on
|
|
125
|
+
the section it is scored on (Table 1 of the paper); the coordinate reference
|
|
126
|
+
is part of spSheaf's grid. MERFISH: one configuration per method over the
|
|
127
|
+
five slices.
|
|
128
|
+
|
|
129
|
+
| | MERFISH (5 slices) | osmFISH | Allen prototype MERFISH | BaristaSeq |
|
|
130
|
+
|---|---|---|---|---|
|
|
131
|
+
| **spSheaf** (selected reference) | **0.571** (local) | 0.673 (tissue) | **0.754** (tissue) | **0.757** (tissue) |
|
|
132
|
+
| spSheaf at the default, tissue | 0.548 | 0.673 | 0.754 | 0.757 |
|
|
133
|
+
| Points2Regions | 0.457 | 0.432 | 0.684 | 0.733 |
|
|
134
|
+
| SSAM | 0.401 | **0.729** | 0.410 | 0.738 |
|
|
135
|
+
| FICTURE | 0.342 | 0.465 | 0.475 | 0.631 |
|
|
136
|
+
|
|
137
|
+
On MERFISH the ranking holds under all three selection rules the five slices
|
|
138
|
+
allow — per-slice argmax 0.584 vs 0.510, one configuration 0.571 vs 0.457,
|
|
139
|
+
leave-one-slice-out 0.552 vs 0.388 — and spSheaf ranks first of twelve on
|
|
140
|
+
NMI against the SDMBench baselines. On osmFISH two of eleven layers are
|
|
141
|
+
recovered at no configuration; see `experiments/osmfish/`. On the MERFISH
|
|
142
|
+
benchmark at the local reference, ten ablation arms attribute +0.102 ARI to
|
|
143
|
+
the sheaf term over no weighting and +0.046 over a kernel baseline; at the
|
|
144
|
+
tissue reference the gain over the kernel baseline is 0.006, and on the two
|
|
145
|
+
SpaceTx sections no edge score separates from it (`reported/tissue/`).
|
|
146
|
+
|
|
147
|
+
## Layout
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
src/spsheaf/ the method (fibers, graph, sheaf, clustering, evaluation, pipeline); what `pip install spsheaf` ships
|
|
151
|
+
examples/ quickstart.py and tutorial.ipynb: one public section from molecules to domains
|
|
152
|
+
experiments/ one folder per dataset (synthetic/ for the k calibration); each README gives commands, inputs and outputs
|
|
153
|
+
benchmarks/segfree/ SSAM, Points2Regions and FICTURE under a matched protocol; results/, results_union/, results_tissue/ tracked
|
|
154
|
+
figures/ figure pipeline; out/ is generated, assets/ holds the few placed rasters
|
|
155
|
+
reported/ spSheaf's own result tables, one row per configuration (tracked); tissue/ for the runs at the default reference
|
|
156
|
+
tests/ tools/ unit tests; provenance and requirements scripts
|
|
157
|
+
docs/ package API; dev-notes/ are working notes
|
|
158
|
+
data/ untracked; data/README.md
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Licence
|
|
162
|
+
|
|
163
|
+
MIT.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""spSheaf quickstart: from a molecule table to spatial domains in one script.
|
|
2
|
+
|
|
3
|
+
Downloads one public section (the SpaceTx BaristaSeq mouse visual cortex
|
|
4
|
+
section, 0.32 million molecules, 81 genes, ~25 MB), runs the eight steps of
|
|
5
|
+
spSheaf at the settings used for it in the paper, draws the domains next to
|
|
6
|
+
the authors' layer annotation and prints the ARI. About one minute on a
|
|
7
|
+
laptop; no other data needed.
|
|
8
|
+
|
|
9
|
+
pip install spsheaf matplotlib
|
|
10
|
+
python examples/quickstart.py # writes quickstart_domains.png
|
|
11
|
+
|
|
12
|
+
Every step is a plain function call so each intermediate (fibers, graph,
|
|
13
|
+
stalks, boundary field) can be inspected or replaced. Units are those of the
|
|
14
|
+
input coordinates (micrometres here).
|
|
15
|
+
"""
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from urllib.request import urlretrieve
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
from spsheaf import clustering, evaluation, fibers, graph, sheaf
|
|
23
|
+
|
|
24
|
+
# ---------------------------------------------------------------- 1. data
|
|
25
|
+
BASE = "https://s3.amazonaws.com/starfish.data.spacetx/spacetx-website/data/BARISTASeq"
|
|
26
|
+
HERE = Path(__file__).resolve().parent
|
|
27
|
+
DATA = HERE / "data_quickstart"
|
|
28
|
+
DATA.mkdir(exist_ok=True)
|
|
29
|
+
for name in ("s3_spot_table.csv", "s3_mapped_cell_table.csv"):
|
|
30
|
+
if not (DATA / name).exists():
|
|
31
|
+
print(f"downloading {name} ...")
|
|
32
|
+
urlretrieve(f"{BASE}/{name}", DATA / name)
|
|
33
|
+
|
|
34
|
+
spots = pd.read_csv(DATA / "s3_spot_table.csv", usecols=["xc", "yc", "gene"])
|
|
35
|
+
mol = pd.DataFrame({"x": spots.xc.values, "y": spots.yc.values, "gene": spots.gene.astype(str).values})
|
|
36
|
+
print(f"{len(mol):,} molecules, {mol.gene.nunique()} genes")
|
|
37
|
+
|
|
38
|
+
# ---------------------------------------------------------------- 2. parameters
|
|
39
|
+
# the configuration selected for this section in the paper (Supplementary Table 3)
|
|
40
|
+
GRID_SPACING = 10.0 # um between fiber centres
|
|
41
|
+
BANDWIDTH = 40.0 # um, Gaussian kernel of the fibers
|
|
42
|
+
STALK_DIM = 9 # dimension of every local principal subspace
|
|
43
|
+
SMOOTH_RADIUS = 180.0 # um, smoothing of the disagreement field
|
|
44
|
+
SPATIAL_WEIGHT = 0.5 # alpha_s: weight of the coordinates in the clustering feature
|
|
45
|
+
BOUNDARY_ALPHA = 0.5 # alpha_b: strength of the attenuation at transitions
|
|
46
|
+
K = 6 # number of domains (six annotated layers here)
|
|
47
|
+
|
|
48
|
+
# ---------------------------------------------------------------- 3. the eight steps
|
|
49
|
+
grid, raw, genes = fibers.build_kernel_fibers(mol, grid_spacing=GRID_SPACING, bandwidth=BANDWIDTH,
|
|
50
|
+
x_col="x", y_col="y") # (1) fibers on a grid
|
|
51
|
+
fib = fibers.normalize_fibers(raw)
|
|
52
|
+
edges = graph.build_spatial_graph(grid, k=6) # (2) spatial graph
|
|
53
|
+
stalks = sheaf.compute_stalks(fib, grid, uniform_dim=STALK_DIM, k_nbrs=50) # (3) stalks: local PCA bases
|
|
54
|
+
rs = sheaf.compute_restriction_maps(stalks, edges, STALK_DIM) # (4) restriction maps by SVD
|
|
55
|
+
b_raw, b_smooth = sheaf.compute_boundary_scores(fib, grid, stalks, edges, rs,
|
|
56
|
+
smooth_radius=SMOOTH_RADIUS) # (5-6) disagreement, field
|
|
57
|
+
labels = clustering.boundary_aware_clustering(fib, grid, b_smooth, edges, n_clusters=K,
|
|
58
|
+
spatial_weight=SPATIAL_WEIGHT,
|
|
59
|
+
boundary_alpha=BOUNDARY_ALPHA) # (7) attenuated clustering
|
|
60
|
+
labels, _ = clustering.spatial_cleanup(labels, edges, grid_coords=grid) # (8) remove fragments
|
|
61
|
+
|
|
62
|
+
# ---------------------------------------------------------------- 4. against the authors' annotation
|
|
63
|
+
cells = pd.read_csv(DATA / "s3_mapped_cell_table.csv")
|
|
64
|
+
cxy = cells[["rotated_x", "rotated_y"]].values
|
|
65
|
+
cxy = cxy - cxy.min(0) + grid.min(0) # cell frame onto the grid frame by their lower-left corners
|
|
66
|
+
from scipy.spatial import cKDTree
|
|
67
|
+
_, idx = cKDTree(grid).query(cxy, k=1)
|
|
68
|
+
pred = labels[idx]
|
|
69
|
+
names = sorted(cells.layer.dropna().unique())
|
|
70
|
+
gti = np.array([names.index(l) if isinstance(l, str) else -1 for l in cells.layer])
|
|
71
|
+
valid = gti >= 0
|
|
72
|
+
ari = evaluation.compute_ari(pred, gti, valid)
|
|
73
|
+
print(f"ARI against the layer annotation: {ari:.3f} (the paper reports 0.757 at this configuration "
|
|
74
|
+
"with a label-free registration of the cell frame; a corner alignment is used here)")
|
|
75
|
+
|
|
76
|
+
# ---------------------------------------------------------------- 5. picture
|
|
77
|
+
import matplotlib
|
|
78
|
+
matplotlib.use("Agg")
|
|
79
|
+
import matplotlib.pyplot as plt
|
|
80
|
+
fig, axes = plt.subplots(1, 3, figsize=(12, 4.2))
|
|
81
|
+
axes[0].scatter(cxy[valid, 0], cxy[valid, 1], c=gti[valid], cmap="tab10", s=2, linewidths=0)
|
|
82
|
+
axes[0].set_title("annotation (cells)")
|
|
83
|
+
axes[1].scatter(grid[:, 0], grid[:, 1], c=b_smooth, cmap="magma", s=5, marker="s", linewidths=0)
|
|
84
|
+
axes[1].set_title("sheaf disagreement field")
|
|
85
|
+
axes[2].scatter(grid[:, 0], grid[:, 1], c=labels, cmap="tab10", s=5, marker="s", linewidths=0)
|
|
86
|
+
axes[2].set_title(f"spSheaf domains, k = {K}")
|
|
87
|
+
for ax in axes:
|
|
88
|
+
ax.set_aspect("equal"); ax.set_axis_off()
|
|
89
|
+
fig.tight_layout()
|
|
90
|
+
fig.savefig(HERE / "quickstart_domains.png", dpi=150)
|
|
91
|
+
print(f"wrote {HERE / 'quickstart_domains.png'}")
|