Skip to main content

spOT-NMF

PyPI version Python versions License: GPL v3 Publish to PyPI bioRxiv Documentation Status

📖 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.

# 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_mode selects how the input is read: h5ad for a single AnnData .h5ad file, visium (the default) for a Space Ranger output directory, or visium_hd for Visium HD. Pass --data_mode h5ad whenever --adata_path points to a .h5ad file.

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 network command reuses the per-spot usages written by deconvolve. 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_iter factorizations, so it takes roughly n_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.

  1. 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 own percentile quantile (default 0.90) are set to zero first.
  2. 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).
  3. 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_threshold overrides the lift with an absolute cutoff, as in releases before 0.2.0.
  4. 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_stats gives a per-edge p and BH q against rigid toroidal shifts of the presence maps, when array coordinates are given.
  • spatial_association computes threshold-free bivariate Moran's I and Lee's L.
  • The edge table records each edge's direction: reciprocal_val, asymmetry and 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 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 (and leidenalg<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 spot
  • genescores_per_topic_{sample}.csv — gene scores per topic
  • ranked_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)

Source distribution for spot-nmf 0.2.0
File Size Uploaded
spot_nmf-0.2.0.tar.gz 459.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for spot-nmf 0.2.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

0.2.1

2 release files

This release

0.2.0 This release

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page