A Retrieval-Augmented Framework for Spatially Resolved Gene Expression Prediction.
π Project page: https://hyeongsubkim.github.io/GeneRAG-project-page
Spatial transcriptomics (ST) is pivotal for deciphering molecular organization, yet cross-modal variability challenges accurate H&E-based profiling. Existing models struggle to generalize to unseen genes and lack clinical interpretability. GeneRAG is a model-agnostic retrieval-augmented framework with a Dual-Constrained Retrieval module: it decouples knowledge storage from model training, optimizes an ElasticNet-based sparse sampling matrix that fuses morphological and biological constraints, and reconstructs full transcriptomic profiles β including entirely unseen genes β by exploiting conserved gene-to-gene correlations.
- Plug-and-play on top of any Image-to-ST backbone (Stem, UNI, EXAONE Path 2.5, β¦) with no structural modification.
- Massive-scale zero-shot prediction: up to 5,000 unseen genes per spot from a single forward pass + convex retrieval.
- Inherently transparent: ~50 retrieved reference patches per spot
(sparse Ξ±), each weight
Ξ±_idirectly interpretable as the contribution of one training spot. - GPU-accelerated: batched FISTA solves the joint multi-output ElasticNet for an entire test slide in a single pass on one GPU.
Overview of GeneRAG. At test time a frozen H&E image encoder E turns
the input patch into a feature embedding f_img (the morphology
retrieval query), while a frozen gene-expression decoder D produces
an initial prediction Ε·_init (the gene retrieval query). Together
these form a hybrid query into the pre-built Reference Bank D,
whose retrieved answer is reconstructed into the full gene-expression
prediction Ε·_Full β including genes never seen by the backbone. The
encoder and decoder stay frozen; all adaptation happens in the
retrieval.
The retrieval itself is detailed below.
Dual-Constrained Retrieval. (a) A single sparse code Ξ± is solved
jointly against two constraints over the frozen training Reference Bank
D: a Morphological constraint that reconstructs the query image
embedding f_img from the morphology feature bank D_img, and a
Biological constraint that reconstructs the selected anchor genes
y_anchor from the anchor-gene bank D_anchor. (b) The same
sparse code is then applied to the full gene bank D_Full to
reconstruct the complete transcriptome Ε·_Full β including genes never
seen in the anchor panel.
The whole method is two equations.
Eq. 1 β Dual-Constrained Retrieval. Solve one ElasticNet for the
sparse sampling vector Ξ± shared by both modalities:
Eq. 2 β Full reconstruction. Re-use Ξ±Μ against the full gene bank:
| Symbol | Meaning | Code argument |
|---|---|---|
f_img |
frozen-encoder embedding of the query H&E patch | test_embeddings |
y_anchor |
initial anchor-gene prediction for the query spot (Ε·_init) |
test_anchor |
D_img |
morphology feature bank β embeddings of all training spots | bank_embeddings |
D_anchor |
anchor-gene bank β anchor-gene rows of all training spots | (slice of bank_expression) |
D_full |
full gene bank β complete transcriptome of all training spots | bank_expression |
Ξ±Μ |
sparse sampling vector over the N training spots (~50 nnz) |
returned internally |
Ε·_full |
predicted full transcriptome for the query spot | predictions |
- One code, two views.
Οbalances the morphological vs. biological constraint, so a singleΞ±Μmust explain the query both in image space and in gene space β this is what keeps retrieval grounded. - Sparse by construction.
Ξ³trades L2 shrinkage against L1 sparsity; the resultingΞ±Μactivates only ~50 training spots (nβ²in panel (b)), each weightΞ±_idirectly readable as the contribution of one annotated training spot. - Unseen-gene generalization. Eqs. 1 and 2 share
Ξ±Μbut use different banks. Because gene-to-gene correlations are conserved across spots, a code fit on the anchor panel transfers toD_full, letting GeneRAG reconstruct thousands of genes that were never used as constraints. - Training-free adaptation. Everything downstream of the frozen encoder is a convex solve against the Reference Bank β swap the bank and the model adapts to a new tissue/domain with no weight updates.
Requires Python 3.9+.
git clone https://github.com/HyeongSubKim/GeneRAG
cd GeneRAG
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Install PyTorch first using a wheel matched to your CUDA driver.
# (PyPI's default `torch` wheel ships against the newest CUDA toolkit
# and may not match the installed NVIDIA driver β pin the index URL.)
nvidia-smi | head -1 # check your CUDA version
pip install torch --index-url https://download.pytorch.org/whl/cu126 # CUDA 12.6 example
# Other options:
# https://download.pytorch.org/whl/cu121 # CUDA 12.1
# https://download.pytorch.org/whl/cu118 # CUDA 11.8
# https://download.pytorch.org/whl/cpu # CPU-only fallback
# Install GeneRAG (editable) + optional helpers.
pip install -e ".[all]"Extras (defined in pyproject.toml):
| Extra | What it adds |
|---|---|
io |
anndata β used by the .h5ad I/O helpers in generag.data |
viz |
matplotlib |
dev |
pytest, ruff |
all |
io + viz |
The GPU solver dispatches to a PyTorch + CUDA batched FISTA backend
whenever device='cuda' is requested; a pure-CPU scikit-learn
fallback is always available.
Sanity-check the install:
python -c "import torch; print('cuda:', torch.cuda.is_available())"
python examples/quickstart.pyfrom generag import GeneRAG
# 1) Build the model from any in-memory reference bank.
model = GeneRAG(
bank_expression=bank_df, # (n_bank_spots, n_genes) DataFrame (β‘ D_full)
bank_embeddings=bank_embeddings, # (n_bank_spots, d) β optional (β‘ D_img)
anchor_genes=anchor_gene_list, # list of gene names (β‘ D_anchor)
n_high_variable_genes=10_000,
)
# 2) Predict full gene expression for any new spots (one solve, all spots together).
predictions, mean_sparsity = model.predict(
test_anchor=test_pred_df, # (n_test_spots, n_anchor_genes) (β‘ y_anchor)
test_embeddings=test_embeddings, # (n_test_spots, d) β optional (β‘ f_img)
method='elasticnet',
alpha=0.01, l1_ratio=0.9, # Ξ», Ξ³ in Eq. (1)
embedding_ratio=0.75, # Ο in Eq. (1)
positive=True,
device='cuda',
)
# predictions: DataFrame (n_test_spots Γ n_high_variable_genes) (β‘ Ε·_full)Run a fully self-contained synthetic example:
python examples/quickstart.pyFor real spatial-transcriptomics data the full path looks like this β each step lives
in its own notebook under examples/notebooks/:
| Stage | Notebook | What happens |
|---|---|---|
| 1. Download | 01_download_data.ipynb |
Pull HEST-1k slides (PRAD / Kidney / HER2ST / Mouse Brain) from HuggingFace into ./hest1k_datasets/<organ>/. |
| 2. Build bank | 02_build_bank.ipynb |
Select the anchor gene panel (HVG β MT/RPS/RPL filter β top-mean β© top-std), then extract per-spot frozen-encoder embeddings (UNI / CONCH / EXAONE Path 2.5 / β¦) as .pt files. |
| 3. Linear probing | 03_linear_probing.ipynb |
Train a single linear head on the frozen embeddings to produce Ε·_init for every test spot; saves .pt files into init_pred_fm_pt/. |
| 4. Retrieval | examples/run_experiment.py |
Sweep GeneRAG over the (backbone Γ anchor-gene-list) combinations produced above and write per-experiment CSV metrics. |
For a one-shot smoke test that does not need any of the above:
python examples/quickstart.py # runs on synthetic in-memory dataGeneRAG/
βββ generag/ # the Python package
β βββ core.py # GeneRAG class β the plug-and-play API
β βββ solvers.py # GPU (FISTA) + scikit-learn dispatcher
β βββ data.py # bank construction + .h5ad / .pt I/O helpers
β βββ metrics.py # PCC@K, MSE, MAE, RVD
β βββ experiment.py # multi-GPU hyperparameter sweep runner
β βββ utils.py # device selection, seeding
βββ examples/
β βββ notebooks/
β β βββ 01_download_data.ipynb # HEST-1k download β hest1k_datasets/<ORGAN>/{st,wsis}/
β β βββ 02_build_bank.ipynb # anchor gene + embeddings β hest1k_datasets/<ORGAN>/processed_data/
β β βββ 03_linear_probing.ipynb # linear probe Ε·_init β hest1k_datasets/<ORGAN>/init_pred_fm_pt/
β βββ quickstart.py # plug-and-play demo on synthetic data
β βββ benchmark.py # slide-wise / spot-wise wall-time measurement
β βββ run_experiment.py # paper-style sweep over (backbone Γ gene-list)
βββ hest1k_datasets/ # all dataset-scoped artefacts (notebooks + run_experiment all read/write here)
β βββ PRAD/
β βββ st/ # .h5ad slides (from notebook 01)
β βββ wsis/ # .tif images (from notebook 01)
β βββ processed_data/ # anchor gene lists + per-spot embeddings (notebook 02)
β βββ init_pred_fm_pt/ # linear-probe predictions (notebook 03)
β βββ results/ # GeneRAG sweep CSVs (run_experiment.py)
βββ pyproject.toml # pip-installable package metadata
βββ LICENSE # MIT
The paper evaluates on three organs from HEST-1k:
- Breast (HER2ST) β top-300 HVGs for Core HVG; top-5,000 for Global HVG
- Kidney β top-200 HVGs for Core HVG; top-5,000 for Global HVG
- Prostate β top-200 HVGs for Core HVG; top-5,000 for Global HVG
Backbones validated as drop-in encoders include:
- Stem (diffusion generative ST model)
- UNI (pathology H&E foundation model)
- EXAONE Path 2.5 (multi-modal H&E + omics foundation model)
GeneRAG is architecture-agnostic: any frozen encoder E and any frozen
Ε·_init decoder D whose outputs can be cast into f_img and
y_anchor will plug in.
from generag.experiment import run_sweep
results = run_sweep(
bank_expression=bank_df,
test_anchor=test_anchor_df,
test_gt=ground_truth_df,
anchor_genes=anchor_gene_list,
bank_embeddings=bank_emb, test_embeddings=test_emb,
search_space={
'elasticnet': {
'alpha': [0.001, 0.01, 0.1], # Ξ» in Eq. (1)
'l1_ratio': [0.5, 0.9], # 1 - Ξ³ in Eq. (1)
'embedding_ratio': [0.0, 0.5, 0.75, 1.0], # Ο in Eq. (1)
'positive': [True],
},
},
n_jobs=4, # round-robin across GPUs
output_csv='results/sweep.csv',
)A full end-to-end driver matching the paper's experiments (with bank
construction, anchor selection, and per-slide evaluation) lives in
examples/run_experiment.py.
| Method | GPU (batched FISTA, PyTorch) | CPU (scikit-learn) |
|---|---|---|
| ElasticNet (used in Eq. 1) | β batched | β |
| Lasso | β batched | β |
| Ridge | β closed-form | β |
| OMP | β | β |
| LassoLars | β | β |
| Bayesian Ridge | β | β |
| NNLS | β | β |
The GPU backend matches scikit-learn's loss definition exactly; on
production data we measured reconstruction Pearson r β 0.999 between
the two backends, with the GPU path running ~25β70Γ faster.
Maintainer: Hyeongsub Kim (hyeongsub.kim@snu.ac.kr), Seoul National University.
This repository contains two separately licensed components:
- Code (the
generagpackage, examples, and scripts) β released under the MIT License. - Paper, figures, and text β Β© 2026 the authors. The published version (Version of Record) appears in Medical Image Computing and Computer Assisted Intervention β MICCAI 2026 (Lecture Notes in Computer Science) and is licensed exclusively to Springer Nature Switzerland AG under the MICCAI 2026 Licence to Publish. Reuse of the published paper or its figures is subject to Springer Nature's terms; please cite and link to the Version of Record once the DOI is available.

