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 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
+ ![The spSheaf construction on a 530 × 400 µm window of a MERFISH hypothalamus section](docs/assets/spsheaf_method.gif)
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
+ ![quickstart](examples/quickstart_domains.png)
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.
@@ -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
+ ![The spSheaf construction on a 530 × 400 µm window of a MERFISH hypothalamus section](docs/assets/spsheaf_method.gif)
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
+ ![quickstart](examples/quickstart_domains.png)
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'}")