sctopo
Topological analysis of single-cell data. sctopo extracts statistically
significant 1-dimensional homological features (loops) in single-cell
embeddings, scores genes by how strongly their expression is locked to each
loop, and visualizes both geometry and gene-expression structure in three
complementary 3-D views per cycle.
What it does
Given a single-cell embedding (typically PCA or Concord) and Leiden cluster
labels, sctopo runs the following pipeline per cluster:
-
Per-cluster persistent homology. Compute Vietoris–Rips persistent homology on the cluster's embedding (Ripser).
-
Significance filtering. Keep only H1 classes whose persistence exceeds the (1 − α) quantile of a column-permutation null distribution, and whose birth radius is below a quantile of nearest-neighbor distances.
-
Critical-edge cycle recovery. For each surviving class, extract a representative cycle via shortest-path on the 1-skeleton.
-
Circular coordinates. Assign a continuous angle θ ∈ [0, 2π) to every topocell (cells in a neighborhood of the cycle) using DREiMac.
-
Topogene discovery. Rank genes by the amplitude of their circular correlation with θ:
$$r_{\text{circ}}(g) = \sqrt{\,\operatorname{corr}(x_g, \cos\theta)^2 + \operatorname{corr}(x_g, \sin\theta)^2\,}$$
This is the first Fourier-mode amplitude along the cycle. A permutation test gives per-gene p-values and a family-wise significance threshold.
-
Visualization. Three 3-D views per loop (cluster PCA / global UMAP / topogene PCA), plus per-loop matplotlib panels for PDF export.
Installation
git clone https://github.com/binglunshao/sctopo.git
cd sctopo
pip install -e .
Development install:
pip install -e ".[dev]"
Core dependencies: numpy, scipy, scikit-learn, networkx, pandas,
matplotlib, plotly, anndata, scanpy, ripser, persim, dreimac,
torch (legacy gradient scoring only). torch is only required if you
call sctopo.genes.topo_scores_rep_sampling; the default periodicity
scoring is pure NumPy.
Quick start
import anndata as ad
import sctopo.cells as tpc
import sctopo.genes as tpg
import sctopo.viz as tpv
# Load and subset to one cluster
adata = ad.read_h5ad("brain_dev_concord.h5ad")
adata_cluster = adata[adata.obs["leiden"] == "2"].copy()
# 1. Per-cluster PH with significance filtering
X = adata_cluster.obsm["X_pca"]
topological_loops = tpc.critical_edge_method_significant(
X,
n_subsamples=50,
alpha=0.05,
birth_dist_nn_quantile=0.75,
verbose=True,
)
tpv.quick_loop_summary(topological_loops)
# 2. Circular coords + topogenes for each loop
results = []
for i in range(len(topological_loops)):
cc = tpg.get_circular_coords(topological_loops, loop_index=i)
res = tpg.compute_topogenes(
adata_cluster, topological_loops, cc,
loop_index=i, n_genes=100,
)
results.append({"cc": cc, **res})
print(f"Loop {i+1}: top genes = {res['topogenes'][:5]}")
# 3. Per-gene permutation significance
sig = tpg.topogenes_significance(
adata_cluster, topological_loops, results[0]["cc"],
loop_index=0, n_perms=100,
)
print(f"Family-wise α=0.05 threshold: {sig['max_threshold_0p05']:.3f}")
# 4. Visualize
import scanpy as sc
topocell_ixs = topological_loops[0]["topocell_ixs"]
adata_cc = adata_cluster[topocell_ixs, results[0]["topogenes"]].copy()
sc.tl.pca(adata_cc, n_comps=3)
fig = tpv.plot_topocells_subplots(
adata_cluster, adata_cc, topological_loops,
loop_index=0, color_col="phase",
)
fig.show()
A complete walk-through is in notebooks/example_pipeline.ipynb.
Topogene scoring: two methods
sctopo ships two topogene-scoring algorithms. The periodicity method is the
default and recommended choice; the gradient method is preserved for
reproducibility with earlier analyses.
| Periodicity (default) | Gradient (legacy) | |
|---|---|---|
| Function | topo_scores_periodicity |
topo_scores_rep_sampling |
| Definition | √(corr(x, cos θ)² + corr(x, sin θ)²) | ‖∇₍x₎ Σ subsample-cycle-perimeter‖₂ |
| Determinism | Deterministic | Monte-Carlo over n_reps |
| Interpretation | First Fourier amplitude along cycle | Sensitivity of cycle length to expression |
| Dependencies | NumPy + SciPy | PyTorch |
| Per-gene p-value | Yes (cheap permutation) | Yes (more expensive) |
Both methods accept the same arguments and return the same shape of output, so
you can swap via compute_topogenes(..., method="periodicity"|"rep_sampling").
Package layout
sctopo/
├── pp/ preprocessing utilities
│ ├── pca.py gap-based PCA cutoff (sparse-aware)
│ └── outliers.py Isolation Forest on PCA coords
├── cells/ per-cluster PH, significance, loop extraction
│ ├── graph.py VR 1-skeleton construction
│ ├── significance.py permutation null
│ ├── loops.py critical_edge_method[_significant]
│ └── cycle_assign.py project all cells onto a cycle
├── genes/ circular coords + topogene scoring
│ ├── circular_coords.py DREiMac wrapper, circular variance
│ ├── periodicity.py default scoring (Fourier amplitude)
│ ├── rep_sampling.py legacy gradient scoring
│ └── topogenes.py compute_topogenes, topogenes_significance
└── viz/ plotly + matplotlib visualizations
├── cloud.py 3-D topocell views
├── diagrams.py persistence diagrams
└── summary.py text summaries
Citing
If you use sctopo in published work, please cite:
@software{sctopo2026,
author = {Shao, Binglun},
title = {sctopo: topological analysis of single-cell data},
year = {2026},
url = {https://github.com/binglunshao/sctopo},
version = {0.1.0},
}
A paper describing the method is in preparation; please also cite the
underlying tools ripser (Tralie et al. 2018)
and dreimac (Perea et al.).
Acknowledgements
sctopo builds on ideas from an earlier prototype
totopos, developed in
collaboration with Emanuel Flores Bautista. Per-cluster persistent
homology, periodicity-based topogene scoring, permutation-null
significance testing, and the unified visualization API are new in this
package.
License
BSD 3-Clause License. See LICENSE.
Metadata
Release files for sctopo 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sctopo-0.1.0.tar.gz | 43.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sctopo-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 86.2 kB
Release files / sctopo-0.1.0.tar.gz
| Download URL | sctopo-0.1.0.tar.gz |
|---|---|
| Size | 43.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bde4fb8095437e0d85a8d424fbc789aaa0aa399b21570babd22025c48091c6d0
|
|
BLAKE2b-256 checksum How to use checksums |
2defa581c0568d230ffa30e1559f20d9e06ff74cb6d4c50f23382db10a667cfa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.4
|
Release files / sctopo-0.1.0-py3-none-any.whl
| Download URL | sctopo-0.1.0-py3-none-any.whl |
|---|---|
| Size | 43.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
63b95f5b019922c98928ac5d8c6d96b253ecc00ff84f50433fec9f095be23ec4
|
|
BLAKE2b-256 checksum How to use checksums |
a6d786128cccaa7a9726c6557b22bb597acb9b7ea5c642081c87dd4c45ef28f6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.4
|