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
- Acquisition - detect the platform, load counts and coordinates, per-spot UMI QC.
- Density - cells per spot from a hybrid housekeeping/UMI calibration.
low_slice_quality=Trueenforces a floor of one cell on every spot passing the UMI gate. - Feature selection - noise-gene regex, presence filter, overdispersed genes from the mean-variance trend.
- Manifold - joint gene co-expression topology via ICA + UMAP.
- K-sweep - held-out perplexity across K, rare-topic counts, automatic recommendation.
- 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.
- Sampling - discrete cells drawn per spot; per-spot UMI totals are conserved exactly.
- Placement - cells positioned inside their parent spot footprint by Vogel packing.
- 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)
| File | Size | Uploaded | |
|---|---|---|---|
| codeconv-1.0.0.tar.gz | 53.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|