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
1# Spatial Transcriptomics Analysis
3This reference covers spatial transcriptomics analysis using scvi-tools methods: DestVI for deconvolution and resolVI for building spatial models.
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 45## Overview
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:
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
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
| Method | Description | Use Case |
|---|---|---|
| DestVI | Variational inference for deconvolution | Estimate cell type proportions per spot |
| resolVI | Spatial gene expression model | Learn spatially-aware representations |
| CondSCVI | Reference model for DestVI | Required for DestVI workflow |
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
Part 1: DestVI Deconvolution
Raw lines 33 to 3435### 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 5354### 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 7071### 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 8182### 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"
90)
92sc_model = scvi.model.CondSCVI(
93 adata_sc,
94 n_latent=20
95)
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 100101### Step 5: Train DestVI
103```python
104# Setup spatial data
105scvi.model.DestVI.setup_anndata(
106 adata_spatial,
107 layer="counts"
108)
110# Train DestVI using reference model
111spatial_model = scvi.model.DestVI.from_rna_model(
112 adata_spatial,
113 sc_model
114)
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 118119### 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
134)
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).
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"
164)
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 170171### 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"
179)
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 187188### 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 201202### 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 216217### 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"
226)
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 230231### 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"
243)
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 245246### 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
Visualization
Raw lines 266 to 267268### 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 288289### 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 310311### 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"
413)
415# Visualize
416sq.pl.spatial_scatter(
417 adata_spatial,
418 color=['dominant_type', 'prop_T_cell', 'prop_Tumor'],
419 ncols=3
420)
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
| Issue | Cause | Solution |
|---|---|---|
| Few common genes | Different gene naming | Convert gene names (Ensembl ↔ Symbol) |
| Poor deconvolution | Reference doesn't match | Use tissue-matched reference |
| All spots same type | Over-smoothing | Adjust model parameters, check reference diversity |
| NaN proportions | Missing cell types | Ensure all expected types in reference |
| Training slow | Large spatial dataset | Reduce max_epochs, increase batch_size |
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
- Lopez et al. (2022) "DestVI identifies continuums of cell types in spatial transcriptomics data"
- scvi-tools spatial tutorials