ResolVI to address noise and biases in spatial transcriptomics

ResolVI to address noise and biases in spatial transcriptomics#

In this tutorial, we go through the steps of training resolVI for correction in cellular-resolved spatial transcriptomics. This addresses erroneous co-expression pattern after cellular segmentation as well as unspecific background in these technologies. We highly recommend to optimize cell segmentation before running resolVI as a better cell segmentation will also yield better downstream results and both steps are complementary.

Plan for this tutorial:

  1. Loading the data

  2. Training an unsupervised and (semi-)supervised resolVI model

  3. Visualizing the latent space

  4. Transfer mapping.

  5. Generating corrected counts and perform DE analysis

# Install from GitHub for now
!pip install --quiet spatialvi-tools
!pip install --quiet decoupler
!pip install --quiet adjustText
import os
import tempfile

import decoupler as dc
import matplotlib.pyplot as plt
import numpy as np
import scanpy as sc
import spatialvi

sc.set_figure_params(figsize=(4, 4))
save_dir = tempfile.TemporaryDirectory()
%config InlineBackend.print_figure_kwargs={'facecolor' : "w"}
%config InlineBackend.figure_format='retina'

spatialvi.settings.seed = 0
print("Last run with spatialvi-tools version:", spatialvi.__version__)
INFO: Seed set to 0
2026-03-26 11:12:30 | [INFO] Seed set to 0
Last run with spatialvi-tools version: 0.1.0

Data Loading#

For the purposes of this notebook, we will be loading a Xenium dataset of a mouse brain. We will use the left hemisphere for model training and the right hemisphere for transfer mapping. The data was originally labeled using Leiden clustering of an optimized segmentation using the ProSeg algorithm, which is a scalable algorithm for transcriptome-informed segmentation.

adata_path = os.path.join(save_dir.name, "tutorial_xenium_brain.h5ad")
adata = sc.read(
    adata_path, backup_url="https://exampledata.scverse.org/scvi-tools/tutorial_xenium_brain.h5ad"
)
adata
AnnData object with n_obs × n_vars = 130604 × 248
    obs: 'x_centroid', 'y_centroid', 'transcript_counts', 'control_probe_counts', 'control_codeword_counts', 'total_counts', 'cell_area', 'nucleus_area', 'fov', 'predicted_celltype'
    var: 'gene_ids', 'feature_types', 'genome'
    uns: 'log1p'
    obsm: 'X_resolvi_transferred', 'X_spatial', 'X_umap_transferred'
    layers: 'counts'

We computed a UMAP after transfering labels using the ProSeg segmented dataset and the UMAP serves as ground truth.

adata.obsm["X_umap"] = adata.obsm["X_umap_transferred"]
sc.pl.umap(adata, color="predicted_celltype")
sc.pl.spatial(adata, color="predicted_celltype", spot_size=30)
../_images/748a9448b86e14648e2a056c4190b6434d9d94e0917a849f6e5c1e7f8b4b32d2.png
/tmp/ipykernel_2295458/1077018278.py:3: FutureWarning: Use `squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(adata, color="predicted_celltype", spot_size=30)
../_images/7427da3bb7b8108201058e468607084f4cbe2e7e89e0220ae4fc06675ccaeecb.png
adata.obs["predicted_celltype"].value_counts()
predicted_celltype
Astrocytes                            17315
Oligodendrocyte                       14567
Fibroblast                            10070
Endothelial Cells                      9775
Inhibitory Neurons Hypothalamus        9284
Excitatory Neurons Amygdala            8640
Excitatory Neurons Thalamus            7947
Excitatory Neurons L5                  7004
Excitatory Neurons L2/3                6393
Inhibitory Neurons Putamen             5753
Inhibitory Neurons Cortical            5711
Excitatory Neurons L4                  5500
Excitatory Neurons L6                  5052
Microglia                              4141
Oligodendrocyte Precursor Cells        3872
Excitatory Neurons Dentate Gyrus       3283
Excitatory Neurons Cornu ammonis       2787
Ependymal Cells                        2202
Inhibitory Neurons Pallidum             658
Excitatory Neurons Amygdala medial      650
Name: count, dtype: int64

We use this dataset to set up a semisupervised and an unsupervised model in resolVI and train these. We split the dataset into right and left hemisphere to demonstrate query mapping.

adata.obs["hemisphere"] = ["right" if x > 5000 else "left" for x in adata.obsm["X_spatial"][:, 0]]
sc.pl.spatial(adata, color="hemisphere", spot_size=30)
/tmp/ipykernel_2295458/1090802531.py:2: FutureWarning: Use `squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(adata, color="hemisphere", spot_size=30)
... storing 'hemisphere' as categorical
../_images/50c5437764caaaf1fd9b6f716332a0b0bb7018cf9ea11b83af77e246041dfa57.png
ref_adata = adata[adata.obs["hemisphere"] == "left"].copy()
query_adata = adata[adata.obs["hemisphere"] == "right"].copy()

Train resolVI model#

As in the scANVI notebook, we need to register the AnnData object for use in resolVI. Namely, we can ignore the batch parameter because those cells don’t have much batch effect to begin with as they are derived from a single slice. However, we will give the celltype labels for resolVI to use. Setting up AnnData computes spatial neighbors within each batch. This step might take minutes for very large datasets. It is important that different slices are used as batch covariate.

from spatialvi.model._resolvi import ResolVI
spatialvi.model.ResolVI.setup_anndata(ref_adata, labels_key="predicted_celltype", layer="counts")
INFO     Generating sequential column names                                                                        
INFO     Generating sequential column names                                                                        
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/scvi/data/fields/_dataframe_field.py:187: UserWarning: Category 0 in adata.obs['_scvi_ind_x'] has fewer than 3 cells. Models may not train properly.
  categorical_mapping = _make_column_categorical(
supervised_resolvi = spatialvi.model.ResolVI(ref_adata, semisupervised=True)

We use here only 20 epochs to speed up running the tutorial. We would recommend using 100 epochs here.

supervised_resolvi.train(max_epochs=50)
INFO: GPU available: True (cuda), used: True
2026-03-26 11:26:30 | [INFO] GPU available: True (cuda), used: True
INFO: TPU available: False, using: 0 TPU cores
2026-03-26 11:26:30 | [INFO] TPU available: False, using: 0 TPU cores
INFO: 💡 Tip: For seamless cloud logging and experiment tracking, try installing [litlogger](https://pypi.org/project/litlogger/) to enable LitLogger, which logs metrics and artifacts automatically to the Lightning Experiments platform.
2026-03-26 11:26:30 | [INFO] 💡 Tip: For seamless cloud logging and experiment tracking, try installing [litlogger](https://pypi.org/project/litlogger/) to enable LitLogger, which logs metrics and artifacts automatically to the Lightning Experiments platform.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/trainer/configuration_validator.py:68: You passed in a `val_dataloader` but have no `validation_step`. Skipping val loop.
INFO: You are using a CUDA device ('NVIDIA RTX 6000 Ada Generation') that has Tensor Cores. To properly utilize them, you should set `torch.set_float32_matmul_precision('medium' | 'high')` which will trade-off precision for performance. For more details, read https://pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html#torch.set_float32_matmul_precision
2026-03-26 11:26:30 | [INFO] You are using a CUDA device ('NVIDIA RTX 6000 Ada Generation') that has Tensor Cores. To properly utilize them, you should set `torch.set_float32_matmul_precision('medium' | 'high')` which will trade-off precision for performance. For more details, read https://pytorch.org/docs/stable/generated/torch.set_float32_matmul_precision.html#torch.set_float32_matmul_precision
INFO: LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
2026-03-26 11:26:30 | [INFO] LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/utilities/_pytree.py:21: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/trainer/connectors/data_connector.py:434: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=63` in the `DataLoader` to improve performance.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/scvi/data/_utils.py:71: UserWarning: Sparse CSR tensor support is in beta state. If you miss a functionality in the sparse tensor support, please submit a feature request to https://github.com/pytorch/pytorch/issues. (Triggered internally at /pytorch/aten/src/ATen/SparseCsrTensorImpl.cpp:49.)
  return sparse_csr_tensor(
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/spatialvi/module/_resolvae.py:382: UserWarning: Converting a tensor with requires_grad=True to a scalar may lead to unexpected behavior.
Consider using tensor.detach() first. (Triggered internally at /pytorch/torch/csrc/autograd/generated/python_variable_methods.cpp:836.)
  concentration=torch.tensor(
2026-03-26 11:26:31 | [INFO] Guessed max_plate_nesting = 2
INFO: `Trainer.fit` stopped: `max_epochs=50` reached.
2026-03-26 11:34:20 | [INFO] `Trainer.fit` stopped: `max_epochs=50` reached.

Now we can predict the cell type labels using the trained model, and get the latent space. ResolVI uses pyro. Pyro stores parameters in a pyro_param_store. It is necessary that only a single model is used at a time, e.g. after query training only the query model is valid and the reference model gets overwritten.

ref_adata
AnnData object with n_obs × n_vars = 66457 × 248
    obs: 'x_centroid', 'y_centroid', 'transcript_counts', 'control_probe_counts', 'control_codeword_counts', 'total_counts', 'cell_area', 'nucleus_area', 'fov', 'predicted_celltype', 'hemisphere', '_indices', '_scvi_batch', '_scvi_ind_x', '_scvi_labels'
    var: 'gene_ids', 'feature_types', 'genome'
    uns: 'log1p', 'predicted_celltype_colors', 'hemisphere_colors', '_scvi_uuid', '_scvi_manager_uuid'
    obsm: 'X_resolvi_transferred', 'X_spatial', 'X_umap_transferred', 'X_umap', 'index_neighbor', 'distance_neighbor'
    layers: 'counts'
ref_adata.obsm["resolvi_celltypes"] = supervised_resolvi.predict(
    ref_adata, num_samples=3, soft=True
)
ref_adata.obs["resolvi_predicted"] = ref_adata.obsm["resolvi_celltypes"].idxmax(axis=1)
ref_adata.obsm["X_resolVI"] = supervised_resolvi.get_latent_representation(ref_adata)

Again, we may visualize the latent space as well as the inferred labels

sc.pp.neighbors(ref_adata, use_rep="X_resolVI")
sc.tl.umap(ref_adata)
ref_adata
AnnData object with n_obs × n_vars = 66457 × 248
    obs: 'x_centroid', 'y_centroid', 'transcript_counts', 'control_probe_counts', 'control_codeword_counts', 'total_counts', 'cell_area', 'nucleus_area', 'fov', 'predicted_celltype', 'hemisphere', '_indices', '_scvi_batch', '_scvi_ind_x', '_scvi_labels', 'resolvi_predicted'
    var: 'gene_ids', 'feature_types', 'genome'
    uns: 'log1p', 'predicted_celltype_colors', 'hemisphere_colors', '_scvi_uuid', '_scvi_manager_uuid', 'neighbors', 'umap'
    obsm: 'X_resolvi_transferred', 'X_spatial', 'X_umap_transferred', 'X_umap', 'index_neighbor', 'distance_neighbor', 'resolvi_celltypes', 'X_resolVI'
    layers: 'counts'
    obsp: 'distances', 'connectivities'
sc.pl.umap(ref_adata, color=["predicted_celltype", "resolvi_predicted"])
sc.pl.spatial(ref_adata, color=["predicted_celltype", "resolvi_predicted"], spot_size=30)
... storing 'resolvi_predicted' as categorical
../_images/57383faa6e97b0a34b476272c4cc8b93d8eacfb88cbd2ab1c87542d2b55ebd4d.png
/tmp/ipykernel_2295458/2132712317.py:2: FutureWarning: Use `squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(ref_adata, color=["predicted_celltype", "resolvi_predicted"], spot_size=30)
../_images/402ff59676477f6324917a3390d8585bee9b89cbae63596a1c2fe18cea7f40cb.png

We can use the trained model to perform differential expression of two groups of cells. We compute here genes differentially expressed between neurons in two distinct layers. This uses a similar test to the scVI DE test. Please keep in mind this test doesn’t test for differences in the mean between two groups but tests for differences between random pairs of single cell.

de_result_importance = supervised_resolvi.differential_expression(
    ref_adata,
    groupby="resolvi_predicted",
    group1="Excitatory Neurons L6",
    group2="Excitatory Neurons L5",
    weights="importance",
    pseudocounts=1e-2,
    delta=0.05,
    filter_outlier_cells=True,
    mode="change",
    test_mode="three",
)
de_result_importance.head(5)
proba_de proba_not_de bayes_factor scale1 scale2 pseudocounts delta lfc_mean lfc_median lfc_std ... raw_mean1 raw_mean2 non_zeros_proportion1 non_zeros_proportion2 raw_normalized_mean1 raw_normalized_mean2 is_de_fdr_0.05 comparison group1 group2
Tle4 0.9978 0.0022 6.117091 0.028887 0.006212 0.01 0.05 2.379564 2.439965 0.807286 ... 7.950953 2.031653 0.981260 0.720892 259.976715 58.635036 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Gadd45a 0.9946 0.0054 5.215940 0.018042 0.003423 0.01 0.05 2.512709 2.473621 0.932352 ... 5.308987 1.193503 0.903110 0.519130 170.936279 32.923340 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Nell1 0.9940 0.0060 5.109976 0.000646 0.007045 0.01 0.05 -3.476783 -3.583543 1.094487 ... 0.323765 2.630380 0.240032 0.646023 10.763350 68.496910 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Foxp2 0.9866 0.0134 4.299009 0.012784 0.000983 0.01 0.05 4.003349 4.116029 1.482670 ... 3.218476 0.402422 0.814992 0.262868 102.894379 11.410527 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Zfpm2 0.9832 0.0168 4.069433 0.005907 0.001467 0.01 0.05 2.190883 2.089010 1.023125 ... 1.732842 0.391136 0.710128 0.268098 56.340622 10.878446 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5

5 rows × 22 columns

dc.pl.volcano(
    de_result_importance,
    x="lfc_mean",
    y="proba_not_de",
    thr_sign=0.1,
    thr_stat=0.4,
    top=30,
    figsize=(10, 10),
)
plt.show()

We can use the trained model to perform differential abundance of cell states in the neighborhood of two groups of cells. We find here that excitatory neurons in thalamus and cortex preferentially colocalize with themselves as well as adjacent layer neurons. This uses a similar test to the scVI DE test.

da = supervised_resolvi.differential_niche_abundance(
    groupby="resolvi_predicted",
    group1="Excitatory Neurons L4",
    group2="Excitatory Neurons Thalamus",
    neighbor_key="index_neighbor",
    test_mode="three",
    delta=0.05,
    pseudocounts=3e-2,
)
da.head(5)
proba_de proba_not_de bayes_factor scale1 scale2 pseudocounts delta lfc_mean lfc_median lfc_std lfc_min lfc_max is_de_fdr_0.05 comparison group1 group2
Excitatory Neurons L4 0.9948 0.0052 5.253881 0.483559 0.001316 0.03 0.05 10.625906 10.997845 2.098484 -3.018448 14.854922 True Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Excitatory Neurons Thalamus 0.9942 0.0058 5.144079 0.001154 0.519125 0.03 0.05 -11.342093 -12.118183 2.600523 -14.843862 5.259626 True Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Excitatory Neurons L2/3 0.9080 0.0920 2.289456 0.085110 0.000221 0.03 0.05 7.864612 8.024044 3.259438 -3.363218 14.740730 True Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Oligodendrocyte 0.6688 0.3312 0.702763 0.022825 0.092311 0.03 0.05 -5.328096 -6.059008 6.023091 -14.298560 11.675173 False Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Inhibitory Neurons Cortical 0.6438 0.3562 0.591896 0.087303 0.000415 0.03 0.05 7.511644 11.216698 5.404015 -10.473007 14.086784 False Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
dc.pl.volcano(
    da,
    x="lfc_mean",
    y="proba_not_de",
    thr_sign=0.1,
    thr_stat=0.4,
    top=30,
    figsize=(10, 10),
)
plt.show()

We can also generate counts that are corrected for counts from neighboring cells wrongly assigned due to missegmentation as well as unspecific background. We use custom parameters for num_samples and and summary_fun to accelerate computation. “px_rate” of model_corrected generates corrected count data. “mixture_proportions” of model_residuals generates the amount of diffusion and background for each cell. Batch steps defines how many batches are accumulated before computing summary statistics. To increase the amount of correction, use lower quantiles instead of median.

import pandas as pd
samples_corr = supervised_resolvi.sample_posterior(
    model=supervised_resolvi.module.model_corrected,
    return_sites=["px_rate"],
    summary_fun={"post_sample_q50": np.median},
    num_samples=3,
    summary_frequency=30,
)
samples_corr = pd.DataFrame(samples_corr).T
samples = supervised_resolvi.sample_posterior(
    model=supervised_resolvi.module.model_residuals,
    return_sites=["mixture_proportions"],
    summary_fun={"post_sample_means": np.mean},
    num_samples=3,
    summary_frequency=100,
)
samples = pd.DataFrame(samples).T
ref_adata.obs[["true_proportion", "diffusion_proportion", "background_proportion"]] = samples.loc[
    "post_sample_means", "mixture_proportions"
]
sc.pl.umap(
    ref_adata,
    color=["total_counts", "true_proportion", "diffusion_proportion", "background_proportion"],
)
ref_adata.layers["generated_expression"] = samples_corr.loc["post_sample_q50", "px_rate"]
sc.pl.umap(ref_adata, color=["resolvi_predicted", "Slc17a6"], layer="counts", vmax="p98")
sc.pl.umap(
    ref_adata, color=["resolvi_predicted", "Slc17a6"], layer="generated_expression", vmax="p98"
)

Query transfer#

We can train the reference model on query data to reannotate this data. We rely on the observation names for non-amortized parameters in resolVI. It is important to make sure cells have unique names between query and reference dataset. We set the cell-type to unknown, so that resolVI needs to predict the celltype leveraging the reference model.

query_adata.obs["predicted_celltype"] = "unknown"
query_adata.obs_names = [f"query_{i}" for i in query_adata.obs_names]
supervised_resolvi.prepare_query_anndata(query_adata, reference_model=supervised_resolvi)
query_resolvi = supervised_resolvi.load_query_data(query_adata, reference_model=supervised_resolvi)
INFO     Found 100.0% reference vars in query data.                                                                
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/scvi/data/fields/_dataframe_field.py:227: UserWarning: Category 66457 in adata.obs['_scvi_ind_x'] has fewer than 3 cells. Models may not train properly.
  new_mapping = _make_column_categorical(
query_resolvi.train(max_epochs=20)
INFO: GPU available: True (cuda), used: True
2026-03-26 11:41:57 | [INFO] GPU available: True (cuda), used: True
INFO: TPU available: False, using: 0 TPU cores
2026-03-26 11:41:57 | [INFO] TPU available: False, using: 0 TPU cores
INFO: 💡 Tip: For seamless cloud logging and experiment tracking, try installing [litlogger](https://pypi.org/project/litlogger/) to enable LitLogger, which logs metrics and artifacts automatically to the Lightning Experiments platform.
2026-03-26 11:41:57 | [INFO] 💡 Tip: For seamless cloud logging and experiment tracking, try installing [litlogger](https://pypi.org/project/litlogger/) to enable LitLogger, which logs metrics and artifacts automatically to the Lightning Experiments platform.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/trainer/configuration_validator.py:68: You passed in a `val_dataloader` but have no `validation_step`. Skipping val loop.
INFO: LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
2026-03-26 11:41:57 | [INFO] LOCAL_RANK: 0 - CUDA_VISIBLE_DEVICES: [0]
Running transfer learning mode. Set lr to 0 and blocking variables.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/utilities/_pytree.py:21: `isinstance(treespec, LeafSpec)` is deprecated, use `isinstance(treespec, TreeSpec) and treespec.is_leaf()` instead.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/trainer/connectors/data_connector.py:434: The 'train_dataloader' does not have many workers which may be a bottleneck. Consider increasing the value of the `num_workers` argument` to `num_workers=63` in the `DataLoader` to improve performance.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/lightning/pytorch/loops/fit_loop.py:534: Found 52 module(s) in eval mode at the start of training. This may lead to unexpected behavior during training. If this is intentional, you can ignore this warning.
2026-03-26 11:41:57 | [INFO] Guessed max_plate_nesting = 2
INFO: `Trainer.fit` stopped: `max_epochs=20` reached.
2026-03-26 11:43:47 | [INFO] `Trainer.fit` stopped: `max_epochs=20` reached.
query_adata.obs["resolvi_predicted"] = query_resolvi.predict(
    query_adata, num_samples=3, soft=False
)
query_adata.obsm["X_resolVI"] = query_resolvi.get_latent_representation(query_adata)

Again, we may visualize the latent space as well as the inferred labels

sc.pp.neighbors(query_adata, use_rep="X_resolVI")
sc.tl.umap(query_adata)
sc.pl.umap(query_adata, color=["predicted_celltype", "resolvi_predicted"])
sc.pl.spatial(query_adata, color=["predicted_celltype", "resolvi_predicted"], spot_size=30)
... storing 'predicted_celltype' as categorical
... storing 'resolvi_predicted' as categorical
../_images/91daccb83629fdb519f2fa77785161f4d5bb03f107f9b0c45251e42771293e65.png
/tmp/ipykernel_2295458/1669651559.py:2: FutureWarning: Use `squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(query_adata, color=["predicted_celltype", "resolvi_predicted"], spot_size=30)
../_images/33a7b858e62d100acc9be087d8ee32c8c89f317c5f91227a5547b53011ae17ab.png

We can now concatenate the datasets again and find good integration and accurate cell-type information.

full_adata = ref_adata.concatenate(
    query_adata, batch_key="source", batch_categories=["reference", "query"]
)
/tmp/ipykernel_2295458/1142808005.py:1: FutureWarning: Use anndata.concat instead of AnnData.concatenate, AnnData.concatenate is deprecated and will be removed in the future. See the tutorial for concat at: https://anndata.readthedocs.io/en/latest/concatenation.html
  full_adata = ref_adata.concatenate(
sc.pp.neighbors(full_adata, use_rep="X_resolVI")
sc.tl.umap(full_adata)
sc.pl.umap(full_adata, color=["source", "resolvi_predicted"])
sc.pl.spatial(full_adata, color=["source", "resolvi_predicted"], spot_size=30)
../_images/7b174d94f047375ac46608b8b56eca45e029d775ba0e5cfea87472fcb147739d.png
/tmp/ipykernel_2295458/3452622550.py:2: FutureWarning: Use `squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(full_adata, color=["source", "resolvi_predicted"], spot_size=30)
../_images/2aaa61bbca50cb8f82c87484ae22a6ac45117b0c7be3044ec5346b729edb3364.png