spOT-NMF
Gene expression deconvolution via Optimal Transport with proximity-aware niche mapping for spatial transcriptomics.
spOT-NMF is a Python package for reference-free deconvolution of spatial transcriptomics data. It replaces the reconstruction loss of non-negative matrix factorization (NMF) with an entropic optimal-transport (OT) loss, recovering gene programs and their per-spot usage, and adds modules to annotate those programs and to map them into spatial niches.
Abdelkareem, A.O. et al. Briefings in Bioinformatics (accepted, 2026) · Documentation · Issues
Contents
- Features
- Installation
- Quick start
- Niche networks
- Command-line reference
- Outputs
- Tutorials
- Reproducing the manuscript
- Citation
- Support and license
Features
- OT-NMF deconvolution. Reference-free program discovery with an entropic OT loss, solved on GPU or CPU.
- Consensus mode. Replicate factorizations clustered into consensus programs, in the style of cNMF.
- Gene selection. Batch-aware selection of overdispersed (highly variable) genes.
- Annotation. Pathway enrichment and gene-set overlap for each program.
- Niche networks. Directed program co-incidence networks partitioned into spatial niches, with tools to calibrate and explore the thresholds.
- Visualization. Spatial maps of program usage, network plots and connection heatmaps.
- Two interfaces. A command-line tool and a Python API.
Installation
spOT-NMF requires Python 3.12 or later. PyTorch is not a hard dependency, so you can install the build (CPU or CUDA) that matches your hardware.
Using uv (recommended):
uv venv --python 3.12
source .venv/bin/activate # Windows: .venv\Scripts\activate
# PyTorch: choose one (see https://pytorch.org for other builds)
uv pip install torch --index-url https://download.pytorch.org/whl/cpu
# uv pip install torch --index-url https://download.pytorch.org/whl/cu121
uv pip install spot-nmf
Using 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, for 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]"
uv run pytest -q
Check the installation with spotnmf --help. spOT-NMF uses a GPU when one is
available and falls back to the CPU otherwise.
Quick start
Command line
Run the full pipeline (deconvolution, annotation, spatial plots and networks):
spotnmf spotnmf \
--sample_name SAMPLE1 \
--adata_path ./data/sample1.h5ad \
--data_mode h5ad \
--results_dir ./results \
--k 5 \
--genome GRCh38
--data_mode sets how the input is read: h5ad for an AnnData file, visium (the
default) for a Space Ranger output directory, or visium_hd for Visium HD.
Each step can also be run on its own:
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
network reads the per-spot usages written by deconvolve.
Python
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, ...) have tuned defaults; override them
with a model_params dictionary.
Consensus mode
By default spOT-NMF runs a single factorization. Consensus mode fits n_iter
replicates (default 10), clusters them into consensus programs and refits the per-spot
usages against the consensus spectra. The refit is non-negative least squares by
default, as in cNMF; pass model_params={"refit": "ot"} for an optimal-transport
refit. Runtime grows roughly linearly with n_iter, so a GPU is recommended for large
datasets.
spotnmf deconvolve --sample_name SAMPLE1 --adata_path ./data/sample1.h5ad \
--data_mode h5ad --results_dir ./results --k 5 --consensus --n_iter 10
In Python, pass consensus=True to run_experiment.
Niche networks
The niche module turns the per-spot usage matrix into a directed program-to-program network and partitions it into spatial niches with Infomap.
- Presence. A program is present at a location where its usage exceeds a
threshold. Two detection rules are available:
absolute(default): usage >usage_threshold, default 0.05.percentile: usage above each program's ownpercentilequantile (default 0.90). This is the rule used for Figures 6d and 7d of the paper.
- Directed co-incidence. The weight of edge p → t is the fraction of p's locations
where t is also present, Laplace-smoothed as
(pos + 1) / (n + 2). - Edge criterion. Under spatial independence this fraction equals t's prevalence,
so an edge is kept when
co-incidence / prevalence(t) >= edge_lift.edge_thresholdreplaces the lift with an absolute cutoff, as in releases before 0.2.0. - Niches. Infomap clusters the kept edges. A program without a kept edge is
isolated and labelled
-1.
| Parameter (CLI / Python) | Default | Description |
|---|---|---|
--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 number of locations where the source program is present |
Choosing the edge criterion. On the paper's colorectal Visium HD sample (24 µm bins, 50 programs), the absolute rule gives six niches for lifts between 2.00 and 2.75. At 2.5 they 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. Because the lift sets the resolution of the partition, explore it on new data rather than relying on a single value:
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) # 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"]]
The module also provides:
permutation_edge_stats: a per-edge empirical p value and Benjamini–Hochberg q value against rigid toroidal shifts of the presence maps (requires array coordinates).spatial_association: threshold-free bivariate Moran's I and Lee's L.- Edge direction in the edge table (
reciprocal_val,asymmetry,reciprocal_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 Figures 6 and 7 of the paper:
spotnmf network --sample_name SAMPLE1 --results_dir ./results \
--detection_rule percentile --percentile 0.90 --usage_threshold 0 \
--edge_threshold 0.199 --n_bins 1000
In Python: nn.plot_network_analysis(results_dir, sample, **nn.PUBLISHED_SETTINGS).
Note on python-igraph. Infomap partitions depend on the igraph version. The paper used python-igraph 0.11.8, and the package pins
igraph>=0.11.8,<0.12(withleidenalg<0.11, whose later releases require igraph 1.x). With python-igraph 1.0.0 the published settings merge the vascular and immune niches on the colorectal sample, giving five niches instead of six. The package warns when another igraph series is installed.
Command-line reference
| Command | Description |
|---|---|
spotnmf |
Full pipeline: deconvolution, annotation, spatial plots and networks |
deconvolve |
Run OT-NMF and save the factorization |
plot |
Plot spatial program usage |
annotate |
Annotate programs by pathway enrichment and gene-set overlap |
network |
Build and plot niche networks |
Run spotnmf <command> --help for the options of each command.
Outputs
| File | Contents |
|---|---|
topics_per_spot_{sample}.csv |
Program usage per spot |
genescores_per_topic_{sample}.csv |
Gene scores per program |
ranked_genescores_{sample}.csv |
Ranked marker genes per program |
Annotation, plotting and network runs also write enrichment and gene-set overlap tables, spatial plots, network plots and the niche partition.
Tutorials
The notebooks run on the bundled example dataset and are stored with their figures, so they render directly on GitHub.
- Full pipeline: data loading, gene selection, deconvolution, spatial maps, marker genes and validation against ground-truth cell types.
- Proximity (niche) networks: building the co-occurrence network, detecting niches and plotting the network and connection heatmap.
Reproducing the manuscript
The main branch contains the package. The notebooks and analysis scripts behind the
manuscript figures, including the revision analyses, are on the manuscript branch:
git fetch origin
git checkout manuscript
Notebooks are in scripts/manuscript_notebooks/ and the revision analysis scripts in
scripts/revision/; the README in each folder lists the data each one needs.
Citation
If you use spOT-NMF, 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), doi: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}
}
Support and license
Questions, bug reports and feature requests are welcome on the issue tracker.
spOT-NMF is released under the GNU General Public License v3.0; see LICENSE.
Metadata
Release files for spot-nmf 0.2.1
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.1.tar.gz | 458.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| spot_nmf-0.2.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 905.1 kB
Release files / spot_nmf-0.2.1.tar.gz
| Download URL | spot_nmf-0.2.1.tar.gz |
|---|---|
| Size | 458.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c1fb8c939b513f70ca4363c7bb04a616148878842c3a5db60e4cd7f9fd8adbae
|
|
BLAKE2b-256 checksum How to use checksums |
7d6ed7362493a78db62ebe33117559df5526414f4addff17472897db95a43469
|
| 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.1-py3-none-any.whl
| Download URL | spot_nmf-0.2.1-py3-none-any.whl |
|---|---|
| Size | 446.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ec9cab8bf2618795293db0c5415142509756c8548e5a9ad56e3d99a2f2523a15
|
|
BLAKE2b-256 checksum How to use checksums |
0f4b9b228cf65920852924829b7b7e3e6de13f79b73ba006489096dd5ce05983
|
| 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