spOT-NMF
📖 Documentation: https://spot-nmf.readthedocs.io
Optimal Transport-Based Matrix Factorization for Accurate Deconvolution of Spatial Transcriptomics Abdelkareem, A.O. et al.(2025)
spOT-NMF is a Python package for unsupervised deconvolution and discovery of gene programs in spatial transcriptomics. It integrates Optimal Transport (OT) into a non-negative matrix factorization (NMF) framework, enabling robust topic modeling, high-resolution spatial deconvolution, and rich biological annotation.
✨ Key Features
- OT-NMF Deconvolution: Reference-free topic modeling with OT-regularized NMF.
- HVG Selection: Flexible, batch-aware highly variable gene selection.
- Biological Annotation: Automated enrichment and gene-set overlap of inferred programs.
- Spatial Visualization: Publication-quality spatial plots for topic/program usage.
- Scalable & Modular: Built for large datasets and multi-sample workflows.
- CLI & Python API: Run from the command line or import in notebooks.
📦 Installation
spOT-NMF requires Python ≥ 3.12. We recommend uv for a fast,
reproducible setup. PyTorch is installed separately so you can pick the build (CPU or CUDA) for your platform.
Recommended: uv
# 1. Create and activate an isolated environment (uv fetches Python 3.12 if needed)
uv venv --python 3.12
# Linux/macOS: source .venv/bin/activate
# Windows: .venv\Scripts\activate
# 2. Install PyTorch for your platform (see pytorch.org)
# CPU-only:
uv pip install torch --index-url https://download.pytorch.org/whl/cpu
# CUDA 12.x (Linux/Windows with NVIDIA GPUs):
# uv pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
# 3. Install spOT-NMF
uv pip install spot-nmf
Alternative: pip
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install spot-nmf
From source (development)
git clone https://github.com/MorrissyLab/spOT-NMF.git
cd spOT-NMF
uv venv --python 3.12
uv pip install torch --index-url https://download.pytorch.org/whl/cpu
uv pip install -e ".[dev]" # editable install with test dependencies
uv run pytest -q # run the test suite
Verify the install
spotnmf --help
If no GPU is available, spOT-NMF automatically runs on CPU.
🚀 Quick Start
Command Line
Full pipeline (deconvolution → annotation → spatial plots → networks):
spotnmf spotnmf \
--sample_name SAMPLE1 \
--adata_path ./data/sample1.h5ad \
--data_mode h5ad \
--results_dir ./results \
--k 5 \
--genome GRCh38
--data_modeselects how the input is read:h5adfor a single AnnData.h5adfile,visium(the default) for a Space Ranger output directory, orvisium_hdfor Visium HD. Pass--data_mode h5adwhenever--adata_pathpoints to a.h5adfile.
Other commands:
spotnmf deconvolve --sample_name SAMPLE1 --adata_path ./data/sample1.h5ad --data_mode h5ad --results_dir ./results --k 5
spotnmf plot --sample_name SAMPLE1 --adata_path ./data/sample1.h5ad --data_mode h5ad --results_dir ./results
spotnmf annotate --sample_name SAMPLE1 --results_dir ./results --genome GRCh38
spotnmf network --sample_name SAMPLE1 --results_dir ./results
The
networkcommand reuses the per-spot usages written bydeconvolve. On small datasets no program pairs may pass--n_bins/--edge_lift; lower them to force a graph. See Niche networks below for the parameters.
Python / Notebooks
import spotnmf as spot
adata = spot.io.read_adata("data/sample1.h5ad", data_mode="h5ad")
results = spot.cli.run_experiment(
adata_spatial=adata,
k=5, # number of programs
sample_name="SAMPLE1",
results_dir="./results",
genome="GRCh38",
)
Model hyperparameters (lr, h, w, eps, …) default to tuned values; pass a
model_params dict to override them. See the full pipeline tutorial
for a complete walkthrough (HVG selection, annotation, spatial plots, and validation).
Consensus mode (more robust, more accurate)
By default spOT-NMF runs a single factorization. In consensus mode it fits
several replicates, clusters them into consensus programs (cNMF-style), and refits
the per-spot usages against the frozen consensus spectra. The refit is a
least-squares solve by default (refit="nnls", as in cNMF); pass
model_params={"refit": "ot"} for the fixed-spectra optimal-transport solve. On the packaged
simulated STARmap benchmark it was the top performer across accuracy metrics
(see scripts/benchmark/).
Enable it with --consensus on the CLI or consensus=True in Python; --n_iter
(default 10) controls the replicate count.
spotnmf deconvolve --sample_name SAMPLE1 --adata_path ./data/sample1.h5ad \
--data_mode h5ad --results_dir ./results --k 5 --consensus --n_iter 10
Consensus runs
n_iterfactorizations, so it takes roughlyn_iter× longer than a single run. A GPU is recommended for larger datasets.
🕸️ Niche networks
The niche module turns the per-spot usage matrix into a directed program–program network and partitions it into spatial niches with Infomap.
- Presence. A program is present in a spot or bin where its usage exceeds a
threshold. Two rules are available:
absolute(the default): usage >usage_threshold, which defaults to 0.05.percentile(the rule of the paper's Figures 6d and 7d): usages below each program's ownpercentilequantile (default 0.90) are set to zero first.
- Directed co-incidence. The weight of edge p → t is the fraction of p's
locations where t is also present, Laplace-smoothed:
(pos + 1) / (n + 2). - Edge criterion. Under spatial independence that fraction equals t's
prevalence, so the criterion is a lift: keep p → t when
co-incidence / prevalence(t) >= edge_lift. The default is 2.5 under the absolute rule and 2.0 under the percentile rule.edge_thresholdoverrides the lift with an absolute cutoff, as in releases before 0.2.0. - Niches. Infomap (
python-igraph) clusters the kept edges. A program with no kept edge is isolated and labelled-1.
| Parameter (CLI / Python) | Default | Meaning |
|---|---|---|
--detection_rule / detection_rule |
absolute |
absolute or percentile |
--usage_threshold / usage_threshold |
0.05 (absolute), 0 (percentile) | presence threshold on usage |
--percentile / percentile |
0.90 | per-program quantile (percentile rule only) |
--edge_lift / edge_lift |
2.5 (absolute), 2.0 (percentile) | edge criterion as a fold change over the target's prevalence |
--edge_threshold / edge_threshold |
unset | absolute co-incidence cutoff; overrides the lift |
--n_bins / n_bins |
1000 | minimum locations where the source program is present |
Why 2.5? On the paper's colorectal Visium HD sample (24 µm, 50 programs), the absolute rule gives six niches across lifts 2.00–2.75. At 2.5 those six match the published niches one to one (ARI 0.841). At 2.0 the Vascular programs merge into the Immune niche, and at 3.0 a seventh niche appears. Resolution is tunable, so for a new dataset explore it rather than relying on one setting:
import pandas as pd
from spotnmf import niche_networks as nn
usage = pd.read_csv("results/SAMPLE1/topics_per_spot_SAMPLE1.csv", index_col=0)
edges, niches = nn.infer_niches(usage) # package default: usage > 0.05, lift >= 2.5
grid = nn.sweep_niche_parameters(usage, detection_rule="absolute",
lifts=[1.5, 2.0, 2.5, 3.0, 3.5])
grid[["edge_lift", "n_edges", "n_niches", "n_isolated", "mean_abs_asymmetry"]]
Other tools in the module:
permutation_edge_statsgives a per-edge p and BH q against rigid toroidal shifts of the presence maps, when array coordinates are given.spatial_associationcomputes threshold-free bivariate Moran's I and Lee's L.- The edge table records each edge's direction:
reciprocal_val,asymmetryandreciprocal_kept. Under the percentile rule both directions of an edge always carry the same weight. Only the absolute rule gives the network a direction.
Reproducing the paper's Figures 6 and 7 exactly:
spotnmf network --sample_name SAMPLE1 --results_dir ./results \
--detection_rule percentile --percentile 0.90 --usage_threshold 0 \
--edge_threshold 0.199 --n_bins 1000
or nn.plot_network_analysis(results_dir, sample, **nn.PUBLISHED_SETTINGS).
python-igraph version. Infomap partitions depend on the igraph version. The paper used python-igraph 0.11.8, so the package pins
igraph>=0.11.8,<0.12(andleidenalg<0.11, which needs igraph 1.x). On the colorectal sample, python-igraph 1.0.0 merges the Vascular and Immune niches at the published settings, giving five niches instead of six. The package warns if a different igraph series is installed.
📓 Tutorials
Fully worked, well-commented notebooks run on the packaged example dataset with figures pre-rendered, so GitHub displays them directly in the browser.
- Full pipeline tutorial → — data loading, HVG selection, OT-NMF deconvolution, spatial mapping, marker extraction, and validation against ground-truth cell types, end-to-end.
- Proximity (niche) networks → — build a program–program co-occurrence network from the usage matrix, detect niches with Infomap, and render the network + connection heatmap (step-by-step and one-call).
⚙️ CLI Overview
| Command | Description |
|---|---|
spotnmf |
Full pipeline: deconvolution → annotation → spatial plotting |
deconvolve |
Run OT-NMF and save results |
plot |
Visualize spatial topic/program usage |
annotate |
Enrich and annotate gene programs |
network |
Visualize niche networks based on topic interactions |
Run spotnmf <command> --help for per-command options.
📁 Outputs
topics_per_spot_{sample}.csv— topic/program usage per spotgenescores_per_topic_{sample}.csv— gene scores per topicranked_genescores_{sample}.csv— ranked marker genes per topic- Pathway enrichment and gene-set overlap tables
- Spatial plots & QC visualizations
- Network plots of topic–topic interactions
🔬 Reproducibility (Manuscript Notebooks)
The main branch provides the reusable software package.
The original Jupyter notebooks used to reproduce manuscript figures are maintained in the manuscript branch:
git fetch origin
git checkout manuscript
Notebooks are in:
scripts/manuscript_notebooks/
Use manuscript to regenerate paper figures; use main for running the package on your data.
🧾 Citation
Please cite:
Abdelkareem, A.O., Gill, G.S., Manoharan, V.T., Verhey, T.B., & Morrissy, A.S. spOT-NMF: gene expression deconvolution via Optimal Transport with proximity-aware niche mapping for spatial transcriptomics. Briefings in Bioinformatics (accepted, 2026). Preprint: bioRxiv (2025). https://doi.org/10.1101/2025.08.02.668292
@article{abdelkareem2026spotnmf,
title = {spOT-NMF: gene expression deconvolution via Optimal Transport with proximity-aware niche mapping for spatial transcriptomics},
author = {Abdelkareem, Aly O. and Gill, Gurveer S. and Manoharan, Varsha Thoppey and Verhey, Theodore B. and Morrissy, A. Sorana},
journal = {Briefings in Bioinformatics},
year = {2026},
note = {Accepted. Preprint: bioRxiv doi:10.1101/2025.08.02.668292}
}
🤝 Contributing & Support
Questions, ideas, bug reports, and feature requests—please open a GitHub Issue: https://github.com/MorrissyLab/spOT-NMF/issues
📜 License
GPL-3.0. See LICENSE for details.
Metadata
Release files for spot-nmf 0.2.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 | |
|---|---|---|---|
| spot_nmf-0.2.0.tar.gz | 459.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spot_nmf-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 906.8 kB
Release files / spot_nmf-0.2.0.tar.gz
| Download URL | spot_nmf-0.2.0.tar.gz |
|---|---|
| Size | 459.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a588a01e74c81379e9a04e619c449b4bf30f910dac666bdc9de1739cf94647e3
|
|
BLAKE2b-256 checksum How to use checksums |
6a0d53dc34bfceeca0a405a5107301c65e13a32267189942c355c92862b3b4da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency logRelease files / spot_nmf-0.2.0-py3-none-any.whl
| Download URL | spot_nmf-0.2.0-py3-none-any.whl |
|---|---|
| Size | 447.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df103171ff31fa71f7bdea63c8d137bf4c10af9835b26e491a89ec1aae8891a9
|
|
BLAKE2b-256 checksum How to use checksums |
ab70952674f1d4cb41216427d736b8e11d562c5a350cba83dde4bea7b757a052
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 5, 2026.
Transparency log