Condition-Associated Spatial Edge Inference — spatial transcriptomics edge prediction and differential interaction analysis using bilinear graph classification.
Casei identifies which pairwise relationships between neighboring cells are condition-specific, shifting the unit of biological inference from individual cells to the interactions (edges) between them.
- Bilinear Edge Prediction — a symmetric
W @ Wᵀarchitecture that learns condition-specific gene–gene interaction matrices from spatial proximity graphs, grounded in energy-based modeling of pairwise cellular interactions. - Training Dynamics Confidence — edges are ranked by their mean predicted probability across training epochs, capturing continuous biological variation beyond discrete condition labels.
- Differential Interaction Analysis — contrast predicted edge confidence between conditions (e.g. young vs. aged, healthy vs. diseased) to find interactions that are gained or lost.
- Interaction Driver Discovery — label cells as Interacting vs Bystander based on predicted edges, then run differential expression to find the genes driving those interactions.
- Spatial Visualization — per-sample edge plots, differential heatmaps, gene interaction networks, and scanpy-integrated dotplots.
git clone https://github.com/nitzanlab/Casei.git
cd casei
pip install .- Python ≥ 3.9
- PyTorch ≥ 1.12
- Scanpy ≥ 1.9
- AnnData ≥ 0.9
All dependencies are installed automatically.
import casei
# `adata` is an AnnData with:
# - adata.obsm["spatial"] spatial coordinates (n_cells x 2 or 3)
# - adata.obs["sample_id"] one value per tissue sample
# - adata.obs["condition"] the condition label per sample (e.g. healthy / disease)
# 1. Reproducibility
casei.set_random_seed(42)
# 2. Preprocess -> per-sample spatial kNN graphs, a fitted label encoder,
# and the HVG-filtered AnnData. Returns a 3-tuple.
data_list, label_encoder, adata_f = casei.preprocess_adata(
adata,
n_neighbors=5,
condition_key="condition",
sample_id_key="sample_id",
spatial_key="spatial",
)
# 3. Train the bilinear edge predictor (paper defaults: 100 epochs, lr=1e-3, rank=64).
# Returns (model, edge_results), where edge_results is a list of per-edge dicts
# with mean-over-epochs confidence scores.
model, edge_results = casei.train_edge_predictor(
data_list,
adata_f,
label_encoder,
in_channels=adata_f.n_vars,
hidden_channels=64, # rank r
epochs=100,
lr=1e-3,
)
# 4. Build the condition-adjusted graph: keep the top 5% highest-confidence
# edges per condition, stored as a sparse matrix in adata_f.uns.
adata_f = casei.store_edge_confidence_matrix(adata_f, edge_results, keep_fraction=0.05)
# 5. Differential gene-gene interactions between two conditions (delta = M_cond1 - M_cond2)
pos_pairs, neg_pairs, delta = casei.contrast_gene_interactions(
model, adata_f, label_encoder, cond1="disease", cond2="healthy", top_k=20
)
# 6. Decompose the differential matrix into interaction programs
decomp = casei.enr.decompose_differential_matrix(delta)
# 7. (Optional) GO/KEGG enrichment of those programs — requires `pip install casei[enrichment]`
# decomp_results, enrichment = casei.enr.analyze_differential_interaction_programs_with_enrichment(
# model, adata_f, label_encoder, cond1="disease", cond2="healthy", organism="mouse",
# )
# 8. Visualize
casei.pl.plot_differential_heatmap(
delta, adata_f.var_names.tolist(),
cond1="disease", cond2="healthy",
pos_df=pos_pairs, neg_df=neg_pairs,
)
casei.pl.plot_edges_per_sample(
adata_f, sample_key="sample_id", condition_key="condition",
)A step-by-step tutorial applying Casei to atherosclerosis spatial transcriptomics data (healthy vs. plaque vs. smoker/non-smoker plaques) is available here:
📓 Smoking & Atherosclerosis Tutorial
casei/
├── __init__.py # Public API
├── _version.py # Version info
├── models/
│ └── __init__.py # EdgePredictionMLP
├── plotting/
│ ├── __init__.py # Plotting API
│ ├── _core.py # Heatmaps, networks, comparisons
│ ├── spatial_edges.py # Per-sample edge visualization
│ └── interaction_drivers.py # Interacting vs Bystander dotplots
└── tools/
├── __init__.py # Tools API
├── _preprocessing.py # preprocess_adata, set_random_seed
├── _training.py # train_edge_predictor
└── _analysis.py # contrast_gene_interactions, store_edge_confidence_matrix
| Class | Description |
|---|---|
casei.EdgePredictionMLP |
Bilinear edge predictor with W @ Wᵀ factorization |
| Function | Description |
|---|---|
preprocess_adata() |
Standard spatial transcriptomics preprocessing |
set_random_seed() |
Set all random seeds for reproducibility |
train_edge_predictor() |
Train the bilinear edge prediction model |
store_edge_confidence_matrix() |
Compute and store per-edge confidence scores |
contrast_gene_interactions() |
Build condition-adjusted graphs via differential interaction analysis |
| Function | Description |
|---|---|
plot_edges_per_sample() |
Visualize edges on spatial coordinates per sample |
plot_differential_heatmap() |
Heatmap of differential interaction scores |
plot_gene_network() |
Gene interaction network graph |
plot_edge_comparison() |
Compare edge confidence across conditions |
analyze_interaction_drivers() |
DE analysis comparing interacting vs. bystander cells |
# Identify genes driving T-cell / Macrophage interactions in aged tissue
adata = casei.pl.analyze_interaction_drivers(
adata,
cell_type_1="T_cell",
cell_type_2="Macrophage",
condition_key="condition",
target_cond="aged",
n_top_genes=15,
)This labels cells as T_cell_Interacting / T_cell_Bystander (and likewise for macrophages), runs Wilcoxon differential expression, prints a summary table, and generates dotplots.
If you use Casei in your research, please cite:
@article{Karin2026.05.03.722470,
author = {Karin, Jonathan and Friedman, Roy and Nitzan, Mor},
title = {Decoding Condition-Specific Cellular Crosstalk in Spatial Omics via Bilinear Edge Classification},
year = {2026},
doi = {10.64898/2026.05.03.722470},
publisher = {bioRxiv},
journal = {bioRxiv}
}MIT — see LICENSE for details.