Showing posts with label BANKSY. Show all posts
Showing posts with label BANKSY. Show all posts

Thursday, September 10, 2026

Building a Spatial Transcriptomics Workflow: From cell2location to BANKSY and Beyond

Spatial transcriptomics · complete workflow · best practices · Squidpy · publication figures

Putting It All Together

This series has covered spatial transcriptomics from first principles (Squidpy post 1) through advanced methods — cell2location, SpatialDE2, Tangram, BANKSY, ligand-receptor analysis, trajectory analysis, and platform comparisons. This final post assembles them into a decision-driven workflow you can apply to any new Visium dataset.

The goal is not to run every tool — it is to know which tools answer which questions, in what order, and at what computational cost.

Figure 1. Left: complete spatial analysis pipeline from raw data to publication figures. Right: approximate runtime and peak memory per tool on a standard 8-core laptop with a 3,500-spot Visium dataset.

Phase 1: Data Loading and QC (30 minutes)

Every analysis starts here. Do not skip the QC plots — spatial data has tissue-specific artifacts (necrotic regions, edge effects, adipose tissue with near-zero counts) that will distort downstream results if not filtered.

import squidpy as sq
import scanpy as sc
import matplotlib.pyplot as plt

adata = sq.read.visium("spaceranger_output/", library_id="sample_01")

# QC metrics
sc.pp.calculate_qc_metrics(adata, inplace=True)
sq.pl.spatial_scatter(adata, color=["total_counts", "n_genes_by_counts"], ncols=2)

# Filter: remove low-count spots (necrosis, edge artifacts)
sc.pp.filter_cells(adata, min_counts=500)
sc.pp.filter_cells(adata, min_genes=200)
sc.pp.filter_genes(adata, min_cells=10)

# Normalize
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=3000, flavor="seurat_v3")

# Save raw counts for cell2location
adata.layers["counts"] = adata.X.copy()
adata.raw = adata

Phase 2: Clustering and Annotation (1–2 hours)

Build spatial graph first, then standard dimensionality reduction. The spatial graph is required for neighborhood enrichment and LR analysis later.

sq.gr.spatial_neighbors(adata, coord_type="grid", n_rings=2)

sc.tl.pca(adata, use_highly_variable=True)
sc.pp.neighbors(adata, n_neighbors=15, n_pcs=30)
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.3)

# Inspect: do clusters correspond to tissue morphology?
sq.pl.spatial_scatter(adata, color="leiden", size=1.5)
sc.pl.umap(adata, color="leiden")

# Annotate clusters with known markers
marker_genes = {
    "Cancer": ["EPCAM", "KRT8", "MKI67"],
    "T cells": ["CD3E", "CD8A", "CD4"],
    "Macrophages": ["CD68", "CD163", "SPP1"],
    "Fibroblasts": ["COL1A1", "FAP", "ACTA2"],
    "Endothelial": ["PECAM1", "VWF", "CDH5"],
}
sc.pl.dotplot(adata, marker_genes, groupby="leiden")

Phase 3: Choose Your Analysis Path

After annotation, your research question determines what to run next. Use this decision tree:

Q: What cell types are in each spot, at single-cell resolution?
→ cell2location (Post 12) — requires matched scRNA-seq reference

Q: Which genes show non-random spatial patterns?
→ SpatialDE2 (Post 14) or sq.gr.spatial_autocorr for a faster first pass

Q: Are cell types communicating via known ligand-receptor pairs?
→ sq.gr.nhood_enrichment + sq.gr.ligrec (Post 17)

Q: How does gene expression change across tissue regions continuously?
→ PAGA + DPT trajectory (Post 18)

Q: Are there spatial domains not captured by gene expression alone?
→ BANKSY (Post 16) — incorporates neighbor expression into clustering

Q: I have scRNA-seq from a different tissue; can I map those cells spatially?
→ Tangram (Post 15) — optimal transport mapping

Phase 4: Publication Figure Checklist

Before submitting, verify each figure against this checklist:

# 1. Spatial scatter — confirm spot size is set for the journal's figure width
sq.pl.spatial_scatter(adata, color="cell_type", size=1.2,
                       figsize=(6, 6), dpi=300, save="fig1a.pdf")

# 2. Color palettes — use perceptually uniform maps for continuous data
#    Use categorical palettes with enough contrast for 8+ cell types
import matplotlib.pyplot as plt
# Bad: plt.cm.jet (never use in publications)
# Good: "plasma", "viridis", "RdBu_r" for continuous; "tab20" for categories

# 3. Scale bar — always include physical scale
# sq.pl.spatial_scatter adds it automatically from adata.uns["spatial"] metadata

# 4. Export at 300 dpi minimum for print
plt.savefig("figure1.pdf", dpi=300, bbox_inches="tight")
plt.savefig("figure1.png", dpi=300, bbox_inches="tight")

Compute Budget for a Full Analysis

From the benchmark figure, planning a full Visium TME analysis on a laptop (8 cores, 16 GB RAM):

  • QC + clustering: ~30 min, <2 GB RAM
  • cell2location (1000 epochs): ~45 min, 6 GB RAM — the bottleneck; use GPU if available
  • SpatialDE2: ~8 min, 3.5 GB RAM
  • sq.gr.ligrec (1000 perms): ~12 min, 2 GB RAM
  • PAGA + DPT: ~5 min, 1.5 GB RAM
  • Total: ~1.5–2 hours end-to-end

If you are running on a server with a GPU, cell2location drops to under 10 minutes and the full pipeline completes in well under an hour.

What This Series Has Not Covered

Spatial transcriptomics is a fast-moving field. Topics worth following as they mature:

  • 3D spatial reconstruction — stacking serial sections with PASTE2 or Spateo
  • Spatial proteomics integration — combining Visium with CODEX or IMC protein data
  • Spatial multi-sample integration — harmonizing multiple patients/conditions with Harmony or scVI adapted for spatial
  • Foundation models — Geneformer and scGPT adapted for spatial context are emerging

All code from this series is available on GitHub. The Squidpy Starter Pack ($19) contains runnable Jupyter notebooks for posts 1–5 with a real Visium PBMC dataset. For the immunotherapy biology behind the cell types you are mapping, see the sister blog NeoantigenLab.

Thursday, August 27, 2026

BANKSY: Using Neighborhood Context to Find Spatial Domains

 

SPATIAL TRANSCRIPTOMICS · TUTORIAL

BANKSY: Using Neighborhood Context to Find Spatial Domains

Why adding just 20% neighbor signal outperforms pure expression clustering for tissue layer detection

July 2026  ·  9 min read  ·  SpatiaBio

Standard Leiden clustering on spatial data ignores one obvious fact: cells that sit next to each other tend to be in the same tissue layer. BANKSY (Building Aggregates with a Neighborhood Kernel for Yeoman Segmentation) fixes this by mixing each cell's own expression with a smoothed average of its neighbors — then clustering on the combined signal. The result is cleaner, more spatially coherent domains without requiring any graph-based post-processing.

Figure 1. BANKSY spatial domain map (left) and performance comparison vs. other methods by ARI and NMI (right).

The Core Idea: Lambda Controls the Mix

BANKSY introduces one key parameter: lambda (0 to 1), which controls how much neighbor context to blend in:

lambda = 0
Pure cell-intrinsic expression. Identical to standard Leiden.
lambda = 0.2
Sweet spot. Adds spatial coherence while preserving cell identity.
lambda = 0.8
Strong spatial smoothing. Best for laminar structures (brain cortex).

The embedding is constructed as: z = (1 - lambda) * x_self + lambda * x_neighbors, where x_neighbors is a weighted mean of expression in the spatial neighborhood (typically k=15-30 nearest neighbors in physical space).

Running BANKSY with Squidpy

import squidpy as sq
import scanpy as sc
import numpy as np
from banksy import Banksy

# Load and preprocess
adata = sc.read_visium('path/to/visium/')
sc.pp.normalize_total(adata, target_sum=1e4)
sc.pp.log1p(adata)
sc.pp.highly_variable_genes(adata, n_top_genes=2000)
adata = adata[:, adata.var.highly_variable]

# Build spatial graph (required for BANKSY)
sq.gr.spatial_neighbors(adata, coord_type='generic', n_neighs=15)

# Run BANKSY
banksy = Banksy(
    adata,
    banksy_lambda=0.2,   # neighbor mix ratio
    resolution=0.8,       # Leiden resolution
    pca_dims=20,
    n_neighbors=15,
)
banksy.fit()
banksy.plot_domains(adata, color='banksy_labels')
Installation: pip install banksy-py — works with AnnData/Squidpy natively.

BANKSY vs. Other Spatial Domain Methods

BANKSY BayesSpace STAGATE Leiden only
Approach Neighbor smoothing + clustering Bayesian HMRF Graph autoencoder Expression only
Speed Fast Slow Slow (GPU) Fastest
Scalability Visium HD / Xenium OK Visium only Large datasets OK Any
Interpretability High (lambda is intuitive) Low Low (latent space) High
Best for General use, layered tissues Small datasets, fine structure Complex, non-laminar Quick baseline

Practical Tips

  • Start with lambda=0.2 for most tissues. Go higher (0.5-0.8) only for clearly laminar structures like brain cortex or retina.
  • Run both BANKSY and Leiden and compare — BANKSY domains should look spatially cleaner. If they don't, your spatial graph might be poorly constructed.
  • Use sq.gr.spatial_neighbors with coord_type='generic' for Visium. The default 'grid' mode can misrepresent actual tissue distances.
  • BANKSY domains are not cell types — they are tissue microenvironments. Combine with cell type deconvolution (cell2location or Tangram) to get the full picture.

Key Takeaways

  • BANKSY adds spatial coherence with a single parameter (lambda) on top of standard clustering
  • lambda=0.2 is a reliable default; increase for laminar tissues
  • Faster and more interpretable than deep learning alternatives for most use cases
  • Best used alongside cell type deconvolution, not as a replacement

Interested in the immunology behind those spatial domains?

See NeoantigenLab for checkpoint inhibitors, CAR-T, and neoantigen biology.

Explore NeoantigenLab

From the NeoantigenLab sister blog

Working with tumor transcriptomics for neoantigen research?

NeoantigenLab covers neoantigen biology, WES pipelines, pVACseq, and HLA typing for experimental researchers — the biology behind what you're sequencing.

Visit NeoantigenLab →

Squidpy's Delaunay Radius Pruning Builds a Graph You Can't Save — Here's the Workaround

Squidpy · spatial_neighbors_delaunay · h5ad · IORegistryError · still in latest release Squidpy's Delaunay Radius Pruning Builds a G...