04 · Spatial analysis (squidpy-GPU)#

Mirrors the rapids-singlecell squidpy tutorial. metal-SingleCell’s msc.gr namespace is a drop-in for squidpy.gr, GPU-accelerated on Apple silicon. We use squidpy’s IMC (imaging mass cytometry) dataset — it has cell-type labels and channel intensities, so we can show spatial autocorrelation and the fused-kernel co-occurrence analysis.

import warnings; warnings.filterwarnings('ignore')
import numpy as np, scanpy as sc, squidpy as sq
import metalsinglecell as msc
adata = sq.datasets.imc()
adata
AnnData object with n_obs × n_vars = 4668 × 34
    obs: 'cell type'
    uns: 'cell type_colors'
    obsm: 'spatial'

Spatial neighbors graph#

Builds obsp['spatial_connectivities'] from obsm['spatial'].

msc.gr.spatial_neighbors(adata, n_neighs=6)
adata.obsp['spatial_connectivities'].shape
(4668, 4668)

Moran’s I — global spatial autocorrelation#

Which channels vary smoothly across the tissue?

msc.gr.spatial_autocorr(adata, mode='moran', genes=adata.var_names.tolist(), n_perms=100)
adata.uns['moranI'].head(8)
I pval_sim
3521227Gd155Di Slug 0.770918 0.009901
6967Gd160Di CD44 0.528501 0.009901
1031747Er167Di ECadhe 0.473841 0.009901
234832Lu175Di panCyto 0.463043 0.009901
971099Nd144Di Cytoker 0.412303 0.009901
phospho mTOR 0.411461 0.009901
346876Sm147Di Keratin 0.396131 0.009901
98922Yb174Di Cytoker 0.393929 0.009901

Geary’s C — a complementary autocorrelation statistic#

msc.gr.spatial_autocorr(adata, mode='geary', genes=adata.var_names.tolist(), n_perms=100)
adata.uns['gearyC'].head(8)
C pval_sim
92964Er166Di Carboni 1.040275 0.702970
117792Dy163Di GATA3 0.988649 0.534653
Area 0.929862 0.009901
8001752Sm152Di CD3epsi 0.925628 0.425743
phospho S6 0.919657 0.069307
322787Nd150Di cMyc 0.895445 0.346535
378871Yb172Di vWF 0.861285 0.009901
1441101Er168Di Ki67 0.853448 0.009901

Co-occurrence — how cell types cluster vs distance#

co_occurrence uses a fused Metal atomic-histogram kernel (tiled, scales to large sections). It measures the enrichment of each cell type around a target type as a function of radius.

msc.gr.co_occurrence(adata, cluster_key='cell type', interval=50)
occ = adata.uns['cell type_co_occurrence']['occ']
print('co-occurrence tensor (types × types × radii):', occ.shape)
co-occurrence tensor (types × types × radii): (11, 11, 50)
try:
    sq.pl.co_occurrence(adata, cluster_key='cell type',
                        clusters=list(adata.obs['cell type'].cat.categories[:3]))
except Exception as e:
    print('squidpy plot skipped:', e)
../_images/9b98589416d4920a43f3dc234f715f6d1d8b8cc60249244a036cbcff31fb4581.png

Visualize the tissue#

Top spatially-autocorrelated channel and the cell-type map.

top_gene = adata.uns['moranI'].index[0]
sq.pl.spatial_scatter(adata, color=[top_gene, 'cell type'], shape=None, size=8)
WARNING: Please specify a valid `library_id` or set it permanently in `adata.uns['spatial']`
../_images/21db27d2670a3bab15e06116bc9e06778007420f201c313284412c04546da75f.png

All spatial graph statistics (spatial_neighbors, spatial_autocorr for Moran/Geary, co_occurrence) ran on the Apple GPU and match squidpy’s CPU results exactly.