Skip to content

Downstream analysis

ALHENA provides complementary representations of an H&E image at tile and cell scales. Pre-projection tile and cell embeddings can be explored independently, while projected cell embeddings support reference-based cell-type annotation. When spatial coordinates are available, these results can also be related directly on the slide.

This page shows common downstream analyses with Scanpy, Squidpy, and SpatialData. These tools are not required by ALHENA; they are examples of how the returned representations can be analyzed.

One H&E region with tile embedding clusters, cell embedding clusters, and predicted cell types

One 2.3 mm region shown as H&E, tile embedding clusters, cell embedding clusters, and predicted cell types. Cluster colors represent unsupervised groups in the embedding spaces and are not predefined biological classes. Tutorial 4 computes each layer. H&E: FFPE Human Breast with Custom Add-on Panel, 10x Genomics, CC BY 4.0; cropped, downsampled and overlaid with ALHENA results.

flowchart TD
    HE[H&E slide] --> TE["Pre-projection tile embedding<br/>X_alhena_tile"]
    HE --> CE["Pre-projection cell embedding<br/>X_alhena_backbone"]
    TE --> TC[Tile embedding clusters]
    CE --> CC[Cell embedding clusters]
    CE --> CP["Projection head"]
    CP --> CS["Shared cell space<br/>X_alhena_cell"]
    CS --> CT[Cell types]
    CT --> NB[Cell-type neighborhoods]
    POS[Spatial coordinates] --> NB
    TC --> MS[Multi-scale analysis]
    CC --> MS
    CT --> MS
    NB --> MS

The examples below start from two results: tiles, returned by encode_tiles(), and result, returned by annotate() on the default axis, lineage_v1. Each converts to an AnnData table with to_anndata(), and the analyses run on those tables. The columns of an annotation are named after its axis, so the cell types of result are in alhena_celltype_lineage_v1. Tutorial 4 runs every step below on one slide region, with its figures.

Which representation for which question

Question Representation Typical analysis
Which tissue regions have similar ALHENA representations? X_alhena_tile neighbors, UMAP, clustering into tile embedding clusters
Which cells have similar image-derived representations? X_alhena_backbone neighbors, UMAP, clustering into cell embedding clusters
How are cells arranged in the shared space used for annotation? X_alhena_cell visualization of annotation/retrieval geometry
Which cell type was assigned to each cell? alhena_celltype_lineage_v1 composition summaries
Which cell types occur near one another in physical space? spatial + alhena_celltype_lineage_v1 spatial-neighbor analyses
How do tile- and cell-level results relate? clusters, cell types, and shared spatial coordinates cross-scale analyses

Embedding space and physical space

These coordinate systems answer different questions. Embedding space measures similarity between ALHENA representations and is used for nearest-neighbor graphs, dimensionality reduction, and clustering. Physical space represents positions on the slide and is used for spatial-neighbor and neighborhood analyses.

Similarity in embedding space does not imply physical proximity on the slide, and spatially adjacent observations do not necessarily have similar embeddings.

Tile embedding clusters

Clustering X_alhena_tile groups tiles with similar ALHENA representations. These groups are tile embedding clusters; they are not predefined tissue classes.

import scanpy as sc

tile_table = tiles.to_anndata()
sc.pp.neighbors(tile_table, use_rep="X_alhena_tile")
sc.tl.umap(tile_table)
sc.tl.leiden(
    tile_table,
    key_added="tile_cluster",
    resolution=0.5,
    flavor="igraph",
    n_iterations=2,
)

Interpret each cluster by examining the corresponding H&E regions and their spatial distribution before attaching a biological description. The Leiden resolution parameter controls clustering granularity.

ax = sc.pl.embedding(tile_table, basis="spatial", color="tile_cluster", show=False)
ax.invert_yaxis()

Spatial coordinates allow the clusters identified in embedding space to be mapped back onto the slide. This step requires tiles with spatial bounds. viz.tile_regions() draws each tile region in the color of its cluster, over the part of the slide io.read_region() reads. Given the table and the name of a column, it uses the colors Scanpy stored for that column:

from alhena import io, viz

he, extent = io.read_region("slide.svs", window=tiles.bounds, downsample=8)
viz.tile_regions(tile_table, "tile_cluster", image=he, extent=extent)

Cell embedding clusters

The same workflow can be applied to X_alhena_backbone, ALHENA's pre-projection cell representation. Keep the cells whose embedding is valid:

cell_table = result.to_anndata()
cell_table = cell_table[cell_table.obs["alhena_embedding_valid"]].copy()

sc.pp.neighbors(cell_table, use_rep="X_alhena_backbone")
sc.tl.umap(cell_table)
sc.tl.leiden(
    cell_table,
    key_added="cell_cluster",
    resolution=0.3,
    flavor="igraph",
    n_iterations=2,
)

The resulting cell embedding clusters describe unsupervised structure in the image-derived representation and should not be interpreted automatically as biological cell states or cell types.

These clusters do not depend on the single-cell reference used for annotation.

Inspect cell clusters in spatial context

Each cell embedding includes information pooled from a nucleus whose image tokens were computed within a surrounding tile. Cells from the same tile therefore share some contextual information. Map cell embedding clusters back to the slide before interpreting them, particularly if cluster boundaries appear strongly aligned with tile boundaries.

The shared cell space

X_alhena_cell holds the same cells in the shared cell space, where annotation happens:

pre-projection cell embedding
X_alhena_backbone
        ↓ img_tok_proj
shared cell-space embedding
X_alhena_cell
        ↓ reference retrieval
cell-type annotation

X_alhena_cell can be visualized, but it represents a different analysis space from X_alhena_backbone. It is specifically optimized for comparison with the single-cell reference.

sc.pp.neighbors(cell_table, use_rep="X_alhena_cell", key_added="cell_space")
sc.tl.umap(cell_table, neighbors_key="cell_space", key_added="X_umap_cell_space")
sc.pl.embedding(cell_table, basis="X_umap_cell_space", color="alhena_celltype_lineage_v1")

The table stores the colors of the cell types beside them, so Scanpy draws the cell types in the colors of alhena.viz.

Separation of annotated cell types in this space is therefore partly expected because the annotations themselves are obtained by retrieval in the same space. Use the pre-projection representation for general unsupervised analysis of cellular variation. The shared cell space says how it relates to scConcept.

Cell embedding clusters and cell types

Cell embedding clusters and reference-derived cell types provide complementary descriptions of the cells:

  • cell embedding clusters are obtained by unsupervised clustering of X_alhena_backbone;
  • cell types are assigned by reference retrieval after projection into X_alhena_cell.
import pandas as pd

sc.pl.umap(cell_table, color=["cell_cluster", "alhena_celltype_lineage_v1"])
pd.crosstab(cell_table.obs["cell_cluster"], cell_table.obs["alhena_celltype_lineage_v1"])

Comparing them can reveal whether a reference-defined type forms one or several image-derived clusters, or whether a cluster contains several predicted cell types. These relationships should be interpreted from the underlying H&E and relevant biological context rather than treated as expected agreement.

Cell types in physical space

Cell-type annotations can also be analyzed using the physical positions of the cells. The example below retains cells with valid annotations and constructs a spatial-neighbor graph with a maximum distance of 30 µm:

import squidpy as sq

cell_type = "alhena_celltype_lineage_v1"
called = cell_table[cell_table.obs["alhena_valid_lineage_v1"]].copy()
mpp = called.uns["alhena_cells"]["centroids_mpp"]    # microns per pixel of the positions

sq.gr.spatial_neighbors_delaunay(called, radius=(0, 30 / mpp))
sq.gr.nhood_enrichment(called, cluster_key=cell_type)
sq.pl.nhood_enrichment(called, cluster_key=cell_type)

The resulting spatial graph and the cell positions can support analyses such as:

  • where each cell type lies on the slide;
  • neighborhood enrichment: which types neighbor each other more, or less, than chance;
  • co-occurrence of types as a function of distance, with sq.gr.co_occurrence;
  • cellular niches, from the composition of each cell's neighbors.

Positions are in the pixels of centroids_frame. centroids_mpp converts between those pixels and microns, as for the 30 µm distance above. The graph functions shown need Squidpy 1.8.2 or newer.

Across scales

Tile- and cell-level results can be related when they share the same spatial context.

tiles.locate() names the tile whose region holds each cell, from the positions of the cells and the spatial bounds of the tiles. The tile table is indexed by the same names, so a tile-level value reaches the cells by name:

cluster_of_tile = tile_table.obs["tile_cluster"]

for table in (cell_table, called):
    table.obs["tile_cluster"] = cluster_of_tile.reindex(tiles.locate(table)).to_numpy()

locate() works whether the tiles and the cells were read in one pass or in two, over the same region or not. The tile bounds and the cell positions must be in the same coordinate frame, and locate() refuses frames that differ. A cell outside every tile gets no tile and no value.

Each cell also records, in alhena_tile_index, the tile its embedding was read from, by its position among the tiles of that pass.

Tile clusters and cell types

by_type = pd.crosstab(
    called.obs["tile_cluster"],
    called.obs["alhena_celltype_lineage_v1"],
    normalize="index",
)

This table summarizes the distribution of reference-derived cell types within each tile embedding cluster.

Tile clusters and cell clusters

by_cluster = pd.crosstab(
    cell_table.obs["tile_cluster"],
    cell_table.obs["cell_cluster"],
    normalize="index",
)

This comparison relates unsupervised regional structure in X_alhena_tile to unsupervised cellular structure in X_alhena_backbone.

The two representations are not fully independent because the cell embedding is pooled from image tokens computed in the context of a tile.

Tile clusters and cell-type neighborhoods

Physical cell neighborhoods can also be summarized within tile embedding clusters to compare local cellular organization across image-derived tissue regions. Tutorial 4 shows an example.

The aligned tile and cell outputs make these questions directly accessible:

  • Which predicted cell types are enriched in each tile embedding cluster?
  • Which cell embedding clusters occur within each tile embedding cluster?
  • How does cellular composition vary across image-derived tissue regions?
  • Do different tile embedding clusters contain different cell-type neighborhoods?

The appropriate downstream statistical model depends on the biological question and study design.

Keep the scales together

A SpatialData object keeps every layer of the analysis in one registered spatial context:

SpatialData
├── images
│   └── the H&E
├── shapes
│   ├── the tile regions
│   └── the nucleus outlines
└── tables
    ├── the tile table    tile embeddings, tile embedding clusters
    └── the cell table    pre-projection and cell-space embeddings, cell types, cell embedding clusters

That matters for multi-scale work:

  • the H&E image, nuclei, and ALHENA results remain registered in shared coordinate systems;
  • tile clusters remain associated with their spatial regions;
  • cell annotations and cell clusters remain associated with cell positions or nucleus shapes;
  • coordinate transformations are preserved rather than recreated manually;
  • spatial queries can retrieve several analysis layers from the same region.

Two calls write ALHENA's results into an object that already holds the image and the nuclei:

from alhena import spatial

spatial.tiles_to_spatialdata(sdata, tiles, image_key="he")
spatial.cells_to_spatialdata(sdata, result, region="nucleus_boundaries")

Part F of Tutorial 4 builds such an object from a slide, adds the embedding clusters, and queries it. See Writing into a SpatialData object.

Scanpy, Squidpy and SpatialData

Tool Role Works on
Scanpy similarity structure, dimensionality reduction, and clustering in ALHENA embedding spaces X_alhena_tile, X_alhena_backbone, X_alhena_cell
Squidpy spatial-neighbor and neighborhood analyses cell positions with annotations or cluster labels
SpatialData registration and joint storage of images, nuclei, ALHENA tables, and coordinate transformations spatial layers from the same analysis

These packages are optional downstream tools rather than components of the ALHENA model. ALHENA embeddings are image-derived representation features and should not be interpreted as gene-expression matrices.

Advanced: comparing slides and cohorts

ALHENA produces tile- and cell-level representations rather than a predefined slide-level embedding. Slide- or patient-level summaries must therefore be constructed downstream according to the analysis question. Examples include aggregating tile embeddings, summarizing predicted cell-type composition, or training a downstream model using study-specific labels.

Before comparing such summaries across slides or cohorts, consider at least three sources of variation:

  1. Image geometry. Differences in resolution, padding, or cropping can affect tile embeddings independently of tissue morphology. Use comparable preprocessing geometry across datasets. See How large to cut.
  2. Tissue selection. read_slide() does not perform tissue detection. If whole-slide grids are used, background regions can influence tile-level summaries. Define tissue regions consistently before aggregation. See Choosing which tiles.
  3. Reference and annotation axis. Cell-type composition should be compared using compatible references and the same annotation axis. Differences in reference composition can influence retrieval-based annotations. See How reference composition affects annotation.

Next: Persistence and interchange