Skip to main content

CoexpressDeconvolve

Spot-based spatial transcriptomics measures whole transcriptomes in place, but every spot pools several cells, so a spot profile is a mixture rather than a cell. CoexpressDeconvolve resolves that mixture into single-cell-like profiles, without needing a matched single-cell dataset to learn from.

The problem it addresses is that the usual route out of the mixture is a reference atlas, and a reference is often unavailable, mismatched to the tissue, or missing the very populations that were lost during dissociation. CoexpressDeconvolve works from the slide alone.

Three quantities carry the method. How many cells a spot captured is estimated from the housekeeping-gene signal together with the total UMI count. Which expression programmes are present is learned de novo from gene co-expression across the whole slide. How the spot's transcripts belong to those cells follows from distributing its UMIs among them, conserving the per-spot total exactly: the reconstructed cells of a spot add back up to the transcriptome measured there.

Each cell comes out with a whole transcriptome, absolute counts and a coordinate inside its parent spot, written in the standard 10x SpaceRanger layout, so the result opens in Seurat or scanpy like any other single-cell object.

Method and benchmarks: Perik-Zavodskaia, O., Perik-Zavodskii, R., Alrhmoun, S. & Sennikov, S. iScience 29, 116824 (2026). https://doi.org/10.1016/j.isci.2026.116824

Supported platforms

The platform is detected from the input layout and announced at load. Everything downstream is platform-agnostic: Step 1 normalizes every input to counts, coordinates, a capture footprint and a pitch.

Platform Capture unit Pitch Layout expected Histology
10x Visium (any capture area) 55 µm 100 µm SpaceRanger: filtered_feature_bc_matrix.h5 (or the .mtx folder) + spatial/ yes
DBiT-seq 10 / 25 / 50 µm 2 × channel width one count table on the 50 × 50 grid, labels AxB no

The DBiT-seq channel width is read from the sample name when it carries one (GSM4189611_50t -> 50 µm), and the pitch is twice the width. Platforms that ship no tissue image get a black placeholder canvas sized to the capture grid, with synthesized scale factors, so Seurat::Load10X_Spatial() and every spatial plot still work.

Capture units at or below the size of one cell are out of scope: there is nothing to deconvolve, and segmentation rather than a mixture model is the right step there.

Installation

pip install codeconv

Human (hs) and mouse (mm) species profiles are built into the module, so nothing else has to be downloaded to get started.

Usage

import codeconv
codeconv.set_seed(42)

spatial_path  = "./Glioblastoma"
output_folder = "."
species       = "hs"                      # "hs" | "mm" | anything in your config

slices   = codeconv.step1_acquisition_and_anchoring(spatial_path)
slices   = codeconv.step2_estimate_cell_density(slices, species=species,
                                                min_umi=900, anchor_mean_factor=0.7)
odg_pack = codeconv.step3_feature_selection(slices, species=species, n_odg=3000)
manifold = codeconv.step4_gene_manifold(slices, odg_pack, n_components=30)

ksweep   = codeconv.step5_ksweep(odg_pack, min_k=3, max_k=15)
print(ksweep.summary())  # per-K perplexity, rare topics, which K are in the low band

model = codeconv.step6_final_deconvolution(slices, odg_pack, manifold,
                                           n_topics=codeconv.recommended_K,  # or your own K
                                           k_neighbors=3)
cells = codeconv.step7_sampling_engine(slices, model, species=species)
codeconv.step8_geometry_and_placement(cells, slices)
codeconv.step9_export_results(cells, slices, output_folder, interaction_range_um=50.0)

Several slices at once

Pass a dict instead of a string. Topics are aligned across slices, so the reconstructed cell types are comparable between samples:

spatial_path = {"sample_A": "./A", "sample_B": "./B"}

Per-slice parameters (min_umi, anchor_mean_factor, low_slice_quality) take either a scalar, broadcast to every slice, or a dict keyed by slice name:

slices = codeconv.step2_estimate_cell_density(
    slices, species="hs",
    min_umi={"sample_A": 900, "sample_B": 1200},
    anchor_mean_factor=0.7,
)

A list of paths also works, with slice names taken from the folder names.

Choosing K automatically

The number of topics used to be a visual judgement call. It no longer is:

ksweep = codeconv.step5_ksweep(odg_pack, min_k=3, max_k=15)   # prints the recommendation

The default rule keeps every K whose held-out perplexity lands in the best 5% of the sweep by rank and returns the largest of them, which is the highest K still in the low-perplexity regime. That is deliberate: because CoexpressDeconvolve returns individual cells rather than fractions, a rare topic is a minor cell population worth recovering, not evidence of over-splitting. Alternatives:

codeconv.recommend_k(ksweep.perplexity['Glioblastoma'], rule="relative_tolerance", tol=0.01)
codeconv.recommend_k(ksweep.perplexity['Glioblastoma'], rule="lowest_perplexity")

A recommendation landing on max_k means perplexity had not turned back up yet and the sweep is too narrow; you get a warning. Pass n_topics= to record a manual choice, and it is drawn on the plot next to the automatic one.

The 9 steps

  1. Acquisition - detect the platform, load counts and coordinates, per-spot UMI QC.
  2. Density - cells per spot from a hybrid housekeeping/UMI calibration. low_slice_quality=True enforces a floor of one cell on every spot passing the UMI gate.
  3. Feature selection - noise-gene regex, presence filter, overdispersed genes from the mean-variance trend.
  4. Manifold - joint gene co-expression topology via ICA + UMAP.
  5. K-sweep - held-out perplexity across K, rare-topic counts, automatic recommendation.
  6. Deconvolution - per-slice LDA, Hungarian topic alignment across slices, mean-consensus beta, per-slice theta refit against the frozen consensus, projection onto each slice's full gene list.
  7. Sampling - discrete cells drawn per spot; per-spot UMI totals are conserved exactly.
  8. Placement - cells positioned inside their parent spot footprint by Vogel packing.
  9. Export - 10x layout under output_folder/slice_<name>/deconvolved/, one folder per slice.

Downstream analysis

Each slice exports a filtered_feature_bc_matrix.h5 and a spatial/ folder. From there the reconstruction is an ordinary single-cell spatial object and goes wherever such objects go:

library(Seurat)
seurat_obj <- Load10X_Spatial("./slice_Glioblastoma/deconvolved")
import scanpy as sc
adata = sc.read_visium("./slice_Glioblastoma/deconvolved")

Clustering, annotation, trajectory inference, cell-cell communication - anything that takes a single-cell object works, with no adapters. Seurat Spatial.ipynb in the repository is a worked example. Topics are aligned across slices, so cluster comparison between samples is meaningful.

Citation

@article{PerikZavodskaia2026CoexpressDeconvolve,
  title   = {CoexpressDeconvolve enables reference-free single-cell-resolution
             deconvolution from spot-based spatial transcriptomics},
  author  = {Perik-Zavodskaia, Olga and Perik-Zavodskii, Roman and
             Alrhmoun, Saleh and Sennikov, Sergey},
  journal = {iScience},
  volume  = {29},
  pages   = {116824},
  year    = {2026},
  doi     = {10.1016/j.isci.2026.116824}
}

License

MIT, see LICENSE.

Metadata

Release files for codeconv 1.0.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 codeconv 1.0.0
File Size Uploaded
codeconv-1.0.0.tar.gz 53.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for codeconv 1.0.0
File Interpreter ABI Platform
codeconv-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 104.8 kB

Release files / codeconv-1.0.0.tar.gz

Download URL codeconv-1.0.0.tar.gz
Size 53.3 kB
Tags Source
SHA-256 checksum
How to use checksums
471e95148b886e9189172e4f41fa8d4cb843bb9e41cb12b483dbfd454eb8a949
BLAKE2b-256 checksum
How to use checksums
8519bd4a08fdc25672620b9279df47d3b9b4ab862f75e7c839fa9fa3929d09ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Release files / codeconv-1.0.0-py3-none-any.whl

Download URL codeconv-1.0.0-py3-none-any.whl
Size 51.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
edfafe08595cbf800896301453e1db829396efd02f51a4ebb7b5aafdb1194084
BLAKE2b-256 checksum
How to use checksums
254fdc1739e852486c63f6b99e974726040ddcef03be13b729adaaafe6f6f06a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.9

Release history Release notifications | RSS feed

This release

1.0.0 This release

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