Tutorial 1: Spatial Analysis of a Single Field of View

This tutorial demonstrates the core SpatialQuery workflow on a single spatial transcriptomics dataset. We use a seqFISH mouse organogenesis (E8.5) embryo section containing 17,806 cells and 351 genes. The analysis covers motif enrichment discovery, spatial visualization, differential expression between motif-positive and motif-negative anchor cells, and cross-cell gene-gene covariation analysis.

API class: spatial_query

1. Setup

[1]:
import warnings
warnings.filterwarnings("ignore")

import anndata as ad
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

from SpatialQuery import spatial_query

2. Load Data & Initialize SpatialQuery

[3]:
DATA_DIR = "../data/mouse_organogenesis"

adata = ad.read_h5ad(f"{DATA_DIR}/embryo1.h5ad")
adata
[3]:
AnnData object with n_obs × n_vars = 17806 × 351
    obs: 'embryo', 'pos', 'z', 'x_global', 'y_global', 'x_global_affine', 'y_global_affine', 'embryo_pos', 'embryo_pos_z', 'Area', 'UMAP1', 'UMAP2', 'celltype_mapped_refined', 'segmentation_vertices_x_global_affine', 'segmentation_vertices_y_global_affine'
    var: 'gene'
    uns: 'celltype_mapped_refined_colors'
    obsm: 'X_spatial', 'X_umap'
[4]:
# ---- Configuration: adjust these to match your dataset ----
spatial_key = "X_spatial"
label_key = "celltype_mapped_refined"
feature_name = "gene"
dataset_name = "mouse_embryo1"
[5]:
sp = spatial_query(
    adata=adata,
    dataset=dataset_name,
    spatial_key=spatial_key,
    label_key=label_key,
    feature_name=feature_name,
    build_gene_index=False,       # use adata.X directly for gene expression
    if_lognorm=True,              # log-normalize raw counts
    if_normalize_spatial_coord=True,
)

Auto-normalizing spatial coordinates: mean nearest neighbor distance = 1.0
Scale factor: 51.7200
build_gene_index is False. Using adata.X for gene expression analysis.
Log normalizing the expression data... If data is already log normalized, please set if_lognorm to False.

3. Visualize the Field of View

[6]:
sp.plot_fov(min_cells_label=0, figsize=(8, 6))
../_images/tutorials_tutorial_1_8_0.png

4. Motif Enrichment Across Cell Types and Radii

A spatial motif is a recurring combination of cell types found in the neighborhood of an anchor cell type. Here we scan all cell types as anchors across a range of radii to get an overview of spatial organization complexity.

[7]:
radius_range = list(range(2, 21, 2))
unique_cts = sp.labels.unique().tolist()

enrich_radius_ct = dict()

for ct in unique_cts:
    enrich_radius_ct[ct] = []

    for r in radius_range:
        enrich_tmp = sp.motif_enrichment_dist(
            ct=ct,
            max_dist=r,
            min_support=0.5,
        )
        enrich_tmp = enrich_tmp[enrich_tmp['if_significant']]
        enrich_radius_ct[ct].append(enrich_tmp.shape[0])

[8]:
enrich_radius_ct_df = pd.DataFrame(enrich_radius_ct, index=radius_range)


fig, ax = plt.subplots(figsize=(8, 6))
sns.heatmap(enrich_radius_ct_df.T,
            cmap='GnBu',
            linewidths=0.1,
            square=True, annot=True, fmt='g',
            annot_kws={'fontsize': 8}, ax=ax, linecolor='white',
            cbar_kws={'label': 'Number of enriched motifs'}
            )
for spine_name, spine in ax.spines.items():
    spine.set_visible(True)
    spine.set_color('black')
    spine.set_linewidth(0.5)
plt.xlabel('Radius')
plt.xticks(rotation=0)
plt.title('Enriched motifs across radii')


[8]:
Text(0.5, 1.0, 'Enriched motifs across radii')
../_images/tutorials_tutorial_1_11_1.png

5. Targeted Motif Enrichment

We select Gut tube as the anchor cell type and discover which cell type combinations are significantly enriched in its neighborhood within a radius of 8 (normalized units).

[9]:
anchor_ct = 'Gut tube'
max_dist = 8
min_support = 0.2

enrich = sp.motif_enrichment_dist(
    ct=anchor_ct,
    max_dist=max_dist,
    min_support=min_support
)

enrich
[9]:
center motifs n_center_motif n_center n_motif expectation p-values adj-pval if_significant
0 Gut tube [Endothelium, Gut tube, Haematoendothelial pro... 748 1464 2439 200.533303 1.260625e-290 7.563750e-290 True
1 Gut tube [Cranial mesoderm, Gut tube] 357 1464 1282 105.405369 4.065308e-107 1.219592e-106 True
2 Gut tube [Endothelium, Gut tube, Splanchnic mesoderm] 320 1464 1188 97.676738 7.788483e-91 1.557697e-90 True
3 Gut tube [Gut tube, Haematoendothelial progenitors, Spl... 332 1464 1433 117.820510 1.252217e-75 1.878326e-75 True
4 Gut tube [Gut tube, Lateral plate mesoderm] 312 1464 1595 131.140065 1.212101e-52 1.454521e-52 True
5 Gut tube [Endothelium, Haematoendothelial progenitors, ... 293 1464 2863 235.394361 1.308209e-05 1.308209e-05 True
[10]:
sp.plot_motif_enrichment_heatmap(
    enrich_df=enrich,
    figsize=(7, 5),
    title='Enriched motifs around Gut tube',
)
../_images/tutorials_tutorial_1_14_0.png

6. Motif Enrichment Across Radii

For a specific motif of interest, we examine how enrichment significance and observed/expected ratio change across radii 1–20.

[11]:
p_vals = []
oe_ratio = []
ct = 'Gut tube'
motif = ['Splanchnic mesoderm', 'Endothelium']
radius_list = range(1, 21)

for i in radius_list:
    tt = sp.motif_enrichment_dist(
        ct=ct,
        motifs=motif,
        max_dist=i
    )
    p_vals.append(tt['p-values'][0])
    oe_ratio.append(tt['n_center_motif'][0]/tt['expectation'][0])
[12]:
p_vals_df = pd.DataFrame({
    'radius': radius_list,
    '-log_pvals': -np.log10(p_vals),
    'O/E ratio': oe_ratio
})

fig, ax = plt.subplots(figsize=(4.5, 3))

cmap = plt.get_cmap('RdBu_r')
oe = p_vals_df['O/E ratio']
scaled = (oe - oe.min()) / (oe.max() - oe.min())
colors = [cmap(v) for v in scaled]

bars = ax.bar(p_vals_df['radius'].astype(str),
              p_vals_df['-log_pvals'],
              color=colors, edgecolor='black', alpha=0.8, linewidth=0.25)

sm = plt.cm.ScalarMappable(cmap=cmap)
sm.set_clim(oe.min(), oe.max())
plt.colorbar(sm, ax=ax, label='O/E ratio')

thr = -np.log10(0.05)
ax.axhline(thr, color='red', linestyle='--', linewidth=1)
ax.text(0.5, thr + 0.05, '-log10(0.05)', color='red', fontsize=10, va='bottom')

ax.set_ylabel('-log10(p)')
ax.set_xlabel('radius')
ax.set_title(f'motif={motif} \n surrounding {ct}')
plt.tight_layout()
plt.show()
../_images/tutorials_tutorial_1_17_0.png

7. Spatial Visualization of Motif

Visualize the spatial distribution of motif-positive anchor cells and their associated neighbor cells.

[13]:
motif = ["Splanchnic mesoderm", "Endothelium"]

sp.plot_motif_celltype(
    ct=anchor_ct,
    motif=motif,
    max_dist=max_dist,
    figsize=(6, 6),
)
../_images/tutorials_tutorial_1_19_0.png

Alternatively, visualize motif-positive vs motif-negative anchor cells alongside motif-associated neighbor cells.

[14]:
ids = sp.get_anchor_motif_cell_ids(
    ct=anchor_ct,
    motif=motif,
    max_dist=max_dist
)

sp.plot_all_center_motif(
    ct=anchor_ct,
    ids=ids,
)
../_images/tutorials_tutorial_1_21_0.png

8. Differential Expression: Motif+ vs Motif− Anchor Cells

Compare gene expression between anchor cells that participate in the motif (motif+) and those that do not (motif−). First, retrieve the cell IDs of motif-positive anchors, then run a t-test across all genes.

[15]:
motif_enrich = sp.motif_enrichment_dist(
    ct=anchor_ct,
    motifs=motif,
    max_dist=max_dist,
    return_cellID=True
)
motif_enrich
[15]:
center motifs n_center_motif n_center n_motif expectation p-values neighbor_id center_id if_significant
0 Gut tube [Endothelium, Splanchnic mesoderm] 322 1464 3117 256.278109 0.000002 [6151, 6163, 6168, 6182, 6188, 14381, 6199, 62... [529, 536, 537, 547, 550, 4493, 4611, 4613, 46... True
[16]:
center_id = motif_enrich['center_id'][0]
all_center_id = np.where(sp.labels == anchor_ct)[0]
non_center_id = np.setdiff1d(all_center_id, center_id)

de_genes = sp.de_genes(
    ind_group1=center_id,
    ind_group2=non_center_id,
    min_fraction=0.05,
    method='t-test',
    alpha=0.05
)

Testing 351 genes ...
[17]:
de_genes
[17]:
gene proportion_1 proportion_2 abs_difference log2fc p_value adj-pval de_in
0 Dkk1 0.111801 0.243433 0.131631 -1.586187 8.074493e-14 1.778115e-11 group2
1 Wnt5a 0.319876 0.472855 0.152979 -1.045780 1.013171e-13 1.778115e-11 group2
2 Osr1 0.652174 0.464974 0.187200 0.943958 2.715462e-12 3.177090e-10 group1
3 T 0.161491 0.260946 0.099455 -1.527118 6.675586e-12 5.857827e-10 group2
4 Meis1 0.503106 0.345009 0.158097 0.959926 1.492688e-09 1.047867e-07 group1
... ... ... ... ... ... ... ... ...
75 Ldhb 0.521739 0.443082 0.078657 0.346249 8.586833e-03 3.965761e-02 group1
76 Pou5f1 0.074534 0.132224 0.057690 -0.778253 8.711626e-03 3.971144e-02 group2
77 Slc4a1 0.071429 0.138354 0.066925 -0.749696 9.692000e-03 4.361400e-02 group2
78 Setd1a 0.723602 0.685639 0.037963 0.261399 1.023892e-02 4.549189e-02 group1
79 Sall3 0.217391 0.292469 0.075078 -0.474529 1.071077e-02 4.699352e-02 group2

80 rows × 8 columns

9. Cross-cell Gene-Gene Covariation

Identify gene pairs where one gene is expressed in the anchor cell and the other in a neighboring motif cell, and their expression levels are significantly correlated — specifically in the spatial context of this motif.

[18]:
covarying_genes_by_type = sp.compute_gene_gene_correlation_by_type(
    ct=anchor_ct,
    motif=motif,
    max_dist=max_dist,
)

covarying_genes_by_type = covarying_genes_by_type[covarying_genes_by_type['if_significant']]
Computing covarying genes using expression data...
Analyzing 2 non-center cell types in motif: ['Splanchnic mesoderm', 'Endothelium']
================================================================================
After filtering (min_nonzero=10): 351 genes

================================================================================
Computing Correlation-3: Center without motif vs Neighbors
================================================================================
Unique center cells: 1106
Unique neighbor cells: 2851
... (55 lines omitted) ...

Results prepared and sorted
[19]:
covarying_genes_by_type
[19]:
cell_type gene_center gene_motif corr_neighbor corr_non_neighbor p_value_test1 delta_corr_test1 corr_center_no_motif p_value_test2 delta_corr_test2 combined_score q_value_test1 q_value_test2 reject_test1_fdr reject_test2_fdr abs_combined_score if_significant
0 Splanchnic mesoderm Sp5 Tbx5 0.696998 -0.004279 0.000000e+00 0.701277 0.076782 0.000000 0.620216 193.360220 0.000000 0.000000 True True 193.360220 True
1 Splanchnic mesoderm Nkx2-3 Tagln 0.576550 -0.006229 0.000000e+00 0.582779 -0.047309 0.000000 0.623860 183.460727 0.000000 0.000000 True True 183.460727 True
2 Splanchnic mesoderm Hoxb1 Tbx1 0.606402 -0.030189 0.000000e+00 0.636591 0.063244 0.000000 0.543158 171.356423 0.000000 0.000000 True True 171.356423 True
3 Splanchnic mesoderm Sp5 Wnt2 0.596063 0.000067 0.000000e+00 0.595996 0.150484 0.000000 0.445579 147.211285 0.000000 0.000000 True True 147.211285 True
4 Splanchnic mesoderm Tbx3 Tagln 0.395296 -0.000074 1.286408e-07 0.395370 -0.165390 0.000000 0.560686 118.561425 0.000158 0.000000 True True 118.561425 True
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
771 Splanchnic mesoderm Hoxb9 Meox1 0.289525 0.000234 1.691007e-04 0.289292 0.045858 0.000073 0.243667 1.032991 0.047793 0.025269 True True 1.032991 True
772 Splanchnic mesoderm Gata3 Hapln1 0.289761 -0.004068 1.341177e-04 0.293829 0.047934 0.000082 0.241827 1.032853 0.040498 0.027817 True True 1.032853 True
848 Splanchnic mesoderm Podxl Ezh2 -0.292342 0.004740 1.120631e-04 -0.297082 -0.060807 0.000157 -0.231535 -0.968453 0.035351 0.045551 True True 0.968453 True
852 Splanchnic mesoderm Col1a2 Msx2 0.302228 0.004728 1.043428e-04 0.297501 0.072039 0.000162 0.230189 0.966198 0.033499 0.046536 True True 0.966198 True
896 Splanchnic mesoderm Lin28a Hoxb8 -0.287547 0.004758 1.464706e-04 -0.292305 -0.057600 0.000179 -0.229946 -0.939486 0.043134 0.049649 True True 0.939486 True

411 rows × 17 columns

[20]:
covarying_genes_module = sp.plot_gene_pair_heatmap(
    gene_pair_df=covarying_genes_by_type,
    figsize=(15, 8)
)
../_images/tutorials_tutorial_1_29_0.png
[21]:
covarying_genes_module
[21]:
gene_center gene_motif combined_score cluster_type cluster_row cluster_col cell_type
0 Afp Gata4 1.131970 positive 0 0 Splanchnic mesoderm
1 Akr1c19 Gata4 1.507798 positive 0 0 Splanchnic mesoderm
2 Aldh1a2 Tbx5 1.037975 positive 0 1 Splanchnic mesoderm
3 Axin2 Aldh1a2 4.586966 positive 0 0 Splanchnic mesoderm
4 Axin2 Foxf1 1.081744 positive 0 0 Splanchnic mesoderm
... ... ... ... ... ... ... ...
406 Cdx2 Cavin3 -1.846188 negative 2 2 Endothelium
407 Col4a1 Hoxd4 -1.471384 negative 0 0 Endothelium
408 Cxcl12 Cavin3 -2.278150 negative 2 2 Endothelium
409 Sp5 Pecam1 -1.402200 negative 2 2 Endothelium
410 T Krt18 -2.032839 negative 1 1 Endothelium

411 rows × 7 columns

[ ]: