Skill Tracer
Skills › Knowledge Work Plugins › scvi-tools › spatial_deconvolution.md

spatial_deconvolution.md

438 lines · bio-research/skills/scvi-tools/references/spatial_deconvolution.md at 2574e29

Open on GitHub
1# Spatial Transcriptomics Analysis
2
3This reference covers spatial transcriptomics analysis using scvi-tools methods: DestVI for deconvolution and resolVI for building spatial models.
4

Spatial Transcriptomics Analysis

This reference covers spatial transcriptomics analysis using scvi-tools methods: DestVI for deconvolution and resolVI for building spatial models.

Raw lines 1 to 4
5## Overview
6
7Spatial transcriptomics technologies like Visium capture gene expression at defined spatial locations, but many platforms have multi-cellular resolution. scvi-tools provides two main approaches:
8
9- **DestVI**: Deconvolution - estimates cell type proportions at each spot using a single-cell reference
10- **resolVI**: Builds a spatial model that learns gene expression patterns accounting for spatial context

Overview

Spatial transcriptomics technologies like Visium capture gene expression at defined spatial locations, but many platforms have multi-cellular resolution. scvi-tools provides two main approaches:

  • DestVI: Deconvolution - estimates cell type proportions at each spot using a single-cell reference
  • resolVI: Builds a spatial model that learns gene expression patterns accounting for spatial context
Raw lines 5 to 11
12## Available Methods in scvi-tools
14| Method | Description | Use Case |
15|--------|-------------|----------|
16| **DestVI** | Variational inference for deconvolution | Estimate cell type proportions per spot |
17| **resolVI** | Spatial gene expression model | Learn spatially-aware representations |
18| **CondSCVI** | Reference model for DestVI | Required for DestVI workflow |

Available Methods in scvi-tools

MethodDescriptionUse Case
DestVIVariational inference for deconvolutionEstimate cell type proportions per spot
resolVISpatial gene expression modelLearn spatially-aware representations
CondSCVIReference model for DestVIRequired for DestVI workflow
Raw lines 12 to 19
20## Prerequisites
22```python
23import scvi
24import scanpy as sc
25import squidpy as sq
26import numpy as np
28print(f"scvi-tools version: {scvi.__version__}")
29```
31---

Prerequisites

import scvi
import scanpy as sc
import squidpy as sq
import numpy as np

print(f"scvi-tools version: {scvi.__version__}")

Raw lines 20 to 32
33## Part 1: DestVI Deconvolution

Part 1: DestVI Deconvolution

Raw lines 33 to 34
35### Step 1: Load Spatial Data
37```python
38# Load Visium data
39adata_spatial = sc.read_visium("spaceranger_output/")
41# Check structure
42print(f"Spots: {adata_spatial.n_obs}")
43print(f"Genes: {adata_spatial.n_vars}")
44print(f"Spatial coordinates: {adata_spatial.obsm['spatial'].shape}")
46# Basic QC
47sc.pp.calculate_qc_metrics(adata_spatial, inplace=True)
48adata_spatial = adata_spatial[adata_spatial.obs['n_genes_by_counts'] > 200].copy()
50# Store counts
51adata_spatial.layers["counts"] = adata_spatial.X.copy()
52```

Step 1: Load Spatial Data

# Load Visium data
adata_spatial = sc.read_visium("spaceranger_output/")

# Check structure
print(f"Spots: {adata_spatial.n_obs}")
print(f"Genes: {adata_spatial.n_vars}")
print(f"Spatial coordinates: {adata_spatial.obsm['spatial'].shape}")

# Basic QC
sc.pp.calculate_qc_metrics(adata_spatial, inplace=True)
adata_spatial = adata_spatial[adata_spatial.obs['n_genes_by_counts'] > 200].copy()

# Store counts
adata_spatial.layers["counts"] = adata_spatial.X.copy()
Raw lines 35 to 53
54### Step 2: Load Single-Cell Reference
56```python
57# Load reference single-cell data
58adata_sc = sc.read_h5ad("reference_scrna.h5ad")
60# Requirements:
61# - Raw counts
62# - Cell type annotations
63print(f"Reference cells: {adata_sc.n_obs}")
64print(f"Cell types: {adata_sc.obs['cell_type'].nunique()}")
65print(adata_sc.obs['cell_type'].value_counts())
67# Store counts
68adata_sc.layers["counts"] = adata_sc.X.copy()
69```

Step 2: Load Single-Cell Reference

# Load reference single-cell data
adata_sc = sc.read_h5ad("reference_scrna.h5ad")

# Requirements:
# - Raw counts
# - Cell type annotations
print(f"Reference cells: {adata_sc.n_obs}")
print(f"Cell types: {adata_sc.obs['cell_type'].nunique()}")
print(adata_sc.obs['cell_type'].value_counts())

# Store counts
adata_sc.layers["counts"] = adata_sc.X.copy()
Raw lines 54 to 70
71### Step 3: Prepare Data
73```python
74# DestVI requires gene overlap between reference and spatial
75common_genes = adata_sc.var_names.intersection(adata_spatial.var_names)
76print(f"Common genes: {len(common_genes)}")
78adata_sc = adata_sc[:, common_genes].copy()
79adata_spatial = adata_spatial[:, common_genes].copy()
80```

Step 3: Prepare Data

# DestVI requires gene overlap between reference and spatial
common_genes = adata_sc.var_names.intersection(adata_spatial.var_names)
print(f"Common genes: {len(common_genes)}")

adata_sc = adata_sc[:, common_genes].copy()
adata_spatial = adata_spatial[:, common_genes].copy()
Raw lines 71 to 81
82### Step 4: Train Reference Model (CondSCVI)
84```python
85# Train conditional scVI on reference data
86scvi.model.CondSCVI.setup_anndata(
87 adata_sc,
88 layer="counts",
89 labels_key="cell_type"
92sc_model = scvi.model.CondSCVI(
93 adata_sc,
94 n_latent=20
97sc_model.train(max_epochs=200)
98sc_model.history['elbo_train'].plot()
99```

Step 4: Train Reference Model (CondSCVI)

# Train conditional scVI on reference data
scvi.model.CondSCVI.setup_anndata(
    adata_sc,
    layer="counts",
    labels_key="cell_type"
)

sc_model = scvi.model.CondSCVI(
    adata_sc,
    n_latent=20
)

sc_model.train(max_epochs=200)
sc_model.history['elbo_train'].plot()
Raw lines 82 to 100
101### Step 5: Train DestVI
103```python
104# Setup spatial data
105scvi.model.DestVI.setup_anndata(
106 adata_spatial,
107 layer="counts"
110# Train DestVI using reference model
111spatial_model = scvi.model.DestVI.from_rna_model(
112 adata_spatial,
113 sc_model
116spatial_model.train(max_epochs=500)
117```

Step 5: Train DestVI

# Setup spatial data
scvi.model.DestVI.setup_anndata(
    adata_spatial,
    layer="counts"
)

# Train DestVI using reference model
spatial_model = scvi.model.DestVI.from_rna_model(
    adata_spatial,
    sc_model
)

spatial_model.train(max_epochs=500)
Raw lines 101 to 118
119### Step 6: Get Cell Type Proportions
121```python
122# Infer cell type proportions at each spot
123proportions = spatial_model.get_proportions()
125# Add to adata
126for ct in adata_sc.obs['cell_type'].unique():
127 adata_spatial.obs[f'prop_{ct}'] = proportions[ct]
129# Visualize
130sq.pl.spatial_scatter(
131 adata_spatial,
132 color=[f'prop_{ct}' for ct in adata_sc.obs['cell_type'].unique()[:6]],
133 ncols=3
135```
137---

Step 6: Get Cell Type Proportions

# Infer cell type proportions at each spot
proportions = spatial_model.get_proportions()

# Add to adata
for ct in adata_sc.obs['cell_type'].unique():
    adata_spatial.obs[f'prop_{ct}'] = proportions[ct]

# Visualize
sq.pl.spatial_scatter(
    adata_spatial,
    color=[f'prop_{ct}' for ct in adata_sc.obs['cell_type'].unique()[:6]],
    ncols=3
)

Raw lines 119 to 138
139## Part 2: resolVI Spatial Model
141resolVI is a semi-supervised method that learns cell type assignments and spatially-aware representations directly from spatial data, optionally using initial cell type predictions.
143**Note**: resolVI is in `scvi.external` (not `scvi.model`).

Part 2: resolVI Spatial Model

resolVI is a semi-supervised method that learns cell type assignments and spatially-aware representations directly from spatial data, optionally using initial cell type predictions.

Note: resolVI is in scvi.external (not scvi.model).

Raw lines 139 to 144
145### Step 1: Prepare Spatial Data
147```python
148# Load and preprocess
149adata = sc.read_visium("spaceranger_output/")
151# QC
152sc.pp.calculate_qc_metrics(adata, inplace=True)
153adata = adata[adata.obs['n_genes_by_counts'] > 200].copy()
155# Store counts
156adata.layers["counts"] = adata.X.copy()
158# HVG selection
159sc.pp.highly_variable_genes(
160 adata,
161 n_top_genes=4000,
162 flavor="seurat_v3",
163 layer="counts"
165adata = adata[:, adata.var['highly_variable']].copy()
167# Optional: Get initial cell type predictions (e.g., from a reference)
168# adata.obs["predicted_celltype"] = ...
169```

Step 1: Prepare Spatial Data

# Load and preprocess
adata = sc.read_visium("spaceranger_output/")

# QC
sc.pp.calculate_qc_metrics(adata, inplace=True)
adata = adata[adata.obs['n_genes_by_counts'] > 200].copy()

# Store counts
adata.layers["counts"] = adata.X.copy()

# HVG selection
sc.pp.highly_variable_genes(
    adata,
    n_top_genes=4000,
    flavor="seurat_v3",
    layer="counts"
)
adata = adata[:, adata.var['highly_variable']].copy()

# Optional: Get initial cell type predictions (e.g., from a reference)
# adata.obs["predicted_celltype"] = ...
Raw lines 145 to 170
171### Step 2: Setup and Train resolVI
173```python
174# Setup for resolVI (note: scvi.external, not scvi.model)
175scvi.external.RESOLVI.setup_anndata(
176 adata,
177 labels_key="predicted_celltype", # Initial cell type predictions
178 layer="counts"
181# Create model (semisupervised=True uses the labels)
182model = scvi.external.RESOLVI(adata, semisupervised=True)
184# Train
185model.train(max_epochs=50)
186```

Step 2: Setup and Train resolVI

# Setup for resolVI (note: scvi.external, not scvi.model)
scvi.external.RESOLVI.setup_anndata(
    adata,
    labels_key="predicted_celltype",  # Initial cell type predictions
    layer="counts"
)

# Create model (semisupervised=True uses the labels)
model = scvi.external.RESOLVI(adata, semisupervised=True)

# Train
model.train(max_epochs=50)
Raw lines 171 to 187
188### Step 3: Get Cell Type Predictions
190```python
191# Get refined cell type predictions
192# soft=True returns probabilities, soft=False returns labels
193cell_type_probs = model.predict(adata, num_samples=3, soft=True)
194cell_type_labels = model.predict(adata, num_samples=3, soft=False)
196adata.obs["resolvi_celltype"] = cell_type_labels
198# Visualize
199sq.pl.spatial_scatter(adata, color="resolvi_celltype")
200```

Step 3: Get Cell Type Predictions

# Get refined cell type predictions
# soft=True returns probabilities, soft=False returns labels
cell_type_probs = model.predict(adata, num_samples=3, soft=True)
cell_type_labels = model.predict(adata, num_samples=3, soft=False)

adata.obs["resolvi_celltype"] = cell_type_labels

# Visualize
sq.pl.spatial_scatter(adata, color="resolvi_celltype")
Raw lines 188 to 201
202### Step 4: Get Latent Representation
204```python
205# Get latent representation
206adata.obsm["X_resolVI"] = model.get_latent_representation(adata)
208# Cluster based on spatial representation
209sc.pp.neighbors(adata, use_rep="X_resolVI")
210sc.tl.umap(adata)
211sc.tl.leiden(adata, resolution=0.5)
213# Visualize clusters spatially
214sq.pl.spatial_scatter(adata, color="leiden")
215```

Step 4: Get Latent Representation

# Get latent representation
adata.obsm["X_resolVI"] = model.get_latent_representation(adata)

# Cluster based on spatial representation
sc.pp.neighbors(adata, use_rep="X_resolVI")
sc.tl.umap(adata)
sc.tl.leiden(adata, resolution=0.5)

# Visualize clusters spatially
sq.pl.spatial_scatter(adata, color="leiden")
Raw lines 202 to 216
217### Step 5: Differential Expression
219```python
220# DE between cell types using resolVI
221de_results = model.differential_expression(
222 adata,
223 groupby="resolvi_celltype",
224 group1="T_cell",
225 group2="Tumor"
228print(de_results.head(20))
229```

Step 5: Differential Expression

# DE between cell types using resolVI
de_results = model.differential_expression(
    adata,
    groupby="resolvi_celltype",
    group1="T_cell",
    group2="Tumor"
)

print(de_results.head(20))
Raw lines 217 to 230
231### Step 6: Niche Abundance Analysis
233```python
234# Analyze how cell type neighborhoods differ between conditions
235# Requires spatial neighbor graph
236sq.gr.spatial_neighbors(adata, coord_type="generic")
238niche_results = model.differential_niche_abundance(
239 groupby="resolvi_celltype",
240 group1="T_cell",
241 group2="Tumor",
242 neighbor_key="spatial_neighbors"
244```

Step 6: Niche Abundance Analysis

# Analyze how cell type neighborhoods differ between conditions
# Requires spatial neighbor graph
sq.gr.spatial_neighbors(adata, coord_type="generic")

niche_results = model.differential_niche_abundance(
    groupby="resolvi_celltype",
    group1="T_cell",
    group2="Tumor",
    neighbor_key="spatial_neighbors"
)
Raw lines 231 to 245
246### Step 7: Query Mapping (Transfer to New Data)
248```python
249# Map new spatial data to trained model
250query_adata = sc.read_visium("new_sample/")
251query_adata.layers["counts"] = query_adata.X.copy()
253# Prepare and load query
254model.prepare_query_anndata(query_adata, reference_model=model)
255query_model = model.load_query_data(query_adata, reference_model=model)
257# Fine-tune on query
258query_model.train(max_epochs=20)
260# Get predictions for query
261query_labels = query_model.predict(query_adata, num_samples=3, soft=False)
262```
264---

Step 7: Query Mapping (Transfer to New Data)

# Map new spatial data to trained model
query_adata = sc.read_visium("new_sample/")
query_adata.layers["counts"] = query_adata.X.copy()

# Prepare and load query
model.prepare_query_anndata(query_adata, reference_model=model)
query_model = model.load_query_data(query_adata, reference_model=model)

# Fine-tune on query
query_model.train(max_epochs=20)

# Get predictions for query
query_labels = query_model.predict(query_adata, num_samples=3, soft=False)

Raw lines 246 to 265
266## Visualization
268### Spatial Proportions
270```python
271import matplotlib.pyplot as plt
273# Plot multiple cell type proportions
274cell_types = ['T_cell', 'Tumor', 'Fibroblast', 'Macrophage']
275fig, axes = plt.subplots(2, 2, figsize=(12, 12))
277for ax, ct in zip(axes.flat, cell_types):
278 sq.pl.spatial_scatter(
279 adata_spatial,
280 color=f'prop_{ct}',
281 ax=ax,
282 title=ct,
283 show=False
284 )
286plt.tight_layout()
287```

Spatial Proportions

import matplotlib.pyplot as plt

# Plot multiple cell type proportions
cell_types = ['T_cell', 'Tumor', 'Fibroblast', 'Macrophage']
fig, axes = plt.subplots(2, 2, figsize=(12, 12))

for ax, ct in zip(axes.flat, cell_types):
    sq.pl.spatial_scatter(
        adata_spatial,
        color=f'prop_{ct}',
        ax=ax,
        title=ct,
        show=False
    )

plt.tight_layout()
Raw lines 268 to 288
289### Enrichment by Region
291```python
292# Cluster spatial data
293sc.pp.neighbors(adata_spatial)
294sc.tl.leiden(adata_spatial, resolution=0.5)
296# Compare proportions across regions
297import pandas as pd
299cell_types = adata_sc.obs['cell_type'].unique()
300prop_cols = [f'prop_{ct}' for ct in cell_types]
301region_props = adata_spatial.obs.groupby('leiden')[prop_cols].mean()
302print(region_props)
304# Heatmap
305import seaborn as sns
306plt.figure(figsize=(10, 6))
307sns.heatmap(region_props.T, annot=True, cmap='viridis')
308plt.title('Cell Type Proportions by Region')
309```

Enrichment by Region

# Cluster spatial data
sc.pp.neighbors(adata_spatial)
sc.tl.leiden(adata_spatial, resolution=0.5)

# Compare proportions across regions
import pandas as pd

cell_types = adata_sc.obs['cell_type'].unique()
prop_cols = [f'prop_{ct}' for ct in cell_types]
region_props = adata_spatial.obs.groupby('leiden')[prop_cols].mean()
print(region_props)

# Heatmap
import seaborn as sns
plt.figure(figsize=(10, 6))
sns.heatmap(region_props.T, annot=True, cmap='viridis')
plt.title('Cell Type Proportions by Region')
Raw lines 289 to 310
311### Spatial Cell Type Interactions
313```python
314# Neighborhood enrichment using cell type assignments
315sq.gr.spatial_neighbors(adata_spatial)
317# Create "dominant cell type" annotation
318prop_cols = [f'prop_{ct}' for ct in cell_types]
319adata_spatial.obs['dominant_type'] = adata_spatial.obs[prop_cols].idxmax(axis=1)
320adata_spatial.obs['dominant_type'] = adata_spatial.obs['dominant_type'].str.replace('prop_', '')
322# Co-occurrence analysis
323sq.gr.co_occurrence(adata_spatial, cluster_key='dominant_type')
324sq.pl.co_occurrence(adata_spatial, cluster_key='dominant_type')
325```
327---

Spatial Cell Type Interactions

# Neighborhood enrichment using cell type assignments
sq.gr.spatial_neighbors(adata_spatial)

# Create "dominant cell type" annotation
prop_cols = [f'prop_{ct}' for ct in cell_types]
adata_spatial.obs['dominant_type'] = adata_spatial.obs[prop_cols].idxmax(axis=1)
adata_spatial.obs['dominant_type'] = adata_spatial.obs['dominant_type'].str.replace('prop_', '')

# Co-occurrence analysis
sq.gr.co_occurrence(adata_spatial, cluster_key='dominant_type')
sq.pl.co_occurrence(adata_spatial, cluster_key='dominant_type')

Raw lines 311 to 328
329## Complete DestVI Pipeline
331```python
332def deconvolve_spatial(
333 adata_spatial,
334 adata_ref,
335 cell_type_key="cell_type",
336 n_latent=20,
337 max_epochs_ref=200,
338 max_epochs_spatial=500
339):
340 """
341 Perform spatial deconvolution using DestVI.
343 Parameters
344 ----------
345 adata_spatial : AnnData
346 Spatial transcriptomics data
347 adata_ref : AnnData
348 Single-cell reference with cell type annotations
349 cell_type_key : str
350 Column in adata_ref.obs with cell type labels
351 n_latent : int
352 Latent dimensions
353 max_epochs_ref : int
354 Training epochs for reference model
355 max_epochs_spatial : int
356 Training epochs for spatial model
358 Returns
359 -------
360 AnnData with cell type proportions in obs
361 """
362 import scvi
364 # Get common genes
365 common_genes = adata_ref.var_names.intersection(adata_spatial.var_names)
366 adata_ref = adata_ref[:, common_genes].copy()
367 adata_spatial = adata_spatial[:, common_genes].copy()
369 # Ensure counts are stored
370 if "counts" not in adata_ref.layers:
371 adata_ref.layers["counts"] = adata_ref.X.copy()
372 if "counts" not in adata_spatial.layers:
373 adata_spatial.layers["counts"] = adata_spatial.X.copy()
375 # Train reference model
376 scvi.model.CondSCVI.setup_anndata(
377 adata_ref,
378 layer="counts",
379 labels_key=cell_type_key
380 )
382 ref_model = scvi.model.CondSCVI(adata_ref, n_latent=n_latent)
383 ref_model.train(max_epochs=max_epochs_ref)
385 # Train spatial model
386 scvi.model.DestVI.setup_anndata(adata_spatial, layer="counts")
388 spatial_model = scvi.model.DestVI.from_rna_model(
389 adata_spatial,
390 ref_model
391 )
392 spatial_model.train(max_epochs=max_epochs_spatial)
394 # Get proportions
395 proportions = spatial_model.get_proportions()
397 cell_types = adata_ref.obs[cell_type_key].unique()
398 for ct in cell_types:
399 adata_spatial.obs[f'prop_{ct}'] = proportions[ct]
401 # Add dominant type
402 prop_cols = [f'prop_{ct}' for ct in cell_types]
403 adata_spatial.obs['dominant_type'] = adata_spatial.obs[prop_cols].idxmax(axis=1)
404 adata_spatial.obs['dominant_type'] = adata_spatial.obs['dominant_type'].str.replace('prop_', '')
406 return adata_spatial, ref_model, spatial_model
408# Usage
409adata_spatial, ref_model, spatial_model = deconvolve_spatial(
410 adata_spatial,
411 adata_sc,
412 cell_type_key="cell_type"
415# Visualize
416sq.pl.spatial_scatter(
417 adata_spatial,
418 color=['dominant_type', 'prop_T_cell', 'prop_Tumor'],
419 ncols=3
421```
423---

Complete DestVI Pipeline

def deconvolve_spatial(
    adata_spatial,
    adata_ref,
    cell_type_key="cell_type",
    n_latent=20,
    max_epochs_ref=200,
    max_epochs_spatial=500
):
    """
    Perform spatial deconvolution using DestVI.

    Parameters
    ----------
    adata_spatial : AnnData
        Spatial transcriptomics data
    adata_ref : AnnData
        Single-cell reference with cell type annotations
    cell_type_key : str
        Column in adata_ref.obs with cell type labels
    n_latent : int
        Latent dimensions
    max_epochs_ref : int
        Training epochs for reference model
    max_epochs_spatial : int
        Training epochs for spatial model

    Returns
    -------
    AnnData with cell type proportions in obs
    """
    import scvi

    # Get common genes
    common_genes = adata_ref.var_names.intersection(adata_spatial.var_names)
    adata_ref = adata_ref[:, common_genes].copy()
    adata_spatial = adata_spatial[:, common_genes].copy()

    # Ensure counts are stored
    if "counts" not in adata_ref.layers:
        adata_ref.layers["counts"] = adata_ref.X.copy()
    if "counts" not in adata_spatial.layers:
        adata_spatial.layers["counts"] = adata_spatial.X.copy()

    # Train reference model
    scvi.model.CondSCVI.setup_anndata(
        adata_ref,
        layer="counts",
        labels_key=cell_type_key
    )

    ref_model = scvi.model.CondSCVI(adata_ref, n_latent=n_latent)
    ref_model.train(max_epochs=max_epochs_ref)

    # Train spatial model
    scvi.model.DestVI.setup_anndata(adata_spatial, layer="counts")

    spatial_model = scvi.model.DestVI.from_rna_model(
        adata_spatial,
        ref_model
    )
    spatial_model.train(max_epochs=max_epochs_spatial)

    # Get proportions
    proportions = spatial_model.get_proportions()

    cell_types = adata_ref.obs[cell_type_key].unique()
    for ct in cell_types:
        adata_spatial.obs[f'prop_{ct}'] = proportions[ct]

    # Add dominant type
    prop_cols = [f'prop_{ct}' for ct in cell_types]
    adata_spatial.obs['dominant_type'] = adata_spatial.obs[prop_cols].idxmax(axis=1)
    adata_spatial.obs['dominant_type'] = adata_spatial.obs['dominant_type'].str.replace('prop_', '')

    return adata_spatial, ref_model, spatial_model

# Usage
adata_spatial, ref_model, spatial_model = deconvolve_spatial(
    adata_spatial,
    adata_sc,
    cell_type_key="cell_type"
)

# Visualize
sq.pl.spatial_scatter(
    adata_spatial,
    color=['dominant_type', 'prop_T_cell', 'prop_Tumor'],
    ncols=3
)

Raw lines 329 to 424
425## Troubleshooting
427| Issue | Cause | Solution |
428|-------|-------|----------|
429| Few common genes | Different gene naming | Convert gene names (Ensembl ↔ Symbol) |
430| Poor deconvolution | Reference doesn't match | Use tissue-matched reference |
431| All spots same type | Over-smoothing | Adjust model parameters, check reference diversity |
432| NaN proportions | Missing cell types | Ensure all expected types in reference |
433| Training slow | Large spatial dataset | Reduce max_epochs, increase batch_size |

Troubleshooting

IssueCauseSolution
Few common genesDifferent gene namingConvert gene names (Ensembl ↔ Symbol)
Poor deconvolutionReference doesn't matchUse tissue-matched reference
All spots same typeOver-smoothingAdjust model parameters, check reference diversity
NaN proportionsMissing cell typesEnsure all expected types in reference
Training slowLarge spatial datasetReduce max_epochs, increase batch_size
Raw lines 425 to 434
435## Key References
437- Lopez et al. (2022) "DestVI identifies continuums of cell types in spatial transcriptomics data"
438- [scvi-tools spatial tutorials](https://docs.scvi-tools.org/en/stable/tutorials/index.html)

Key References

Raw lines 435 to 438