Vol. 01 · No. 1 · Beta Edition

Xenium In Situ Spatial Transcriptomics Tutorial

Single-cell-resolution spatial analysis from a 10x Xenium output bundle — native cell-feature matrix loading, density-based artefact removal, transcriptional clustering, and squidpy neighborhood enrichment in micron-precise tissue space.

Intermediate·Spatial Transcriptomics

What you'll learn

  • Load a Xenium output bundle (cell_feature_matrix.h5 + cells.parquet centroids) into AnnData with the Load Xenium node, which aligns the matrix and centroids for you.
  • Filter empty / low-density tissue regions (bubbles, edge artifacts) using the Spatial Density QC workspace.
  • Cluster cells transcriptionally and quantify which cell types co-localize in tissue space using squidpy's neighborhood enrichment z-scores.

Prerequisites

  • • Oreumics installed (install guide).
  • • Familiarity with the standard scRNA-seq workflow — complete the PBMC tutorial first if Normalize → Log1p → PCA → Leiden are unfamiliar.
  • • An AnnData object — the standard cells × genes container that Oreumics nodes pass between each other (defined inline in the glossary at the bottom of this page).
  • • Data: a Xenium output folder of your own (Oreumics does not bundle a full Xenium bundle). The bundled sampledata/spatial/xenium_demo.h5ad is a small demo for the Spatial Density QC step only — see the Dataset section.

Dataset

Real Xenium output bundle (your data)

A complete Xenium output folder contains cell_feature_matrix.h5 (10x-compatible counts; a MEX directory also works), cells.parquet (centroids + per-cell QC metrics; cells.csv is accepted too), cell_boundaries.parquet, and transcripts.parquet. The Load Xenium node reads the matrix and the centroids together — stitching them by hand is where Xenium analyses usually go wrong, because the row order is not guaranteed to match. Whole-slide morphology images are not loaded.

Bundled demo (Spatial Density QC only)

sampledata/spatial/xenium_demo.h5ad~435 KB — 500 synthetic cells × 300 genes; obs holds x_centroid, y_centroid and a cell_type label

This file stores coordinates only as obs columns, not in obsm["spatial"]. Spatial Density QC accepts that (its x_col / y_col fallback), so Load H5AD → Spatial Density QC works out of the box for trying the QC workspace. Spatial Neighbors and everything after it require obsm["spatial"], which Load Xenium writes natively for a real bundle (or Add Coordinates from a centroid CSV).

What Xenium is

A 10x Genomics in situ platform. Unlike Visium spots (each spot averages several cells), Xenium resolves individual cells with single-molecule transcript localization. Coordinates are in micrometers (µm). Gene panels are targeted (hundreds of genes rather than the whole transcriptome), with sub-micron spatial precision.

Pipeline

Xenium End-to-End Pipeline

Load the Xenium counts + centroids natively, filter low-density tissue regions, run standard preprocessing & clustering on transcriptomes, then compute squidpy's neighborhood enrichment to ask which cell types are spatially adjacent in the tissue. There is no shipped Xenium template yet — build this chain from the Node Library's Spatial view (the nodes are listed there in this order) or ask the AI Copilot to lay it out.

Pipeline

Load Xenium
→
Spatial Density QC
→
Normalize
→
Log1p
→
PCA
↳
Neighbors
→
Clustering
→
Spatial Neighbors
→
Nhood Enrichment
→
Spatial Visualization
Load XeniumSpatial

Point directory_path at the Xenium output folder holding cell_feature_matrix.h5 (preferred) or the MEX directory, plus cells.parquet or cells.csv. The node aligns the matrix rows with the centroids, writes micron coordinates to obsm["spatial"], carries the per-cell metadata from cells.parquet into obs, and records the platform in uns["xenium"]. gex_only drops control probes when feature types are present.

directory_path<your Xenium output folder>
library_idblank = folder name
gex_onlytruekeep Gene Expression features only
var_namesgene_symbols

What you'll see

AnnData with cells × panel genes. obsm["spatial"] holds (x, y) micron centroids; obs carries transcript_counts, control probe counts, cell_area and the other cells.parquet columns.

Tip

To try only the Density QC step without a real bundle, use Load H5AD on sampledata/spatial/xenium_demo.h5ad instead — its coordinates live in obs (x_centroid / y_centroid), which the next node accepts as a fallback.

Spatial Density QCSpatial

Xenium tissue sections always have artefacts: bubbles, tissue edges, low-density regions where segmentation produced isolated single-cell ghosts. This node divides the tissue into a square grid and keeps only grid cells with strictly more than min_cells cells. Coordinates are read from obsm["spatial"] when present, otherwise from the obs columns named by x_col / y_col. Defaults (grid_size 100 µm, min_cells 2) are sensible Xenium starting points — fine-tune in the workspace modal.

grid_size100100 µm grid
min_cells2keep grids with more than 2 cells
x_colx_centroidfallback obs column when obsm["spatial"] is absent
y_coly_centroid

What you'll see

A subset of cells passes the density filter; obs["spatial_density_qc"] records PASS/FAIL. Open the Spatial Density QC Workspace from the node to inspect the Plotly scatter and tune the two sliders interactively (the preview updates without a backend re-run).

Tip

For dense tissue (cortex, cerebellum), increase min_cells to 5–10. For sparser tissue (lung alveolar regions, tumor edges), keep min_cells at 1–2 to avoid removing biology.

NormalizePreprocessing

Scale every cell to the same total count. Xenium counts are sparse — a cell typically has tens to a few hundred transcripts vs. thousands in scRNA-seq — so target_sum 10000 still works but the resulting values are heavily inflated. Some users prefer target_sum=1000 or even no scaling for Xenium.

target_sum10000
Log1pPreprocessing

Apply log(x + 1) transformation. With a targeted Xenium panel, Log1p is enough — you can usually skip Highly Variable Genes since the panel is already a curated marker selection.

basenullnatural log (default)
PCAAnalysis

Compute principal components on the panel of detected genes. Use n_comps: 30 — a panel of a few hundred genes does not benefit from 50 PCs the way a whole-transcriptome dataset does. use_highly_variable: false because the entire panel is already a focused marker set (and no HVG node ran).

n_comps30
use_highly_variablefalseXenium panels are pre-curated
NeighborsAnalysis

Build the transcriptional kNN graph in PCA space. Same parameters as scRNA-seq — this graph drives Leiden clustering and (optionally) UMAP. Note: this is the TRANSCRIPTIONAL neighbor graph; the SPATIAL neighbor graph comes later.

n_neighbors15
use_repX_pca
n_pcs15default
ClusteringAnalysis

Run Leiden community detection. Resolution 0.5 is a good default for Xenium because the panel size constrains the number of distinguishable cell states. Cluster labels are stored in obs["leiden"] and used by both Nhood Enrichment and Spatial Visualization.

methodleiden
resolution0.5
key_addedleiden

What you'll see

Cell clusters labeled 0, 1, 2… Each cluster corresponds to a tissue cell type — for tumour data: tumor cells, fibroblasts, T cells, macrophages, endothelial cells, etc.

Spatial NeighborsSpatial

Build a SPATIAL neighbor graph based on physical proximity in tissue. For Xenium keep coord_type at generic (the node default — cells sit at arbitrary positions, not on a grid; "grid" is for Visium). n_neighs: 10 connects each cell to its 10 nearest physical neighbors — typical for Xenium where cells are densely packed. The spatial graph is stored in obsp["spatial_connectivities"] and obsp["spatial_distances"] and is the prerequisite for ALL spatial statistics nodes. This node reads obsm["spatial"] only.

coord_typegenericdefault — "grid" is Visium-only
n_neighs1010–20 typical for Xenium/MERSCOPE
delaunayfalse
spatial_keyspatial

Tip

The common mistake here is the reverse of the Visium one: copying coord_type=grid from a Visium workflow. Xenium cells are not on a hexagonal lattice — leave the default generic.

Nhood EnrichmentSpatial

Squidpy's permutation test for cluster-pair spatial co-localization. For each permutation, cluster labels are randomly shuffled across cells and the co-adjacency counts re-computed. Z-scores compare observed counts to the permutation distribution. Positive z = these cell types are more often spatially adjacent than chance; negative z = spatial avoidance.

cluster_keyleiden
n_perms1000default; 500 for quick exploration
seed0

What you'll see

Z-score matrix in adata.uns["nhood_enrichment"]. Strong positive z-scores reveal niches: tumor + fibroblast + macrophage pockets, T cell + DC interaction zones, etc. Negative z = spatial separation (e.g., tumor core vs. surrounding stroma).

Spatial VisualizationVisualization

Render the cells in physical space colored by cluster. spatial_scatter is the canonical Xenium overview plot — use it both as a sanity check after clustering and as a publication figure. size: 0.5–1.0 works for Xenium's dense single-cell layouts; lower if cells overlap visually.

plot_typespatial_scatter
color_byleiden
size0.5
cmaptab20

What you'll see

A scatter of dots in micrometer coordinates colored by cluster. Tissue regions emerge: tumor nests appear as compact same-colored islands; stromal regions show interspersed cell types; immune infiltrate clusters along tumor-stroma boundaries.

Tip

Add a second Spatial Visualization with plot_type: nhood_enrichment to render the z-score heatmap from the previous node. The two plots together (tissue map + heatmap) tell the full neighborhood story. For interactive exploration use the Spatial Viewer node instead.

Expected Results

  • • Loaded data: cells × panel genes with obsm[“spatial”] in micrometers and the cells.parquet metadata in obs
  • • After Spatial Density QC: most cells retained; edge / bubble cells removed — the exact fraction depends on tissue quality and is shown on the node card
  • • Leiden clustering: a handful to a dozen or so clusters at resolution 0.5, corresponding to tissue cell types
  • • Spatial neighbor graph: obsp[“spatial_connectivities”] populated; ~10 connections per cell
  • • Nhood enrichment: z-score matrix with positive blocks for biologically adjacent cell-type pairs and negative blocks for separated populations
  • • Spatial scatter: tissue map showing cell-type-colored cells in micrometer coordinates — spatial domains visible by eye

Tips & Gotchas

coord_type must be "generic" for Xenium

Spatial Neighbors defaults to coord_type="generic". Do not change it to "grid" — grid mode is Visium-only and assumes a regular hexagonal arrangement that Xenium cells do not have.

Skip Highly Variable Genes for Xenium panels

Xenium gene panels are pre-curated marker sets. Running HVG selection on top is unnecessary and can drop low-expression but biologically critical markers. Set use_highly_variable=false in PCA.

Transcript-level data

To overlay individual molecules, add a Load Transcripts node after the loader pointing at transcripts.parquet (min_qscore 20 is the Xenium standard; cap with max_transcripts on large runs). The molecules land in uns["transcripts"] and show up as dot overlays in the Spatial Viewer — adata.X is unchanged.

Sparse counts — lower target_sum if needed

Xenium cells carry far fewer transcripts than droplet scRNA-seq cells. Normalize's default target_sum=10000 still works but produces large normalized values. Some users prefer target_sum=1000 for Xenium (or skip Normalize entirely with smaller panels).

Going Further

  • Run Spatial Autocorrelation (Moran's I) to find spatially variable genes within the panel without using cluster labels — see the Visium tutorial Phase 4.
  • Group cells by the company they keep with Cellular Neighborhood (Schürch-style microenvironments from the cell-type mix around each cell), or segment contiguous tissue domains with Spatial Domains (BANKSY).
  • Explore interactively in the Spatial Viewer (colour by gene or obs; lasso, box or polygon selection; per-group highlight) and persist drawn regions as an obs label with Spatial Grouping for Subset Data.
  • Annotate clusters with Auto Annotation or CellTypist Annotation and re-run Nhood Enrichment with cell-type names instead of leiden integers. MERSCOPE and CosMx exports have their own native loaders (Load MERSCOPE, Load CosMx) and then follow this same pipeline.

Glossary

AnnData
An annotated data matrix — cells (rows) × genes (columns) with side panels for cell metadata (obs), gene metadata (var), embeddings (obsm), and unstructured results (uns). The standard container that flows between Oreumics nodes.
obsm["spatial"]
An n_cells × 2 array of (x, y) coordinates — in micrometers for Xenium. Written by Load Xenium (or Add Coordinates); Spatial Neighbors, Ripley, Co-Occurrence and the Spatial Viewer read only from here.
In situ spatial transcriptomics
A class of platforms (Xenium, MERSCOPE, CosMx) that detect transcripts in intact tissue using imaging-based barcoded probes — preserving single-cell resolution and exact spatial location.
Neighborhood enrichment
A permutation test (squidpy.gr.nhood_enrichment) that asks: do these two cell types appear next to each other more often than random? Output is a z-score per cluster pair.
Cell-feature matrix
The Xenium counts file (cell_feature_matrix.h5), produced by 10x Onboard analysis, that contains per-cell counts of every probe in the panel. Load Xenium reads it together with cells.parquet so the counts and centroids stay aligned.