Spatial Interaction Analysis#

Understanding how different cell types and signaling molecules are spatially organized within tissues is fundamental to deciphering the mechanisms of cell-cell communication and tissue function. This notebook demonstrates spatial interaction analysis using the exprmat package, covering both density-based and graph-based approaches. We construct expression density maps from spatial imaging data to visualize the spatial distribution of specific markers and cell types, compute density intersections to identify colocalization hotspots, and perform permutation-based spatial ligand-receptor interaction testing. The notebook also demonstrates CellNEST analysis for inferring cell-cell communication networks from spatial transcriptomics data, identifying ligand-receptor pairs and signaling relays that mediate intercellular interactions within the tissue microenvironment.

[1]:
%load_ext autoreload
%autoreload 2
[2]:
import exprmat as em
em.setwd('../../../data')
ver = em.version()
[i] exprmat 0.2.66 / exprmat-db 0.2.66
[i] os: posix (linux)  platform version: 6.8.0-90-generic
[i] loaded configuration from /home/data/yangz/.exprmatrc
[i] current working directory: /home/data/yangz/packages/exprmat/data
[i] current database directory: /home/data/yangz/packages/database (0.2.66)
[i] resident memory: 776.18 MiB
[i] virtual memory: 5.95 GiB

[3]:
expm = em.load_experiment('expm/codex')
   ━━━━━━━━━━━━━━━━━━━━━━━━━━ loading samples          2 / 2     (00:01 < 00:00)
[!] integrated mudata object is not generated.


Density based interaction#

For dataset with low spatial resolution, expression densities may become a smoothed imputed intensity for interaction partners, and can be easily expanded and diffused to support diffusion ranges.

For high-resolution data but without segmentation masks of good quality, the operations on expression densities will preserve the logical selection of target cell pixels and perform distance analysis. Cell density can provide information on correlations of cell occurance

[4]:
expm.spatial_cell.summary(run_on_samples = ['a-11'])
a-11
├── adjusted  of shape 3733 ✗ 3732 ✗ 6[0] cd4   [1] cd8   [2] dapi  [3] cd45  [4] cd20  [5] cd31
├── flow  of shape 3733 ✗ 3732 ✗ 4[0] r            [1] g            [2] b            [3] probability
├── mask  of shape 3733 ✗ 3732
├── mixed-membrane  of shape 3733 ✗ 3732 ✗ 1[0] mixed
├── origin  of shape 3733 ✗ 3732 ✗ 59[ 0] dapi        [ 1] cd45        [ 2] cd11c       [ 3] bcl2        [ 4] cd90
│       [ 5] foxp3       [ 6] egfr        [ 7] p16         [ 8] pd1         [ 9] cd206
│       [10] cd45ro      [11] il10        [12] cd56        [13] cd11b       [14] cd31
│       [15] cd163       [16] cd21        [17] cd8         [18] pnad        [19] cd20
│       [20] cxcr5       [21] ki67        [22] lag3        [23] cd73        [24] cd16
│       [25] asma        [26] icos        [27] cd25        [28] coliv       [29] pdgfrb
│       [30] cd4         [31] cd68        [32] cd34        [33] vimentin    [34] podoplanin
│       [35] hladr       [36] cxcl12      [37] cd3         [38] fap         [39] cd138
│       [40] tbet        [41] periostin   [42] spp1        [43] s100a8a9    [44] clec9a
│       [45] cd45ra      [46] caix        [47] gzmb        [48] bcat        [49] sox2
│       [50] pdl1        [51] mmp9        [52] tcrgd       [53] cd38        [54] cd69
│       [55] cd15        [56] ido1        [57] mct1        [58] panck
└── segmentation-rgb  of shape 3733 ✗ 3732 ✗ 3
        [0] r  [1] g  [2] b
[5]:
expm.spatial_cell.image_auto_histogram(
    run_on_samples = True,
    channels = [
        'origin/cd4', 'origin/cd8', 'origin/dapi', 'origin/cd45',
        'origin/cd20', 'origin/cd31', 'origin/foxp3'
    ],
    key_added = 'adjusted',
    low_pct = 0.5,
    high_pct = 99.5
)
[7]:
fig = expm.spatial_cell.plot_spatial(
    run_on_samples = ['a-11'],
    channels = ['adjusted/cd4', 'adjusted/foxp3', 'adjusted/cd20'],
    channel_colors = ['red', 'green', "#4c85ff"],
    channel_intensities = [1, 1, 1],
    plot_embeddings = { 'visible': False, },
    figsize = (5, 5),
    ticks = True
)
../_images/spatial_i1-interaction_9_0.png
[8]:
expm.spatial_cell.density_image(
    run_on_samples = ['a-11'],
    source_channel = ['adjusted/cd8', 'adjusted/cd4', 'adjusted/cd20', 'adjusted/foxp3'],
    target_image = 'density',
    target_channel = None,
    downresolution = 16
)
[9]:
expm.spatial_cell.summary(
    run_on_samples = ['a-11'],
)
a-11
├── adjusted  of shape 3733 ✗ 3732 ✗ 7[0] cd4    [1] cd8    [2] dapi   [3] cd45   [4] cd20   [5] cd31   [6] foxp3
├── flow  of shape 3733 ✗ 3732 ✗ 4[0] r            [1] g            [2] b            [3] probability
├── mask  of shape 3733 ✗ 3732
├── mixed-membrane  of shape 3733 ✗ 3732 ✗ 1[0] mixed
├── origin  of shape 3733 ✗ 3732 ✗ 59[ 0] dapi        [ 1] cd45        [ 2] cd11c       [ 3] bcl2        [ 4] cd90
│       [ 5] foxp3       [ 6] egfr        [ 7] p16         [ 8] pd1         [ 9] cd206
│       [10] cd45ro      [11] il10        [12] cd56        [13] cd11b       [14] cd31
│       [15] cd163       [16] cd21        [17] cd8         [18] pnad        [19] cd20
│       [20] cxcr5       [21] ki67        [22] lag3        [23] cd73        [24] cd16
│       [25] asma        [26] icos        [27] cd25        [28] coliv       [29] pdgfrb
│       [30] cd4         [31] cd68        [32] cd34        [33] vimentin    [34] podoplanin
│       [35] hladr       [36] cxcl12      [37] cd3         [38] fap         [39] cd138
│       [40] tbet        [41] periostin   [42] spp1        [43] s100a8a9    [44] clec9a
│       [45] cd45ra      [46] caix        [47] gzmb        [48] bcat        [49] sox2
│       [50] pdl1        [51] mmp9        [52] tcrgd       [53] cd38        [54] cd69
│       [55] cd15        [56] ido1        [57] mct1        [58] panck
├── segmentation-rgb  of shape 3733 ✗ 3732 ✗ 3[0] r  [1] g  [2] b
└── density  of shape 233 ✗ 233 ✗ 4
        [0] cd8    [1] cd4    [2] cd20   [3] foxp3

For example, the combinated density of CD4+ FOXP3+ regulatory T cells can be masked as follows:

[11]:
expm.spatial_cell.density_intersection(
    run_on_samples = ['a-11'],
    source_densities = ['density/cd4', 'density/foxp3'],
    diffusion = 2,
    target_image = 'treg',
    target_channel = 'density'
)

This is the double-positive density mask, compared to the CD4 single mask

[12]:
fig = expm.spatial_cell.plot_spatial(
    run_on_samples = ['a-11'],
    channels = ['treg/density'],
    channel_colors = ["white"],
    channel_intensities = [1],
    plot_embeddings = { 'visible': False, },
    figsize = (5, 5),
    ticks = True
)
../_images/spatial_i1-interaction_15_0.png
[13]:
fig = expm.spatial_cell.plot_spatial(
    run_on_samples = ['a-11'],
    channels = ['density/cd4'],
    channel_colors = ["white"],
    channel_intensities = [1],
    plot_embeddings = { 'visible': False, },
    figsize = (5, 5),
    ticks = True
)
../_images/spatial_i1-interaction_16_0.png

Or we may further watch for the colocalization area between Tregs and CD8+ T cells

[18]:
expm.spatial_cell.density_intersection(
    run_on_samples = ['a-11'],
    source_densities = ['density/cd4', 'density/foxp3', 'density/cd8'],
    diffusion = 0,
    target_image = 'treg-t8',
    target_channel = 'density'
)
[19]:
fig = expm.spatial_cell.plot_spatial(
    run_on_samples = ['a-11'],
    channels = ['treg-t8/density'],
    channel_colors = ["white"],
    channel_intensities = [1],
    plot_embeddings = { 'visible': False, },
    figsize = (5, 5),
    ticks = True
)
../_images/spatial_i1-interaction_19_0.png

Initialize density maps from cell density#

Density maps can also be kernel density estimates from scatters

[32]:
expm.spatial_cell['a-11'].obs[['x', 'y', 'leiden', 'cell.type']]
[32]:
x y leiden cell.type
a-11:c1 2100.405405 123.478764 18 Unk
a-11:c2 2130.552743 123.552743 5 B
a-11:c3 2149.627451 125.058824 18 Unk
a-11:c4 1553.190476 123.396825 13 Unk
a-11:c5 2209.738532 125.977064 5 gdT
... ... ... ... ...
a-11:c48512 1731.616099 3574.241486 13 gdT
a-11:c48513 1774.181435 3574.426160 13 T4
a-11:c48514 1699.267399 3576.263736 11 Epi
a-11:c48515 1754.540179 3575.816964 9 Unk
a-11:c48516 1829.691120 3576.455598 13 gdT

47396 rows × 4 columns

[33]:
expm.spatial_cell.density_from_spots(
    run_on_samples = ['a-11'],
    category = 'cell.type',
    source_channel = 'origin/dapi',
    target_image = 'ctdens',
    downresolution = 16,
)
[34]:
expm.spatial_cell.summary(
    run_on_samples = ['a-11'],
)
a-11
├── adjusted  of shape 3733 ✗ 3732 ✗ 7[0] cd4    [1] cd8    [2] dapi   [3] cd45   [4] cd20   [5] cd31   [6] foxp3
├── flow  of shape 3733 ✗ 3732 ✗ 4[0] r            [1] g            [2] b            [3] probability
├── mask  of shape 3733 ✗ 3732
├── mixed-membrane  of shape 3733 ✗ 3732 ✗ 1[0] mixed
├── origin  of shape 3733 ✗ 3732 ✗ 59[ 0] dapi        [ 1] cd45        [ 2] cd11c       [ 3] bcl2        [ 4] cd90
│       [ 5] foxp3       [ 6] egfr        [ 7] p16         [ 8] pd1         [ 9] cd206
│       [10] cd45ro      [11] il10        [12] cd56        [13] cd11b       [14] cd31
│       [15] cd163       [16] cd21        [17] cd8         [18] pnad        [19] cd20
│       [20] cxcr5       [21] ki67        [22] lag3        [23] cd73        [24] cd16
│       [25] asma        [26] icos        [27] cd25        [28] coliv       [29] pdgfrb
│       [30] cd4         [31] cd68        [32] cd34        [33] vimentin    [34] podoplanin
│       [35] hladr       [36] cxcl12      [37] cd3         [38] fap         [39] cd138
│       [40] tbet        [41] periostin   [42] spp1        [43] s100a8a9    [44] clec9a
│       [45] cd45ra      [46] caix        [47] gzmb        [48] bcat        [49] sox2
│       [50] pdl1        [51] mmp9        [52] tcrgd       [53] cd38        [54] cd69
│       [55] cd15        [56] ido1        [57] mct1        [58] panck
├── segmentation-rgb  of shape 3733 ✗ 3732 ✗ 3[0] r  [1] g  [2] b
├── density  of shape 233 ✗ 233 ✗ 4[0] cd8    [1] cd4    [2] cd20   [3] foxp3
├── treg  of shape 3728 ✗ 3728 ✗ 1[0] density
├── treg-t8  of shape 3728 ✗ 3728 ✗ 1[0] density
└── ctdens  of shape 233 ✗ 233 ✗ 11
        [ 0] b     [ 1] ec    [ 2] epi   [ 3] fib   [ 4] mye   [ 5] nk    [ 6] t4    [ 7] t8
        [ 8] treg  [ 9] unk   [10] gdt
[36]:
fig = expm.spatial_cell.plot_spatial(
    run_on_samples = ['a-11'],
    channels = ['ctdens/t8'],
    channel_colors = ["white"],
    channel_intensities = [1],
    plot_embeddings = { 'visible': False, },
    figsize = (5, 5),
    ticks = True
)
../_images/spatial_i1-interaction_24_0.png

Gene expression densities can also be derived

[6]:
expm.spatial_cell.density_from_expression(
    run_on_samples = ['a-11'],
    feature = 'CD8',
    layer = 'compensated',
    source_channel = 'origin/dapi',
    target_image = 'expression',
    downresolution = 16,
)
[9]:
fig = expm.spatial_cell.plot_spatial(
    run_on_samples = ['a-11'],
    channels = ['expression/cd8'],
    channel_colors = ["white"],
    channel_intensities = [1],
    plot_embeddings = { 'visible': False, },
    figsize = (5, 5),
    ticks = True
)
../_images/spatial_i1-interaction_27_0.png

Downstream analysis of density maps#

Density maps can be useful as a proxy of smoothed gene expression in spatial context.

[25]:
fig = expm.spatial_cell.plot_density_correlation(
    run_on_samples = ['a-11'],
    x_channels = ['treg-t8/density', 'treg/density', 'density/cd4', 'density/cd8', 'density/cd20'],
    y_channels = ['treg-t8/density', 'treg/density', 'density/cd4', 'density/cd8', 'density/cd20'],
    cmap = 'rdbu/r',
    figsize = (3, 2.5),
    dpi = 100,
)
../_images/spatial_i1-interaction_29_0.png
[28]:
fig = expm.spatial_cell.plot_density_cooccurance(
    run_on_samples = ['a-11'],
    x_channel = 'treg/density',
    y_channel = 'density/cd8',
    x_threshold = 0.8,
    n_permutations = 100,
    max_distance = None,
    near_threshold = 50,
    far_threshold = 200,
    n_distance_bins = 50,
    random_state = 42,
    figsize = (8, 3),
)
../_images/spatial_i1-interaction_30_0.png

Spatial Ligand-receptor Permutation Test#

CellChat’s permutation test variation is implemented for spatial data.

[6]:
expm = em.load_experiment('expm/visium')
   ━━━━━━━━━━━━━━━━━━━━━━━━━━ loading samples          8 / 8     (00:03 < 00:00)
[5]:
sample = 'p2-t5'
[16]:
expm.spatial_bin.view(sample)
annotated data of size 1470 × 35554
    obs : sample <cat> <c/sample> batch <cat> <c/batch> group <cat> <c> modality <cat> <c/modality>
          taxa <cat> <c/taxa> barcode <o> <o> ubc <o> <o> in.tissue <i64> <bool/intissue>
          row <i64> <f/coordinate/row> col <i64> <f/coordinate/col> y <i64> <f/coordinate/y>
          x <i64> <f/coordinate/x> leiden <cat> <c> spagcn.domain <cat> <c/domain>
          spagcn.refined <cat> <c/domain> imagerow <f64> imagecol <f64>
          deepst.domain <cat> <c/domain> deepst.refined <cat> <c/domain> integrated <cat>
          metagene.17 <f64> <f> ep1 <f32> ep2 <f32>
    var : chr <cat> <c/chromosome> start <i64> <i> end <i64> <i> strand <cat> <c/strand> id <o> <o>
          subtype <cat> <c/gsubtype> gene <cat> <o/gene> tlen <f64> <i/tlen> cdslen <i64> <i/cdslen>
          assembly <cat> <c> uid <o> <o/ugene> vst.means <f64> <f> vst.vars <f64> <f>
          vst.vars.norm <f64> <f> vst.hvg.rank <f32> <i/rank> vst.hvg <bool> <bool/hvg>
          spatialde.padj <f64> <f> spatialde.svg <bool> <bool/hvg/svg> spatialde.rank <f64> <i/rank>
 layers : counts <f32> <i/counts> imputed <f32> <f/normal> lognorm <f32> <f/normal>
          norm <f32> <f/linear>
   obsm : adjacent <arr:f64(35554)> augmented.genes <arr:f64(35554)>
          deepst.embedding <arr:f32(28)> <f/embedding> knn <arr:i32(30)> knn.d <arr:f64(30)>
          pca <arr:f64(35)> <f/embedding/pca> spatial <arr:i64(2)> <f/coordinate:2d/embedding>
          spatial.array <arr:i64(2)> tangram <df> <weights> umap <arr:f32(2)> <f/embedding>
          weights <arr:f64>
   varm : pca <arr:f64(35)> <f/weights>
   obsp : connectivities <csr:f32> distances <csr:f64>
    uns : commands <system> deepst.domain leiden neighbors <knn> pca <dict> slots <system>
          spagcn.markers <markers> spatial <spatial> tangram <tangram> umap
[26]:
expm.spatial_bin.ligand_receptor(
    run_on_samples = [sample],
    cluster_key = 'spagcn.refined',
    gene_symbol = 'gene',
    resource_name = 'consensus',
    taxa_source = 'hsa',
    taxa_dest = 'hsa',
    threshold = 0.1,
    n_perms = 1000,
    seed = 0,
    corr_method = 'fdr_bh',
    corr_axis = 'clusters',
    key_added = 'lr',
    layer = 'X'
)
[i] running ligrec with 4712 interactions, 7 clusters, 1000 permutations
[i] applying fdr correction (fdr_bh) across clusters
[i] stored ligrec results in adata.uns['lr']

[27]:
expm.spatial_bin[sample].uns['lr']
[27]:
ligand receptor source target means pvalues
0 A2M LRP1 0 0 0.544752 0.311821
1 A2M LRP1 0 1 0.410884 1.000000
2 A2M LRP1 0 2 0.432022 1.000000
3 A2M LRP1 0 3 0.418456 1.000000
4 A2M LRP1 0 4 0.502908 1.000000
... ... ... ... ... ... ...
230883 ZP3 MERTK 6 2 NaN 1.000000
230884 ZP3 MERTK 6 3 NaN 1.000000
230885 ZP3 MERTK 6 4 NaN 0.005682
230886 ZP3 MERTK 6 5 NaN 1.000000
230887 ZP3 MERTK 6 6 NaN 1.000000

230888 rows × 6 columns

CellNEST#

CellNEST takes explicit consideration of the spatial mapping. However, it is calculation intense work and only recommended to run on sparse bin-based metrics within reasonable runtime.

[7]:
expm.spatial_bin.cellnest(
    run_on_samples = [sample],
    taxa = 'hsa',
    key_added = 'cellnest',
    layer = 'counts',
    resource = 'cellnest',
    resource_table = None,
    device = 'auto',
    quick = True
)
[i] quick mode: num_runs = 2, num_epochs = 2000.
[i] cellnest stage 1/3: building communication graph ...
[i] gene filter: 35554 -> 18790 (min_cells = 1).
[i] quantile-normalizing expression matrix ...
[i] building neighborhood graph ...
[i] juxtacrine cutoff = 200.002 (same units as coordinates).
[i] loaded 12605 ligand-receptor pairs from hsa/cellnest.
[i] usable lr pairs: 5994 over 534 ligands.
[i] genes acting as ligand or receptor: 1084 / 18790 total.
[i] enumerating cell-cell edges across 534 ligand genes ...
[i] built communication graph: 1470 nodes, 1309631 edges.
[i] cellnest stage 2/3: training dgi for 2 runs ...
[i] cellnest training: 2 runs with seeds [1826701614, 1367864806].
[i] run 1 / 2 (seed = 1826701614) ...
[i] training cellnest dgi (seed = 1826701614, epochs = 2000, hidden = 512, heads = 1, device = cuda).
[i]   epoch      1: loss = 1.3886
[i]   epoch    501: loss = 0.5906
[i]   epoch   1001: loss = 0.3678
[i]   epoch   1501: loss = 0.2161
[i] run 2 / 2 (seed = 1367864806) ...
[i] training cellnest dgi (seed = 1367864806, epochs = 2000, hidden = 512, heads = 1, device = cuda).
[i]   epoch      1: loss = 1.3899
[i]   epoch    501: loss = 0.5976
[i]   epoch   1001: loss = 0.1334
[i]   epoch   1501: loss = 0.1107
[i] cellnest stage 3/3: ensembling runs ...
[i]   ensembling layer 0 ...
[i]   ensembling layer 1 ...
[i] rescaling per-layer scores to [0, 1] ...
[i] aggregated 1259630 unique communications across 2 runs.
[i] attention-score distribution skewness = 3.4972.
[i] results stored under adata.uns["cellnest"].

[51]:
fig = expm.spatial_bin.plot_attention_distribution_cellnest(
    run_on_samples = [sample],
    key_added = 'cellnest',
    bins = 80,
    figsize = (4, 2)
)
../_images/spatial_i1-interaction_39_0.png
[50]:
fig = expm.spatial_bin.plot_lr_frequency_cellnest(
    run_on_samples = [sample],
    key_added = 'cellnest',
    top_n = 30,
    figsize = (8, 3)
)
../_images/spatial_i1-interaction_40_0.png
[49]:
fig = expm.spatial_bin.plot_communications_cellnest(
    run_on_samples = [sample],
    key_added = 'cellnest',
    min_attention_score = 0.06,
    basis = 'spatial',
    point_color = None,
    linkage_color = 'ligand',
    linkage_cmap = 'set1',
    edge_alpha = 0.6,
    edge_width = 2,
    max_edges = 1000,
    figsize = (5.5, 4.5),
    legend_col = 2
)
../_images/spatial_i1-interaction_41_0.png
[34]:
expm.spatial_bin[sample].uns['cellnest']['communications']
[34]:
from_cell to_cell ligand receptor edge_rank component from_id to_id attention_score
0 p2-t5:305 p2-t5:317 BMP2 FGFR2 1 -1 304 316 0.426324
1 p2-t5:998 p2-t5:635 WNT3A FZD2 2 -1 997 634 0.345302
2 p2-t5:527 p2-t5:102 INHBA ACVR1B 3 -1 526 101 0.365205
3 p2-t5:812 p2-t5:102 INHBA ACVR1B 4 -1 811 101 0.367952
4 p2-t5:1219 p2-t5:1414 WNT5A TFRC 5 -1 1218 1413 0.333475
... ... ... ... ... ... ... ... ... ...
251921 p2-t5:487 p2-t5:559 CCL5 C3AR1 251922 -1 486 558 0.162639
251922 p2-t5:496 p2-t5:524 MDK NCL 251923 -1 495 523 0.079774
251923 p2-t5:1200 p2-t5:1281 JAG2 SEC63 251924 -1 1199 1280 0.052665
251924 p2-t5:259 p2-t5:48 PIK3CB CD247 251925 -1 258 47 0.042083
251925 p2-t5:306 p2-t5:1144 CXCL2 C5AR1 251926 -1 305 1143 0.127665

251926 rows × 9 columns

[35]:
expm.spatial_bin.cellnest_extract_relay(
    run_on_samples = [sample],
    key_added = 'cellnest',
    max_top = 200,
    score_confidence = True,
    identity = 'spagcn.refined', # cell type label
    taxa = 'hsa',
    activation_only = True
)
[i] extracted 4 unique 2-hop relay patterns.
[i] loading signaling and gene-regulatory networks ...
[i] pathway graph: 24754 sources, 8419668 edges, 5206 tf genes.
[i] scored 4 / 4 relays with valid paths.

[48]:
fig = expm.spatial_bin.plot_relays_cellnest(
    run_on_samples = [sample],
    key_added = 'cellnest',
    top_n = 30,
    figsize = (2.3, 5)
)
../_images/spatial_i1-interaction_44_0.png
[42]:
expm.spatial_bin.view(sample)
annotated data of size 1470 × 35554
    obs : sample <cat> <c/sample> batch <cat> <c/batch> group <cat> <c> modality <cat> <c/modality>
          taxa <cat> <c/taxa> barcode <o> <o> ubc <o> <o> in.tissue <i64> <bool/intissue>
          row <i64> <f/coordinate/row> col <i64> <f/coordinate/col> y <i64> <f/coordinate/y>
          x <i64> <f/coordinate/x> leiden <cat> <c> spagcn.domain <cat> <c/domain>
          spagcn.refined <cat> <c/domain> imagerow <f64> imagecol <f64>
          deepst.domain <cat> <c/domain> deepst.refined <cat> <c/domain> integrated <cat>
          metagene.17 <f64> <f> ep1 <f32> ep2 <f32>
    var : chr <cat> <c/chromosome> start <i64> <i> end <i64> <i> strand <cat> <c/strand> id <o> <o>
          subtype <cat> <c/gsubtype> gene <cat> <o/gene> tlen <f64> <i/tlen> cdslen <i64> <i/cdslen>
          assembly <cat> <c> uid <o> <o/ugene> vst.means <f64> <f> vst.vars <f64> <f>
          vst.vars.norm <f64> <f> vst.hvg.rank <f32> <i/rank> vst.hvg <bool> <bool/hvg>
          spatialde.padj <f64> <f> spatialde.svg <bool> <bool/hvg/svg> spatialde.rank <f64> <i/rank>
 layers : counts <f32> <i/counts> imputed <f32> <f/normal> lognorm <f32> <f/normal>
          norm <f32> <f/linear>
   obsm : adjacent <arr:f64(35554)> augmented.genes <arr:f64(35554)>
          deepst.embedding <arr:f32(28)> <f/embedding> knn <arr:i32(30)> knn.d <arr:f64(30)>
          pca <arr:f64(35)> <f/embedding/pca> spatial <arr:i64(2)> <f/coordinate:2d/embedding>
          spatial.array <arr:i64(2)> tangram <df> <weights> umap <arr:f32(2)> <f/embedding>
          weights <arr:f64> cellnest.embedding <arr:f64(512)> <f/embedding>
   varm : pca <arr:f64(35)> <f/weights>
   obsp : connectivities <csr:f32> distances <csr:f64>
    uns : commands <system> deepst.domain leiden neighbors <knn> pca <dict> slots <system>
          spagcn.markers <markers> spatial <spatial> tangram <tangram> umap cellnest <cellnest>
          cellnest.relays
[47]:
expm.save()
[i] main dataset write to expm/visium/integrated.h5mu
[i] saving individual samples. (pass `save_samples = False` to skip)

   ━━━━━━━━━━━━━━━━━━━━━━━━ modality [spatial-bin]     8 / 8     (00:10 < 00:00)