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 scviva-tools
!pip install --quiet decoupler
!pip install --quiet adjustText
[notice] A new release of pip is available: 24.3.1 -> 26.1.2
[notice] To update, run: pip install --upgrade pip

[notice] A new release of pip is available: 24.3.1 -> 26.1.2
[notice] To update, run: pip install --upgrade pip

[notice] A new release of pip is available: 24.3.1 -> 26.1.2
[notice] To update, run: pip install --upgrade pip
import os
import tempfile
import rapids_singlecell as rsc

import decoupler as dc
import matplotlib.pyplot as plt
import numpy as np
import scanpy as sc
import scvi
import scviva
sc.set_figure_params(figsize=(4, 4))
save_dir = tempfile.TemporaryDirectory()
%config InlineBackend.print_figure_kwargs={'facecolor' : "w"}
%config InlineBackend.figure_format='retina'

scviva.settings.seed = 0
print("Last run with scVIVA-Tools version:", scviva.__version__)
INFO: Seed set to 0
2026-07-02 17:11:58 | [INFO] Seed set to 0
Last run with scVIVA-Tools version: 0.1.0
scvi.settings.seed = 0
print("Last run with scvi-tools version:", scvi.__version__)

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_1073223/1077018278.py:3: FutureWarning: The function spatial is deprecated and will be removed in the future. Use :func:`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_1073223/1090802531.py:2: FutureWarning: The function spatial is deprecated and will be removed in the future. Use :func:`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 scviva.model._resolvi import ResolVI
scviva.model.ResolVI.setup_anndata(ref_adata, labels_key="predicted_celltype", layer="counts")
Preparing data for training. This may take a while. RAPIDS SingleCell will be used if installed.
/opt/anaconda3/envs/scvi_new/lib/python3.13/site-packages/rapids_singlecell/__init__.py:38: UserWarning: 
Multiple rapids_singlecell packages are installed: rapids-singlecell, rapids-singlecell-cu13
Please uninstall all versions and reinstall only one:
  pip uninstall rapids-singlecell rapids-singlecell-cu13

  _detect_duplicate_installation()
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 = scviva.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-07-02 17:12:40 | [INFO] GPU available: True (cuda), used: True
INFO: TPU available: False, using: 0 TPU cores
2026-07-02 17:12:40 | [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-07-02 17:12:40 | [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-07-02 17:12:40 | [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-07-02 17:12:40 | [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/scviva/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-07-02 17:12:41 | [INFO] Guessed max_plate_nesting = 2
INFO: `Trainer.fit` stopped: `max_epochs=50` reached.
2026-07-02 17:32:26 | [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', '_resolvi_prepare_data_config', '_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

rsc.pp.neighbors(ref_adata, use_rep="X_resolVI")
rsc.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', '_resolvi_prepare_data_config', '_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/2250eda368a9616dd3ee60fe3e5f06182c117417e3db3ac72f07688830858f4b.png
/tmp/ipykernel_1073223/2132712317.py:2: FutureWarning: The function spatial is deprecated and will be removed in the future. Use :func:`squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(ref_adata, color=["predicted_celltype", "resolvi_predicted"], spot_size=30)
../_images/9c0946b4d81bb071a6f97f77ed59a3ce774b3b22cba02de51642d3651c576ffe.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
Gadd45a 0.9938 0.0062 5.076985 0.019484 0.003514 0.01 0.05 2.550474 2.592474 0.884700 ... 5.222747 1.181458 0.898206 0.518031 168.268661 32.135601 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Foxp2 0.9936 0.0064 5.045035 0.010652 0.001079 0.01 0.05 3.636607 3.594411 1.468941 ... 3.170064 0.392385 0.808502 0.260160 101.419388 10.955342 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Tle4 0.9926 0.0074 4.898846 0.027620 0.005790 0.01 0.05 2.432866 2.539701 0.866388 ... 7.856497 1.991429 0.978159 0.715799 256.799835 56.882290 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Nell1 0.9860 0.0140 4.254598 0.000880 0.007222 0.01 0.05 -2.939413 -3.050663 0.983502 ... 0.324106 2.712630 0.241030 0.655695 10.849372 70.231941 True Excitatory Neurons L6 vs Excitatory Neurons L5 Excitatory Neurons L6 Excitatory Neurons L5
Zfpm2 0.9860 0.0140 4.254598 0.006176 0.001280 0.01 0.05 2.402354 2.410070 0.930363 ... 1.707881 0.377217 0.702418 0.260446 55.549061 10.202839 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 Thalamus 0.9964 0.0036 5.623212 0.001628 0.523904 0.03 0.05 -11.533279 -12.521278 2.738604 -14.942905 7.574803 True Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Excitatory Neurons L4 0.9928 0.0072 4.926447 0.479629 0.001068 0.03 0.05 10.926589 11.232227 2.066000 -5.206793 14.967279 True Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Excitatory Neurons L2/3 0.8602 0.1398 1.816952 0.079553 0.000331 0.03 0.05 7.021642 7.038022 3.416842 -8.065561 14.513630 False Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Oligodendrocyte 0.6804 0.3196 0.755611 0.024570 0.095450 0.03 0.05 -5.705110 -6.621741 6.162485 -14.385056 12.383071 False Excitatory Neurons L4 vs Excitatory Neurons Th... Excitatory Neurons L4 Excitatory Neurons Thalamus
Inhibitory Neurons Cortical 0.6308 0.3692 0.535650 0.088699 0.000459 0.03 0.05 7.265851 10.995819 5.543326 -11.694252 14.018014 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.                                                                
Preparing data for training. This may take a while. RAPIDS SingleCell will be used if installed.
/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-07-02 17:36:14 | [INFO] GPU available: True (cuda), used: True
INFO: TPU available: False, using: 0 TPU cores
2026-07-02 17:36:14 | [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-07-02 17:36:14 | [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-07-02 17:36:14 | [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-07-02 17:36:14 | [INFO] Guessed max_plate_nesting = 2
INFO: `Trainer.fit` stopped: `max_epochs=20` reached.
2026-07-02 17:37:31 | [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

rsc.pp.neighbors(query_adata, use_rep="X_resolVI")
rsc.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/e820d63895bc1081af1aa9bc5a55ce6c0097750b0d06deca3ac3b8b706f16165.png
/tmp/ipykernel_1073223/1669651559.py:2: FutureWarning: The function spatial is deprecated and will be removed in the future. Use :func:`squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(query_adata, color=["predicted_celltype", "resolvi_predicted"], spot_size=30)
../_images/734c15a03ed49aeb0a91c11bdbe67ef76ecc5d87101dbeaee5b19bcf1d3aeae2.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_1073223/1142808005.py:1: FutureWarning: The method concatenate is deprecated and will be removed in the future. Use anndata.concat instead of AnnData.concatenate. See the tutorial for concat at: https://anndata.readthedocs.io/en/latest/concatenation.html
  full_adata = ref_adata.concatenate(
rsc.pp.neighbors(full_adata, use_rep="X_resolVI")
rsc.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/8e3181cb106a9d9fdad97a41a8aa4c3279624e7a93d8e10223f749ec19148ba6.png
/tmp/ipykernel_1073223/3452622550.py:2: FutureWarning: The function spatial is deprecated and will be removed in the future. Use :func:`squidpy.pl.spatial_scatter` instead.
  sc.pl.spatial(full_adata, color=["source", "resolvi_predicted"], spot_size=30)
../_images/5a6162637135e7f7583d85cce09f1b5eb02943b4efdcb2c0f6b3d7bc29d336e6.png
import gc

gc.collect()
import torch

torch.cuda.empty_cache()