From 0d270fc77afcd2db5dd9df72c9417d04b68d063d Mon Sep 17 00:00:00 2001 From: ori-kron-wis Date: Thu, 9 Jul 2026 14:02:22 +0300 Subject: [PATCH 1/4] Removed the spatial models from scvi-tools in v1.6!, inc all prepherial classes/funcrtion that are no longer used --- docs/api/developer.md | 6 - docs/api/user.md | 7 - docs/tutorials/index_spatial.md | 62 - docs/user_guide/index.md | 30 - docs/user_guide/models/destvi.md | 258 ---- docs/user_guide/models/gimvi.md | 204 --- docs/user_guide/models/index.md | 6 - docs/user_guide/models/resolvi.md | 195 --- docs/user_guide/models/scviva.md | 154 -- docs/user_guide/models/stereoscope.md | 185 --- src/scvi/external/__init__.py | 11 - src/scvi/external/gimvi/__init__.py | 4 - src/scvi/external/gimvi/_model.py | 690 --------- src/scvi/external/gimvi/_module.py | 529 ------- src/scvi/external/gimvi/_task.py | 195 --- src/scvi/external/gimvi/_utils.py | 115 -- src/scvi/external/resolvi/__init__.py | 4 - src/scvi/external/resolvi/_model.py | 748 ---------- src/scvi/external/resolvi/_module.py | 1324 ----------------- src/scvi/external/resolvi/_utils.py | 564 ------- src/scvi/external/scviva/__init__.py | 13 - src/scvi/external/scviva/_components.py | 262 ---- src/scvi/external/scviva/_constants.py | 30 - src/scvi/external/scviva/_log_likelihood.py | 109 -- src/scvi/external/scviva/_model.py | 1173 --------------- src/scvi/external/scviva/_module.py | 637 -------- .../differential_expression/__init__.py | 11 - .../differential_expression/_de_utils.py | 113 -- .../_marker_classifier.py | 105 -- .../differential_expression/_niche_de_core.py | 233 --- .../_results_dataclass.py | 260 ---- src/scvi/external/stereoscope/__init__.py | 4 - src/scvi/external/stereoscope/_model.py | 394 ----- src/scvi/external/stereoscope/_module.py | 265 ---- src/scvi/external/tangram/__init__.py | 4 - src/scvi/external/tangram/_model.py | 355 ----- src/scvi/external/tangram/_module.py | 164 -- src/scvi/model/__init__.py | 2 - src/scvi/model/_destvi.py | 700 --------- src/scvi/module/__init__.py | 2 - src/scvi/module/_mrdeconv.py | 676 --------- tests/external/gimvi/test_gimvi.py | 232 --- tests/external/resolvi/test_resolvi.py | 225 --- tests/external/scviva/test_scviva.py | 487 ------ .../external/stereoscope/test_stereoscope.py | 42 - tests/external/tangram/test_tangram.py | 95 -- tests/model/test_destvi.py | 194 --- 47 files changed, 12078 deletions(-) delete mode 100644 docs/tutorials/index_spatial.md delete mode 100644 docs/user_guide/models/destvi.md delete mode 100644 docs/user_guide/models/gimvi.md delete mode 100644 docs/user_guide/models/resolvi.md delete mode 100644 docs/user_guide/models/scviva.md delete mode 100644 docs/user_guide/models/stereoscope.md delete mode 100644 src/scvi/external/gimvi/__init__.py delete mode 100644 src/scvi/external/gimvi/_model.py delete mode 100644 src/scvi/external/gimvi/_module.py delete mode 100644 src/scvi/external/gimvi/_task.py delete mode 100644 src/scvi/external/gimvi/_utils.py delete mode 100644 src/scvi/external/resolvi/__init__.py delete mode 100644 src/scvi/external/resolvi/_model.py delete mode 100644 src/scvi/external/resolvi/_module.py delete mode 100644 src/scvi/external/resolvi/_utils.py delete mode 100644 src/scvi/external/scviva/__init__.py delete mode 100644 src/scvi/external/scviva/_components.py delete mode 100644 src/scvi/external/scviva/_constants.py delete mode 100644 src/scvi/external/scviva/_log_likelihood.py delete mode 100644 src/scvi/external/scviva/_model.py delete mode 100644 src/scvi/external/scviva/_module.py delete mode 100644 src/scvi/external/scviva/differential_expression/__init__.py delete mode 100644 src/scvi/external/scviva/differential_expression/_de_utils.py delete mode 100644 src/scvi/external/scviva/differential_expression/_marker_classifier.py delete mode 100644 src/scvi/external/scviva/differential_expression/_niche_de_core.py delete mode 100644 src/scvi/external/scviva/differential_expression/_results_dataclass.py delete mode 100644 src/scvi/external/stereoscope/__init__.py delete mode 100644 src/scvi/external/stereoscope/_model.py delete mode 100644 src/scvi/external/stereoscope/_module.py delete mode 100644 src/scvi/external/tangram/__init__.py delete mode 100644 src/scvi/external/tangram/_model.py delete mode 100644 src/scvi/external/tangram/_module.py delete mode 100644 src/scvi/model/_destvi.py delete mode 100644 src/scvi/module/_mrdeconv.py delete mode 100644 tests/external/gimvi/test_gimvi.py delete mode 100644 tests/external/resolvi/test_resolvi.py delete mode 100644 tests/external/scviva/test_scviva.py delete mode 100644 tests/external/stereoscope/test_stereoscope.py delete mode 100644 tests/external/tangram/test_tangram.py delete mode 100644 tests/model/test_destvi.py diff --git a/docs/api/developer.md b/docs/api/developer.md index d80eb6f72c..34e7f96ffd 100644 --- a/docs/api/developer.md +++ b/docs/api/developer.md @@ -178,12 +178,9 @@ Module classes in the external API with respective generative and inference proc :template: class_no_inherited.rst :nosignatures: - external.gimvi.JVAE external.cytovi.CytoVAE external.cellassign.CellAssignModule external.contrastivevi.ContrastiveDataSplitter - external.stereoscope.RNADeconv - external.stereoscope.SpatialDeconv external.scbasset.ScBassetModule external.contrastivevi.ContrastiveVAE external.velovi.VELOVAE @@ -191,10 +188,7 @@ Module classes in the external API with respective generative and inference proc external.methylvi.METHYLVAE external.methylvi.METHYLANVAE external.decipher.DecipherPyroModule - external.resolvi.RESOLVAE external.totalanvi.TOTALANVAE - external.scviva.nicheVAE - external.scviva.NicheLossOutput external.sysvi.SysVAE external.diagvi.DIAGVAE external.drvi.DRVIModule diff --git a/docs/api/user.md b/docs/api/user.md index c25177f33d..5cf33a0a58 100644 --- a/docs/api/user.md +++ b/docs/api/user.md @@ -25,7 +25,6 @@ import scvi model.AUTOZI model.CondSCVI - model.DestVI model.LinearSCVI model.PEAKVI model.SCANVI @@ -49,9 +48,6 @@ import scvi external.CellAssign external.CYTOVI - external.GIMVI - external.RNAStereoscope - external.SpatialStereoscope external.SOLO external.SCAR external.SCBASSET @@ -63,13 +59,10 @@ import scvi external.METHYLANVI external.Decipher external.TOTALANVI - external.RESOLVI external.SysVI - external.SCVIVA external.DIAGVI external.DRVI external.JointEmbeddingSCVI - external.Tangram ``` ## Data loading diff --git a/docs/tutorials/index_spatial.md b/docs/tutorials/index_spatial.md deleted file mode 100644 index c67af7294f..0000000000 --- a/docs/tutorials/index_spatial.md +++ /dev/null @@ -1,62 +0,0 @@ -# Spatial transcriptomics - -```{toctree} -:maxdepth: 1 - -notebooks/spatial/resolVI_tutorial -notebooks/spatial/scVIVA_tutorial -notebooks/spatial/DestVI_tutorial -notebooks/spatial/gimvi_tutorial -notebooks/spatial/tangram_scvi_tools -notebooks/spatial/stereoscope_heart_LV_tutorial -notebooks/spatial/cell2location_lymph_node_spatial_tutorial -``` - -```{customcard} -:path: notebooks/spatial/resolVI_tutorial -:tags: Analysis, Integration, Transfer-learning, Dimensionality-reduction - -Use resolVI to correct cellular-resolved spatial transcriptomics data. -``` - -```{customcard} -:path: notebooks/spatial/scVIVA_tutorial -:tags: Analysis, Integration, Dimensionality-reduction, Differential-comparison - -Stratify spatial transcriptomics data into niche-aware cell states with scVIVA -``` - -```{customcard} -:path: notebooks/spatial/DestVI_tutorial -:tags: Deconvolution, Modality-imputation, Differential-comparison - -Perform multi-resolution analysis on spatial transcriptomics data with DestVI -``` - -```{customcard} -:path: notebooks/spatial/gimvi_tutorial -:tags: Modality-imputation, Integration - -Use gimVI to impute missing genes in spatial data -``` - -```{customcard} -:path: notebooks/spatial/tangram_scvi_tools -:tags: Deconvolution, Analysis - -Use Tangram to map spatial transcriptomics data -``` - -```{customcard} -:path: notebooks/spatial/stereoscope_heart_LV_tutorial -:tags: Deconvolution, Integration - -Go through the Stereoscope workflow to map single-cell data -``` - -```{customcard} -:path: notebooks/spatial/cell2location_lymph_node_spatial_tutorial -:tags: Dev, Analysis, Integration - -Spatially map lymph node cell types using Cell2location -``` diff --git a/docs/user_guide/index.md b/docs/user_guide/index.md index a895900f67..8deb51983c 100644 --- a/docs/user_guide/index.md +++ b/docs/user_guide/index.md @@ -155,36 +155,6 @@ scvi-tools is composed of models that can perform one or many analysis tasks. In ``` -## Spatial transcriptomics analysis - -```{eval-rst} -.. list-table:: - :widths: 15 100 25 - :header-rows: 1 - - * - Model - - Tasks - - Reference - * - :doc:`/user_guide/models/destvi` - - Multi-resolution deconvolution, cell-type-specific gene expression imputation, comparative analysis - - :cite:p:`Lopez22` - * - :doc:`/user_guide/models/stereoscope` - - Deconvolution - - :cite:p:`Andersson20` - * - :doc:`/user_guide/models/gimvi` - - Imputation of missing spatial genes - - :cite:p:`Lopez19` - * - :doc:`/user_guide/models/tangram` - - Deconvolution, single cell spatial mapping - - :cite:p:`Biancalani21` - * - :doc:`/user_guide/models/resolvi` - - Generative model of single-cell resolved spatial transcriptomics - - :cite:p:`Ergen25` - * - :doc:`/user_guide/models/scviva` - - Representation of cells and their environments in spatial transcriptomics - - :cite:p:`Levy25` -``` - ## General purpose analysis ```{eval-rst} diff --git a/docs/user_guide/models/destvi.md b/docs/user_guide/models/destvi.md deleted file mode 100644 index 01ce1bcd96..0000000000 --- a/docs/user_guide/models/destvi.md +++ /dev/null @@ -1,258 +0,0 @@ -# DestVI - -**DestVI** {cite:p}`Lopez22` (Deconvolution of Spatial Transcriptomics profiles using Variational Inference; Python class {class}`~scvi.model.DestVI`) -posits a conditional generative model of spatial transcriptomics down to the sub-cell-type variation level which -can be used to explore the spatial organization of a tissue and understanding gene expression variation between tissues and conditions. - -The advantages of DestVI are: - -- Can stratify cells into discrete cell types and model continuous sub-cell-type variation. -- Scalable to very large datasets (>1 million cells). - -The limitations of DestVI include: - -- Effectively requires a GPU for fast inference. - -:::{note} -Starting scVI-Tools v1.5 this model is part of scVIVA-Tools, and no longer being maintained here. -::: - -```{topic} Tutorial: - -- {doc}`/tutorials/notebooks/spatial/DestVI_tutorial` -``` - -## Preliminaries - -DestVI requires training two models, the scLVM (single-cell latent variable model) and the -stLVM (spatial transcriptomic latent variable model). The scLVM takes in as input a scRNA-seq gene -expression matrix of UMI counts $X$ with $N$ cells and $G$ genes, along with -a vector of cell type labels $\vec{c}$. Subsequently, the stLVM takes in the trained scLVM, -along a spatial gene expression matrix $Y$ with $S$ spots and $G$ genes. -Optionally, the user can specify the number of components used for the mixture model underlying the -empirical prior. - -## Generative process - -### scLVM - -For cell $n$, the scLVM assumes observed discrete cell type labels $c_n$ and models -continuous covariates $\gamma_n$ of dimension $d$ to explain variation in gene expression within a cell type. -The scLVM posits that the observed UMI counts for cell $n$ are generated by the following process: - -```{math} -:nowrap: true - -\begin{align} - \gamma_n &\sim \textrm{Normal}(0, I) \tag{1} \\ - x_{ng} &\sim \textrm{NegativeBinomial}(l_nf^g(c_n, \gamma_n), p_g) \tag{2} \\ -\end{align} -``` - -where $l_n$ is the library size, $f$ is a two-layer neural network which outputs a $G$ -dimensional vector, and $p_g$ is the rate parameter of the negative binomial distribution for -a given gene $g$. - -:::{note} -We are using the standard rate-shape parametrization of the negative binomial here, rather than the mean-dispersion -parametrization used in {doc}`/user_guide/models/scvi`. This is to take advantage of the additive property of -negative binomial distributions sharing the same shape parameter. In this case, the rate parameter for the -negative binomial modeling the expression counts for a given gene and spot is equivalent to the sum of the rate -parameters for each contributing cell. -::: - -This generative process is also summarized in the following graphical model: - -:::{figure} figures/scLVM_graphical_model.svg -:align: center -:alt: scLVM graphical model -:class: img-fluid - -scLVM graphical model. -::: - -The latent variables for the scLVM, along with their description, are summarized in the following table: - -```{eval-rst} -.. list-table:: - :widths: 20 90 15 - :header-rows: 1 - - * - Latent variable - - Description - - Code variable (if different) - * - :math:`\gamma_n \in \mathbb{R}^d` - - Low-dimensional representation of sub-cell-type covariates. - - ``z`` - * - :math:`p_g \in (0, \infty)` - - Rate parameter for the negative binomial distribution. - - ``px_r`` -``` - -### stLVM - -For the stLVM, we also model the expression counts with a $\mathrm{NegativeBinomial}$. However, -for spatial data, we assume that each spot $s$ has expression $x_s$ composed of a bulk of cell types, with -cell type abundance, $\beta_{sc}$, for each cell type $c$. We assume that for a given spot $s$ -and gene $g$, the observation is generated as a function of the latent variables $(c, \gamma_s^c)$ by the following process: - -```{math} -:nowrap: true - -\begin{align} - \gamma_x^c &\sim \sum_{k=1}^K m_{kc} q_\Phi(\gamma^c \mid u_{kc}, c) \tag{4} \\ - x_{sg} &\sim \mathrm{NegativeBinomial}(l_s\alpha_g\sum_{c=1}^{C}\beta_{sc}f^g(c, \gamma_s^c), p_g) \tag{5} \\ -\end{align} -``` - -Where $l_s$ is the library size and $\alpha_g$ is a correction term for -difference in experimental assays. Like the scLVM, $f$ is a decoder neural network, and -$p_g$ is the rate parameter for the negative binomial distribution. - -To avoid the latent variable $\gamma_s^c$ from incorporating variation attributed to experimental -assay differences, we assign an empirical prior informed by the scLVM and the corresponding -cells of the same cell type in the scRNA-seq dataset. To compute this function, we cluster the latent space of the -scLVM for each cell type to K cell-type-specific clusters. For each cluster we compute an empirical mean and variance. -Above, $\{u_{kc}\}_{k=1}^K$ designates the set of cell-type-specific subclusters from cell type $c$ in the scRNA-seq dataset, and -$q_\Phi$ designates the empirical normal distribution from the computed cluster mean and variance. -The loss is weighted by the probability of a random cell from this cell type to be in the respective cluster in the -scRNA-seq dataset (mixture probability, $m_{kc}$). -In literature, the prior is referred to as a VampPrior ("variational aggregated mixture of posteriors" prior) [^ref2]. -More can be read on this prior in the DestVI paper. - -Lastly, an additional latent variable, $\eta_g$, is incorporated into the aggregated cell expression profile -as a dummy cell type to represent gene-specific noise. The dummy cell type's expression profile is distributed -as $\epsilon_g := \mathrm{Softplus}(\eta_g)$ where $\eta_g \sim \mathrm{Normal}(0, 1)$. -Like the other cell types, there is an associated cell type abundance parameter $\beta_{sc}$ associated with $\eta$. -We suspect each spot to only contain a fraction of the different cell types. To increase the sparsity of the cell type -proportions, the stLVM supports L1 regularization on the cell types proportions $\beta_{sc}$. By default, this loss is -not used. - -This generative process is also summarized in the following graphical model: - -:::{figure} figures/stLVM_graphical_model.svg -:align: center -:alt: stLVM graphical model -:class: img-fluid - -stLVM graphical model. -::: - -The latent variables for the stLVM, along with their description, are summarized in the following table: - -```{eval-rst} -.. list-table:: - :widths: 20 90 15 - :header-rows: 1 - - * - Latent variable - - Description - - Code variable (if different) - * - :math:`\beta_{sc} \in (0, \infty)` - - Spot-specific cell type abundance. - - ``v_ind`` - * - :math:`\gamma_s^c \in (-\infty, \infty)` - - Low-dimensional representation of sub-cell-type covariates for a given spot and cell type. - - ``gamma`` - * - :math:`\eta_g \in (0, \infty)` - - Gene-specific noise. - - ``eta`` - * - :math:`\alpha_g \in (0, \infty)` - - Correction term for technological differences. - - ``beta`` - * - :math:`p_g \in (0,\infty)` - - Rate parameter for the negative binomial distribution. - - ``px_o`` - -``` - -## Inference - -### scLVM - -DestVI uses variational inference and specifically auto-encoding variational bayes (see {doc}`/user_guide/background/variational_inference`) -to learn both the model parameters (the neural network params, rate params, etc.) and an approximate posterior distribution -for the scLVM. Like {class}`scvi.model.SCVI`, the underlying class used as the encoder for DestVI is {class}`~scvi.nn.Encoder`. - -### stLVM - -For the stLVM, DestVI infers point estimates for latent variables $\gamma^c, \alpha, \beta$ using a penalized -likelihood method. Beyond vanilla MAP inference, to regularize $\alpha$ a variance penalty is applied across all genes. -Additionally, rather than having just $C$ parameters per spot to denote the estimated cell type abundances per spot, the stLVM -has $dC$ parameters per spot as well to account for the latent space learned by the scLVM. - -The loss is defined as: - -```{math} -:nowrap: true - -\begin{align} - L(l, \alpha, \beta, f^g, \gamma, p, \eta) := &-\log p(X \mid l, \alpha, \beta, f^g, \gamma, p, \eta) - \lambda_{\eta} \log p(\eta) \\ - &+ \lambda_{\alpha} \mathrm{Var}(\alpha) - \log p(\gamma \mid \mathrm{VampPrior}) + \lambda_{\beta} \lVert \beta_{sc} \rVert_1 \tag{6} \\ -\end{align} -``` - -Where $\mathrm{Var}(\alpha)$ refers to the empirical variance of the parameters alpha across all genes. We used this as a practical form of regularization (a similar regularizer is used in the ZINB-WaVE model [^ref3]). - -$\lambda_{\beta}$ (`l1_reg` in code), $\lambda_{\eta}$ (`eta_reg` in code) and $\lambda_{\alpha}$ (`beta_reg` in code) are hyperparameters used to scale the loss term. Increasing $\lambda_{\beta}$ leads to increased sparsity of cell type proportions. Increasing $\lambda_{\alpha}$ leads to less model flexibility for technical variation between single cell and spatial sequencing dataset. Increasing $\lambda_{\eta}$ leads to more genes being explained by the dummy cell type (we recommend not changing the default value). -To avoid overfitting, DestVI amortizes inference using a neural network to parametrize the latent variables. -Via the `amortization` parameter of {class}`scvi.module.MRDeconv`, the user can specify which of -$\beta$ and $\gamma^c$ will be parametrized by the neural network. - -## Tasks - -### Cell type deconvolution - -Once the model is trained, one can retrieve the estimated cell type proportions in each spot using the method: - -``` ->>> proportions = st_model.get_proportions() ->>> st_adata.obsm["proportions"] = proportions -``` - -These proportions are computed by normalizing across all learned cell type abundances, $\beta_{sc}$, for a given spot $s$. -I.e., the estimated proportion of cell type $c$ for spot $s$ is $\frac{\beta_{sc}}{\sum_c \beta_{sc}}$. - -Subsequently for a given cell type, users can plot a heatmap of the cell type proportions spatially using scanpy with: - -``` ->>> import scanpy as sc ->>> st_adata.obs['B cells'] = st_adata.obsm['proportions']['B cells'] ->>> sc.pl.spatial(st_adata, color="B cells", spot_size=130) -``` - -### Intra cell type variation - -Users can retrieve the values of $\gamma$, the latent variables corresponding to the -modeled cell-type-specific continuous covariates with: - -``` ->>> gamma = st_model.get_gamma()["B cells"] ->>> st_adata.obsm["B_cells_gamma"] = gamma -``` - -### Cell-type-specific gene expression imputation - -Assuming the user has identified key gene modules that vary within a cell type of interest, they can -impute the spatial pattern of the cell-type-specific gene expression with: - -``` ->>> # Filter spots with low abundance. ->>> indices = np.where(st_adata.obsm["proportions"][ct_name].values > 0.03)[0] ->>> imputed_counts = st_model.get_scale_for_ct("Monocyte", indices=indices)[["Cxcl9", "Cxcl10", "Fcgr1"]] -``` - -### Comparative analysis between samples - -To perform differential expression across samples, one can apply a frequentist test by taking samples -from the parameters of the generative distribution predicted for each spot in question. - -### Utilities function - -To explore the results of the output of the stLVM, we published an utilities function covering functions -for automatic thresholding of cell type proportions, a spatial PCA analysis to find the main axis of variation -in spatial gene expression and the described frequentist test for differential expression. Further information -can be found on [destvi_utils](https://destvi-utils.readthedocs.io/en/latest/installation.html) - -[^ref2]: Jakub Tomczak, Max Welling (2018),_VAE with a VampPrior_, [Proceedings of Machine Learning Research](https://proceedings.mlr.press/v84/tomczak18a.html) -[^ref3]: Davide Risso, Fanny Perraudeau, Svetlana Gribkova, Sandrine Dudoit, Jean-Philippe Vert (2018). _A general and flexible method for signal extraction from single-cell RNA-seq data_, [Nature Communications] (https://www.nature.com/articles/s41467-017-02554-5) diff --git a/docs/user_guide/models/gimvi.md b/docs/user_guide/models/gimvi.md deleted file mode 100644 index 776829b36d..0000000000 --- a/docs/user_guide/models/gimvi.md +++ /dev/null @@ -1,204 +0,0 @@ -# gimVI - -**gimVI** {cite:p}`Lopez19` (Generative Imputation Model with Variational Inference; Python class {class}`~scvi.external.GIMVI`) is a deep generative model for the joint analysis of an scRNA-seq dataset and a spatial transcriptomics dataset that measures only a subset of the genes. gimVI learns a single shared latent space for the two modalities and uses it to **impute the genes that are missing from the spatial assay** by transferring information from the (genome-wide) sequencing reference. - -The two datasets are modeled by a single joint variational autoencoder ({class}`~scvi.external.gimvi.JVAE`) with modality-specific encoder and decoder heads but a shared latent representation. Because the spatial panel is a subset of the genes captured by scRNA-seq, the decoder reconstructs into the full sequencing gene space, and each modality's reconstruction loss is masked to only the genes it actually observes. Alignment of the two modalities in the shared latent space is encouraged by an adversarial classifier that is trained to predict the modality of origin from a cell's latent code, while the generative model is trained to fool it. - -The advantages of gimVI are: - -- Imputation of genes that are not measured in the spatial assay, using a paired (genome-wide) scRNA-seq reference. -- A shared latent space that integrates spatial and sequencing cells for joint visualization and clustering. -- Flexible, per-modality generative distributions (e.g. `zinb` for the sequencing data and `nb` for the spatial data) and per-modality choice of whether to model library size. -- Handles the two assays measuring different gene sets and different numbers of cells. - -The limitations of gimVI include: - -- Supports exactly two datasets (one sequencing, one spatial). -- The spatial genes must be a subset of the sequencing genes. -- Imputation quality depends on how well the scRNA-seq reference matches the spatial tissue. - -:::{note} -Starting scVI-Tools v1.5 this model is part of scVIVA-Tools, and no longer being maintained here. -::: - -```{topic} Tutorials: - -- {doc}`/tutorials/notebooks/spatial/gimvi_tutorial` -``` - -:::{note} -gimVI is a spatial transcriptomics model that will be moved to the scvi-tools spatial companion package `scviva-tools` starting in scvi-tools v1.5 and will no longer be supported in scvi-tools; it is scheduled for deprecation in v1.6. -::: - - -## Preliminaries - -gimVI takes as input two raw-count gene expression matrices: - -- a sequencing matrix $X^{(0)} \in \mathbb{N}^{N_0 \times G_0}$ with $N_0$ cells and $G_0$ genes (the full gene set), and -- a spatial matrix $X^{(1)} \in \mathbb{N}^{N_1 \times G_1}$ with $N_1$ cells and $G_1$ genes, - -where the spatial genes are a subset of the sequencing genes ($\mathcal{G}_1 \subseteq \mathcal{G}_0$). Internally the decoder operates on the full sequencing gene space of size $G = G_0$, and a per-modality index mapping $\mathcal{M}_m$ records where each modality's observed genes live in that space. The spatial reconstruction is therefore evaluated only on the columns corresponding to $\mathcal{G}_1$. - -Each dataset can additionally carry a categorical batch covariate $s$ and, optionally, categorical cell-type labels $c$. Both datasets are registered independently with two separate calls to {meth}`~scvi.external.GIMVI.setup_anndata` before the joint model is constructed: - -``` ->>> scvi.external.GIMVI.setup_anndata(adata_seq, batch_key="batch", labels_key="labels") ->>> scvi.external.GIMVI.setup_anndata(adata_spatial, batch_key="batch", labels_key="labels") ->>> model = scvi.external.GIMVI(adata_seq, adata_spatial) ->>> model.train() -``` - -The two datasets share a single batch space: batch indices of the spatial dataset are offset by the number of sequencing batches so that the two modalities never collide on a batch id (see [#2446](https://github.com/scverse/scvi-tools/issues/2446)). - -### Model architecture - -gimVI is built from the following components, which together define the [training objective](#training-objective): - -#### Joint encoder - -A single multi-head encoder ({class}`~scvi.nn.MultiEncoder`) maps both modalities into a shared $d$-dimensional latent space. Each modality has its own input layer(s) (because the two assays have different numbers of genes), followed by shared layers that produce the variational posterior $q(\mathbf{z}_n \mid x_n)$. The `mode` argument selects which input head is used. - -For each modality, an optional, separate library-size encoder produces a latent library size. Whether a modality models library size as a latent variable or instead uses the observed log total counts is controlled by `model_library_size` (default `[True, False]`: the sequencing data models library size, the spatial data uses the observed value). - -#### Joint decoder - -A single, shared multi-decoder ({class}`~scvi.nn.MultiDecoder`) maps the shared latent code back into the full sequencing gene space of size $G$, conditioned on the batch covariate (the decoder weights are **not** modality-specific). Because the decoder reconstructs all $G$ genes for both modalities, the latent representation of a spatial cell can be decoded into genes that were never measured spatially — this is exactly the imputation mechanism (see [Imputation of missing spatial genes](#imputation-of-missing-spatial-genes)). The modality index only selects which gene-index mapping is used to renormalize the decoded expression over each modality's observed genes; it does not route through different decoder networks. - -#### Adversarial modality classifier - -A small classifier ({class}`~scvi.module.Classifier`, 3 layers, 2 output classes) is trained to predict, from a latent code $\mathbf{z}$, which modality it came from. The generative model is trained adversarially to *fool* this classifier, which pushes the sequencing and spatial latent distributions to overlap and yields an integrated, modality-mixed latent space. This is implemented in {class}`~scvi.external.gimvi._task.GIMVITrainingPlan` (a subclass of {class}`~scvi.train.AdversarialTrainingPlan`), and the strength of the adversarial term is set by the `kappa` argument of {meth}`~scvi.external.GIMVI.train`. - - -## Generative process - -For a cell $n$ in modality $m \in \{0, 1\}$ (0 = sequencing, 1 = spatial) with batch $s_n$, gimVI posits the following generative process over the full gene space: - -```{math} -:nowrap: true - -\begin{align} - \mathbf{z}_n &\sim \mathrm{Normal}(0, I) \\ - \ell_n &\sim - \begin{cases} - \mathrm{LogNormal}(\ell_\mu^\top s_n,\ \ell_{\sigma^2}^\top s_n) & \text{if modality } m \text{ models library size} \\ - \delta(\log \textstyle\sum_g x_{ng}) & \text{otherwise (observed library size)} - \end{cases} \\ - \rho_n &= f_w(\mathbf{z}_n, s_n) \\ - x_{ng} &\sim \mathrm{ObservationModel}_m(\ell_n \rho_{ng},\ \theta_{s_n g}),\quad g \in \mathcal{G}_m -\end{align} -``` - -The latent code $\mathbf{z}_n$ is **shared across modalities** — the same prior and the same latent space are used for both datasets. The shared decoder $f_w$ produces normalized expression $\rho_n$ over the full gene set; for modality $m$, $\rho_n$ is renormalized to sum to 1 over only the genes observed in that modality ($\mathcal{M}_m$), and the mean rate is $\mu_{ng} = \ell_n \rho_{ng}$. - -The observation model is chosen per modality via `generative_distributions` (default `["zinb", "nb"]`). gimVI supports: - -- `zinb` — Zero-Inflated Negative Binomial (default for the sequencing data), -- `nb` — Negative Binomial (default for the spatial data), -- `poisson` — Poisson. - -The inverse dispersion $\theta$ is by default gene- and batch-specific (`dispersion="gene-batch"`); `gene` and `gene-label` parameterizations are also available. - -The latent variables are summarized below: - -```{eval-rst} -.. list-table:: - :widths: 25 60 15 - :header-rows: 1 - - * - Latent variable - - Description - - Code variable - * - :math:`\mathbf{z}_n \in \mathbb{R}^{d}` - - Shared low-dimensional representation of a cell, common to both modalities. - - ``z`` - * - :math:`\rho_n \in \Delta^{|\mathcal{M}_m|-1}` - - Denoised, normalized expression for cell :math:`n`, renormalized over the genes observed in its modality. - - ``px_scale`` - * - :math:`\ell_n \in (0, \infty)` - - Library size. Modeled as a latent variable for modalities flagged in ``model_library_size``; otherwise set to the observed log total count. - - ``library`` - * - :math:`\mu_{ng} \in (0, \infty)` - - Mean of the count likelihood, :math:`\ell_n \rho_{ng}`. - - ``px_rate`` - * - :math:`\theta_{s_n g} \in (0, \infty)` - - Inverse dispersion of the (zero-inflated) negative binomial, by default gene- and batch-specific. - - ``px_r`` -``` - - -## Inference - -gimVI uses variational inference (auto-encoding variational Bayes; see {doc}`/user_guide/background/variational_inference`) with the per-modality factorization - -```{math} -:nowrap: true - -\begin{align} - q_\eta(\mathbf{z}_n, \ell_n \mid x_n) := q_\eta(\mathbf{z}_n \mid x_n)\, q_\eta(\ell_n \mid x_n). -\end{align} -``` - -The latent posterior $q_\eta(\mathbf{z}_n \mid x_n)$ is produced by the shared multi-head encoder (using the input head for the cell's modality), and $q_\eta(\ell_n \mid x_n)$ by the modality's optional library encoder. Inputs are log-transformed (`log(1 + x)`) before encoding for numerical stability. - - -## Training objective - -gimVI minimizes a per-modality evidence lower bound — a reconstruction term plus KL terms on the latent variables — summed across both modalities, with an adversarial term added on top: - -- **Reconstruction**: the negative log-likelihood of the observed counts under the chosen observation model, **masked to the genes observed in each modality** (the spatial term ignores genes outside the spatial panel). -- **KL divergence on $\mathbf{z}$**: regularizes the shared latent variable toward the standard normal prior. -- **KL divergence on $\ell$**: applied only for modalities that model library size as a latent variable, with a batch-specific log-normal prior. -- **Adversarial loss**: the modality classifier is trained to predict modality from $\mathbf{z}$, while the generative model is trained to fool it. The relative weight is controlled by `kappa` (the `scale_adversarial_loss` of the training plan); when set to `"auto"` it follows `1 - kl_weight` over the warmup schedule. - -The two objectives (model vs. classifier) are optimized in alternating steps with manual optimization, as in {class}`~scvi.train.AdversarialTrainingPlan`. - -### Cyclic multi-dataset data loading - -The two datasets generally have different numbers of cells, so a custom data loader, {class}`~scvi.external.gimvi._task.CyclicMultiDataLoader`, combines their per-modality dataloaders: it iterates the longest loader once per epoch and **cycles the shorter loader(s)** so that every training step yields one minibatch from each modality. It accepts either a sequence (yielding a tuple of minibatches) or a mapping of modality name → loader (yielding a dict, preserving modality names). - -This loader is shared infrastructure: {class}`~scvi.external.DIAGVI` imports it (`from scvi.external.gimvi._task import CyclicMultiDataLoader as TrainDL`) and uses the mapping form to drive its own multi-modality training, so the same cycling logic backs both gimVI and DiagVI. - - -## Tasks - -Here we provide an overview of some of the tasks that gimVI can perform. Please see {class}`~scvi.external.GIMVI` for the full API reference. - -### Dimensionality reduction - -{meth}`~scvi.external.GIMVI.get_latent_representation` returns the shared latent representation for **each** dataset as a list `[latent_seq, latent_spatial]`. By default the mean of the approximate posterior is returned (`deterministic=True`). - -``` ->>> latent_seq, latent_spatial = model.get_latent_representation() ->>> adata_seq.obsm["X_gimvi"] = latent_seq ->>> adata_spatial.obsm["X_gimvi"] = latent_spatial -``` - -Because the latent space is shared and modality-mixed (via the adversarial classifier), the two representations can be concatenated for a joint neighbor graph and UMAP to assess integration quality: - -``` ->>> import scanpy as sc, anndata as ad, numpy as np ->>> joint = ad.AnnData(np.concatenate([latent_seq, latent_spatial])) ->>> joint.obs["modality"] = ["seq"] * len(latent_seq) + ["spatial"] * len(latent_spatial) ->>> sc.pp.neighbors(joint, use_rep="X") ->>> sc.tl.umap(joint) -``` - -### Imputation of missing spatial genes - -The central task of gimVI is to impute, for the spatial cells, the genes that were only measured in the scRNA-seq data. {meth}`~scvi.external.GIMVI.get_imputed_values` returns the decoded expression for **all** sequencing genes for each dataset: - -``` ->>> imputed_seq, imputed_spatial = model.get_imputed_values(normalized=True) ->>> # imputed_spatial now contains values for every sequencing gene, ->>> # including those absent from the original spatial panel. -``` - -- `normalized=True` (default) returns the normalized expression frequencies $\rho_n$ (decoder `sample_scale`); `normalized=False` returns the unnormalized mean rate $\mu_n = \ell_n \rho_n$ (decoder `sample_rate`). -- `decode_mode` selects which dataset's decoding is applied. The cell is always encoded with its own modality's encoder; gimVI then uses a **single shared decoder**, and `decode_mode` only changes which modality's gene-index mapping is used to renormalize the decoded expression over the probability simplex (`indices_mappings[decode_mode]`). It does not switch to a separate per-modality decoder network. - -A common evaluation (used in the tutorial) holds out a fraction of genes from the spatial panel, imputes them with gimVI, and measures the per-gene Spearman correlation between the held-out true spatial expression and the imputed values. - -### Saving and loading - -gimVI uses a custom {meth}`~scvi.external.GIMVI.save` / {meth}`~scvi.external.GIMVI.load` that persists the two registries and both sets of `var_names` (the model stores two AnnData managers). `save_anndata=True` writes both `adata_seq` and `adata_spatial` alongside the model. Legacy saves (pre-v0.15.0) can be upgraded with {meth}`~scvi.external.GIMVI.convert_legacy_save`. diff --git a/docs/user_guide/models/index.md b/docs/user_guide/models/index.md index fdd90c8d26..d9f38bb280 100644 --- a/docs/user_guide/models/index.md +++ b/docs/user_guide/models/index.md @@ -9,10 +9,8 @@ cellassign contrastivevi cytovi decipher -destvi diagvi drvi -gimvi jointembeddingscvi linearscvi methylanvi @@ -21,16 +19,12 @@ mrvi multivi peakvi poissonvi -resolvi scanvi scar scbasset scvi -scviva solo -stereoscope sysvi -tangram totalanvi totalvi velovi diff --git a/docs/user_guide/models/resolvi.md b/docs/user_guide/models/resolvi.md deleted file mode 100644 index 958a6e937e..0000000000 --- a/docs/user_guide/models/resolvi.md +++ /dev/null @@ -1,195 +0,0 @@ -# ResolVI - -**resolVI** {cite:p}`Ergen25` (Python class {class}`~scvi.external.RESOLVI`) is a generative model of single-cell resolved spatial -transcriptomics that can subsequently be used for many common downstream tasks. - -The advantages of resolVI are: - -- Addresses noise and bias in ST data due to wrong segmentation, unspecific background and limited spatial resolution -- Scalable to very large datasets (>1 million cells). - -The limitations of resolVI include: - -- Effectively requires a GPU for fast inference. -- Latent space is not interpretable, unlike that of a linear method. -- Assumes single cells are observed and do not work with low-resolution ST like Visium or Slide-Seq. - -:::{note} -Starting scVI-Tools v1.5 this model is part of scVIVA-Tools, and no longer being maintained here. -::: - -```{topic} Tutorials: - -- {doc}`/tutorials/notebooks/spatial/resolVI_tutorial` -``` - -## Preliminaries - -ResolVI takes as input spatially resolved RNA_seq count matrices downstream of cellular segmentation and molecule -assignments to cells. These counts can be either derived from sequencing spatially resolved molecules or fluorescent -imaging. ResolVI leverages the gene expression of neighboring cells and reassigns observed gene expression to neighboring -cells as well as an unspecific background. - -ResolVI accepts as input the observed expression of the cell itself, its spatial neighbors and their gene expression -as well as the distance between these cells. Additionally, a vector of categorical covariates $S$, representing -batch, donor, etc., is an optional input to the model. ResolVI provides a semi-supervised mode, adjusting the prior in -the latent space for different cell types and training a classifier to predict cell types from latent embeddings. - -## Generative process - -ResolVI posits that the observed expression of cell $n$ in gene $g$, $x_{ng}$ is generated by the following process: - -```{math} -:nowrap: true - -\begin{align} - z &\sim \mathrm{MixtureOfGaussians}(\mu_1, \dots, \mu_K, \Sigma_1, \dots, \Sigma_K) \\ - \alpha_n &\sim \mathrm{Dirichlet}(C) \\ - r_{ng} &\sim \mathrm{Exponential}(R) \\ - h_{ng} &= - \mathrm{Gamma}(r_{ng}, \frac{r_{ng}}{\alpha_0 f_\theta(z, b) + \alpha_1 \sum\limits_{{N(n)}} \beta_{N(n)} f_\theta(z_{N(n)}, b)}) + \alpha_2 bg\\ - x_{ng} &\sim \mathrm{Poisson}(l_n h_{ng}) -\end{align} -``` - -In particular, $z$ and $z_{N(n)}$ are the latent embeddings of the cell itself as well as its spatial neighbors -both of dimension $L$. ResolVI uses a mixture of Gaussians prior to $z$: - -```{math} -:nowrap: true - -\begin{align} - c_n &\sim \textrm{Categorical}( - \pi_1, \pi_2, \dots, \pi_K - ), \\ - z_n \mid c_n = c &\sim \mathcal{N}(\mu_c, \sigma_c) -\end{align} -``` - -In brief, we assume that observed expression of gene $g$ for cell $n$ can be modelled as a sum over -the components of expression truly expressed by the cell $\alpha_0$, the expression explained by neighboring -cells $\alpha_1$ and wrongly assigned to $n$ and a component due to unspecific background $\alpha_2$. -The expression of neighboring cells $N(n)$ is assigned to each of the neighboring cells $\beta_{N(n)n}$. -Both the expression of cell $n$ and the expression of neighboring cells $N(n)$ are generated using the same -generative network $f_\Theta$ from their respective latent code $z_{N(n)}$ and $z_n$. -This generative process uses a neural network: - -```{math} -:nowrap: true - -\begin{align} - f_{\theta}(z_{n}, s_n) &: \mathbb{R}^{d} \times \{0, 1\}^K \to \Delta^{G-1} -\end{align} -``` - -which estimates the normalized gene expression of cell $n$. We use the observed counts per cell to scale these rates. - -The latent variables, along with their description, are summarized in the following table: - -```{eval-rst} -.. list-table:: - :widths: 20 90 15 45 - :header-rows: 1 - - * - Latent variable - - Description - - Code variable (if different) - - Prior - * - :math:`z_n \in \mathbb{R}^L` - - Low-dimensional representation capturing the state of a cell - - ``latent`` - - Mixture-of-Gaussian - * - :math:`\beta_{N(n)} \in \Delta^{N(n) - 1}` - - Per-neighbor diffusion - - ``per_neighbor_diffusion`` - - Dirichlet - * - :math:`\alpha_{n0 \dots 2} \in \Delta^{2}` - - Per cell true, diffusion and background proportion - - ``mixture_proportions`` - - Dirichlet - * - :math:`bg_{ng} \in \Delta^{G - 1}` - - Per cell estimate of background - - ``background`` - - None - * - :math:`background_{s} \in \mathbb{R}^G` - - Per sample background vector - - ``per_gene_background`` - - Dirichlet - * - :math:`\rho_n \in \Delta^{G - 1}` - - Per cell rate of expression - - ``px_scale`` - - None - * - :math:`\mu_n, \mu_{N(n)} \in \mathbb{R}^G` - - Per cell estimated expression - - ``px_rate and px_rate_n`` - - None -``` - - -## Inference - -ResolVI uses variational inference, specifically auto-encoding variational Bayes -(see {doc}`/user_guide/background/variational_inference`) in Pyro to learn both the model parameters -(the neural network parameters, dispersion parameters, etc.) and an approximate posterior distribution. -We perform amortization using neural network for $z_n$ and $\alpha_n$, while $\beta_{N(n)n}$ is estimated -for each cell. - -## Tasks - -Here we provide an overview of some of the tasks that resolVI can perform. Please see {class}`scvi.external.RESOLVI` -for the full API reference. - -### Dimensionality reduction - -For dimensionality reduction, the mean of the approximate posterior $q_\phi(z_i \mid y_i, n_i)$ is returned by default. -This is achieved using the method: - -``` ->>> adata.obsm["X_resolvi"] = model.get_latent_representation() -``` - -Users may also return samples from this distribution, as opposed to the mean, by passing the argument `give_mean=False`. -The latent representation can be used to create a nearest neighbor graph with scanpy with: - -``` ->>> import scanpy as sc ->>> sc.pp.neighbors(adata, use_rep="X_resolvi") ->>> adata.obsp["distances"] -``` - -### Transfer learning - -A resolVI model can be pre-trained on reference data and updated with query data using {meth}`~scvi.external.RESOLVI.load_query_data`, which then facilitates transfer of metadata like cell type annotations. $\beta_{N(n)n}$ is extended to the new cells and learned on these cells. The encoder by default does not see the batch covariate, and $z_n$ can be predicted without performing query model training. See the {doc}`/user_guide/background/transfer_learning` guide for more information. - -### Estimation of true expression levels - -In {meth}`~scvi.external.RESOLVI.get_normalized_expression` ResolVI returns the expected true expression value of $\rho_n$ under the approximate posterior. For one cell $n$, this can be written as: - -```{math} -:nowrap: true - -\begin{align} - \mathbb{E}_{q_\phi(z_n \mid x_n)}\left[f_{\theta}\left(z_{n}, s_n \right) \right] -\end{align} -``` - -### Differential expression - -Differential expression analysis is achieved with {meth}`~scvi.external.RESOLVI.differential_expression`. -ResolVI tests differences in expression levels $\rho_{n} = f_{\theta}\left(z_n, s_n\right)$. -We allow for importance-based sampling using pyro's built-in function. - -### Cell-type prediction - -Prediction of cell-type labels is performed with {meth}`~scvi.external.RESOLVI.predict`. -A semisupervised model is necessary to perform this analysis as it leverages the cell-type classifier. -ResolVI performs for each cell $n$ $c_{n} = h_{nu}\left(z_n\right)$ and samples from $z_n$ to yield -the cell-type labels. - -### Differential niche abundance - -Differential niche abundance analysis is achieved with {meth}`~scvi.external.RESOLVI.differential_niche_abundance`. -A semisupervised model is necessary to perform this analysis as it leverages the cell-type classifier. -ResolVI tests differences in abundance of various cell-types in the neighborhood of a cell $n$ -$c_{n} = h_{nu}\left(z_n\right)$. Cell-type prediction vectors are averaged weighted by the distance of a specific cell -and differential computation is performed. diff --git a/docs/user_guide/models/scviva.md b/docs/user_guide/models/scviva.md deleted file mode 100644 index 68c7ab993f..0000000000 --- a/docs/user_guide/models/scviva.md +++ /dev/null @@ -1,154 +0,0 @@ -# scVIVA - -**scVIVA** {cite:p}`Levy25` (Python class {class}`~scvi.external.SCVIVA`) is a generative model of single-cell resolved spatial -transcriptomics that can subsequently be used for many common downstream tasks. - -The advantages of scVIVA are: - -- Provides a probabilistic low-dimensional representation of the state of each cell that is corrected for batch effects - and captures its gene expression profile and its environment. -- Enables differential expression analysis across niches while accounting for wrong assignment of molecules to cells. -- Scalable to very large datasets (>1 million cells). - -The limitations of scVIVA include: - -- Effectively requires a GPU for fast inference. -- Latent space is not interpretable, unlike that of a linear method. -- Assumes single cells are observed and do not work with low-resolution ST like Visium or Slide-Seq. - -:::{note} -Starting scVI-Tools v1.5 this model is part of scVIVA-Tools, and no longer being maintained here. -::: - -```{topic} Tutorials: - -- {doc}`/tutorials/notebooks/spatial/scVIVA_tutorial` -``` - -## Preliminaries - -scVIVA takes as input spatially resolved scRNA data. In addition to the gene expression matrix ${X}$ with $N$ cells and $G$ genes, -it requires for each cell $n$: -- the spatial coordinates of the cell $y_n$ -- the cell type assignment (possibly coarse) $c_n \in \{1, ..., T\}$ -- the batch assignment $s_n$. - - -As preprocessing, we take the $K$ nearest neighbors of a cell to define its niche using the Euclidean distance in physical space. -We characterize the niche by its cell-type composition and gene expression. We denote by ${\alpha_n}$ the $T$-dimensional vector of cell type -proportions among the $K$ nearest neighbors of the cell $n$. Its values are in the probability simplex. -The niche gene expression is defined as the average expression of each cell type present in the niche. -In practice, we leverage gene expression embeddings (PCA, scVI or similar) and characterize a cell type expression profile as the local average -embedding of cells of the same type. The average embeddings are stored in the matrix ${\eta_n} \in \mathbb{R}^{T \times D}$, where $D$ is the embedding dimension. -## Descriptive model - -We propose a latent variable model aiming to capture both gene expression heterogeneity and spatial variation resulting from the micro-environment. -We assume these two sources of variability are both captured by a $P$-dimensional latent variable ($P \ll G$): - -```{math} -:nowrap: true -\begin{align} - z_n \sim \mathbf{MixtureOfGaussians}(\mu_1, ..., \mu_M; \Sigma_1, ..,\Sigma_M; \pi_1, ...,\pi_M) -\end{align} -``` - -We assume that the observed counts for cell $n$ and gene $g$, $x_{ng}$, are generated from the following process: - -```{math} -:nowrap: true -\begin{align} - \rho _n &= f_{w}\left( z_n, s_n \right) \\ - x_{ng} &\sim \mathbf{NegativeBinomial}(\ell_n \rho_n, \theta_g), - \end{align} -``` -where $\rho_n$ is the normalized gene expression, $\ell_n$ is the library size of cell $n$ and $\theta_g$ is the dispersion parameter for gene $g$. See {doc}`/user_guide/models/scvi` for more details. -The cell-type proportions of the cell's $K$ nearest neighbors are obtained as - -```{math} -:nowrap: true -\begin{align} - \alpha_n &\sim \mathbf{Dirichlet}\left( f_{\omega}(z_n) \right), -\end{align} -``` - -Last, we assume that the neighboring cells' average expression profiles are obtained as - -```{math} -:nowrap: true -\begin{equation} -\eta_{nt} \sim -\begin{cases} -\mathcal{N} \left(f_{\nu}^{t}(z_n) \right), & \text{if } \alpha_{t} > 0 \\ -0, & \text{otherwise} -\end{cases} -\end{equation} -``` - -where $t=1,...,T$. $w$, $\omega$ and $\nu$ are neural network parameters. - - -## Inference - -We want to maximize the evidence of the data, which can be decomposed as: - -```{math} -:nowrap: true -\begin{align} - \log p \left( \alpha, x, \eta \mid s \right) = \log p \left(x \mid s \right) + \log p \left( \alpha, \eta \mid x, s \right). -\end{align} -``` - -scVIVA uses variational inference, specifically auto-encoding variational Bayes -(see {doc}`/user_guide/background/variational_inference`) to learn both the model parameters -(the neural network parameters, dispersion parameters, etc.) and an approximate posterior distribution. - -## Tasks - -Here we provide an overview of some of the tasks that scVIVA can perform. Please see {class}`scvi.external.SCVIVA` -for the full API reference. - -### Dimensionality reduction - -For dimensionality reduction, the mean of the approximate posterior $q_\phi(z \mid x)$ is returned by default. -This is achieved using the method: - -``` ->>> adata.obsm["X_scVIVA"] = model.get_latent_representation() -``` - -$\phi$ is a set of parameters corresponding to inference neural networks (encoders). -Users may also return samples from this distribution, as opposed to the mean, by passing the argument `give_mean=False`. - -### Estimation of normalized expression - -In {meth}`~scvi.external.SCVIVA.get_normalized_expression` scVIVA returns the expected true expression value of $\rho_n$ under the approximate posterior. For one cell $n$, this can be written as: - -```{math} -:nowrap: true - -\begin{align} - \mathbb{E}_{q_\phi(z_n \mid x_n)}\left[f_{w}\left(z_{n}, s_n \right) \right] -\end{align} -``` - -### Differential Expression (DE) - -Differential expression analysis is achieved with {meth}`~scvi.external.SCVIVA.differential_expression`. \ -We leverage the lvm-DE method (see {doc}`/user_guide/background/differential_expression`) and adapt it to spatial data by taking into account cell neighborhood expression in a bid to discard false positives due to contamination. \ -Considering two groups of cells $\textit{C1}$ and $\textit{C2}$ corresponding to different spatial contexts (for instance, astrocytes in two brain regions), the goal is to determine which genes have different expression levels between the two groups. When setting `niche_mode="true"`, we compute the group spatial neighborhoods $\textit{N1}$ and $\textit{N2}$, which are the spatial nearest neighbors of a different type than the cells in $\textit{C1}$, and $\textit{C2}$ respectively. - - -To determine the upregulated genes of $\textit{C1 vs C2}$, we compute DE between $\{\textit{C1, C2}\}$, $\{\textit{N1, C2}\}$ and $\{\textit{C1, N1}\}$: using lvm-DE, we test differences in expression levels $\rho_{n}$ to compute Log-Fold Changes (LFC). \ -The significantly upregulated genes for $\textit{C1, N1}$ define a set of local cell type markers, denoted $\mathcal{S}_1$. Conversely, if a gene is both higher expressed in $\textit{N1}$ compared to $\textit{C1}$ and $\textit{C1}$ compared to $\textit{C2}$, it is likely that the increased expression in $\textit{C1}$ is spurious. -We argue that the probability of a gene being a $\textit{local marker}$ could be a relevant score to filter spurious genes. To compute this score, we considered the upregulation of a gene in one group relative to the upregulation in its neighborhood: a local marker $g$ should verify - -```{math} -:nowrap: true -\begin{align} - \mathit{LFC^{~g}_{C1~vs~C2}} > \mathit{LFC^{~g}_{N1~vs~C2}}, -\end{align} -``` - -which means that the signal comes from cells in $\textit{C1}$ rather than their neighbors $\textit{N1}$. \ -We select genes for which $\mathit{LFC_{C1~vs~C2}} > 0$ and use the genes $\mathcal{S}_1$ as truly differentially expressed. We also define $\mathcal{N}_1 = \{g|\mathit{LFC^{~g}_{C1~vs~C2}} > 0,~g \notin \mathcal{S}_1 \}$. \ -We train a Gaussian process classifier on $\mathbf{X} = [LFC_{C1~vs~C2}~,~LFC_{N1~vs~C2}]$ to classify between the $\textit{local markers}$ $\mathcal{S}_1$ and the $\textit{neighborhood genes}$ $\mathcal{N}_1$. Once fitted, the classifier returns a local marker probability $p_g=\mathit{p}(g \in \mathcal{S}_1 | \mathbf{X})$ for each gene $g$, that we can compare to a given threshold $\tau$ to filter the neighborhood genes. diff --git a/docs/user_guide/models/stereoscope.md b/docs/user_guide/models/stereoscope.md deleted file mode 100644 index bd59aadb84..0000000000 --- a/docs/user_guide/models/stereoscope.md +++ /dev/null @@ -1,185 +0,0 @@ -# Stereoscope - -**Stereoscope** {cite:p}`Andersson20` (Python classes {class}`~scvi.external.RNAStereoscope` and {class}`~scvi.external.SpatialStereoscope`) posits a probabilistic model of spatial transcriptomics and an associated -method for the deconvoluton of cell type profiles using a single-cell RNA sequencing reference dataset. - -The advantages of Stereoscope are: - -- Can stratify cells into discrete cell types. -- Scalable to very large datasets (>1 million cells). - -The limitations of Stereoscope include: - -- Effectively requires a GPU for fast inference. - -:::{note} -Starting scVI-Tools v1.5 this model is part of scVIVA-Tools, and no longer being maintained here. -::: - -```{topic} Tutorial: - -- {doc}`/tutorials/notebooks/spatial/stereoscope_heart_LV_tutorial` -``` - -## Preliminaries - -Stereoscope requires training two latent variable models (LVMs): one for the single-cell reference -dataset and one for the spatial transcriptomics dataset, which incorporates the learned parameters of the -single-cell reference LVM. The first LVM takes in as input a scRNA-seq gene expression matrix of UMI counts -$Y$ with $N$ cells and $G$ genes, along with a vector of cell type labels $\vec{z}$. -Subsequently, the second LVM takes in the learned parameters of the first LVM, along with a spatial gene -expression matrix $X$ with $S$ spots and $G$ genes. - -## Generative process - -### Single-cell reference LVM - -For cell $c$, the LVM assumes an observed discrete cell type label $z_c$ and models -the UMI count observation for a given gene $g$ as a negative binomial distribution. This LVM posits that the observed -UMI counts for cell $c$ and gene $g$ are generated by the following process: - -```{math} -:nowrap: true - -\begin{align} - y_{gc} &\sim \textrm{NegativeBinomial}(s_{c}r_{gz}, p_{g}) \tag{1} \\ -\end{align} -``` - -where $s_c = \sum_{g\in G} y_{gc}$ is the observed library size of the cell, -$r_{gz}$ is the latent rate parameter for the cell type $z_c$ and gene $g$, -and $p_g$ is the latent variable representing the success probability for gene $g$. - -:::{note} -We are using the standard rate-shape parametrization of the negative binomial here, rather than the mean-dispersion -parametrization used in {doc}`/user_guide/models/scvi`. This is to take advantage of the additive property of -negative binomial distributions sharing the same shape parameter. In this case, the rate parameter for the -negative binomial modeling the expression counts for a given gene and spot is equivalent to the sum of the rate -parameters for each contributing cell. -::: - -This generative process is also summarized in the following graphical model: - -:::{figure} figures/stsc_scLVM_graphical_model.svg -:align: center -:alt: single-cell reference LVM graphical model -:class: img-fluid - -single-cell reference LVM graphical model. -::: - -The latent variables for the single-cell reference LVM, along with their description, are summarized in the following table: - -```{eval-rst} -.. list-table:: - :widths: 20 90 15 - :header-rows: 1 - - * - Latent variable - - Description - - Code variable (if different) - * - :math:`r_{gz} \in (0, \infty)` - - Rate parameter for the negative binomial distribution. - - ``px_scale`` - * - :math:`p_g \in [0, 1]` - - Shape parameter for the negative binomial distribution. - - ``px_o`` :math:`:= \log \left( \frac{p_g}{1 - p_g} \right)` -``` - -### Spatial transcriptomics LVM - -For the second LVM, we also model the expression counts with a $\mathrm{NegativeBinomial}$. However, -for spatial data, we assume that each spot $s$ has expression $x_s$ composed of a bulk of cell types, with -cell type abundance, $v_{sz}$, for each cell type $z$. We assume that for a given spot $s$ and gene $g$, -the observation is generated by the following process: - -```{math} -:nowrap: true - -\begin{align} - x_{sg} &\sim \mathrm{NegativeBinomial}(\beta_g\sum_{z\in Z}v_{sz}r_{gz}, p_g) \tag{2} \\ -\end{align} -``` - -where $\beta_g$ is a gene-specific correction term for technical differences. -The parameters $r_{gz}$ and $p_g$ are the learned parameters from the first LVM. - -An additional latent variable, $\eta_g$, is incorporated into the aggregated cell expression profile -as a dummy cell type to represent gene-specific noise. The dummy cell type's expression profile is distributed -as $\varepsilon_g := \mathrm{Softplus}(\eta_g)$ where $\eta_g \sim \mathrm{Normal}(0, 1)$ to avoid the model -from incorrectly assigning explanatory power to this term. -Like the other cell types, there is an associated cell type abundance parameter $\gamma_s$ associated with $\varepsilon$. - -This generative process is also summarized in the following graphical model: - -:::{figure} figures/stsc_stLVM_graphical_model.svg -:align: center -:alt: spatial transcriptomics LVM graphical model -:class: img-fluid - -spatial transcriptomics LVM graphical model. -::: - -The latent variables for the spatial transcriptomics LVM, along with their description, are summarized in the following table: - -```{eval-rst} -.. list-table:: - :widths: 20 90 15 - :header-rows: 1 - - * - Latent variable - - Description - - Code variable (if different) - * - :math:`v_{sz} \in (0, \infty)` - - Spot-specific cell type abundance. The code variable ``v_ind`` also incorporates the - the abundance term, :math:`\gamma_s` for the dummy noise cell type, :math:`\varepsilon`. - - ``v_ind`` - * - :math:`\eta_g \in (-\infty, \infty)` - - Gene-specific noise. Incorporated into the model as :math:`\varepsilon_g := \mathrm{Softplus}(\eta_g)`. - - ``eta`` - * - :math:`\beta_g \in (0, \infty)` - - Correction term for technological differences. - - ``beta`` - * - :math:`r_{gz} \in (0, \infty)` - - Rate parameter for the negative binomial distribution shared from the single-cell reference LVM. - - ``w`` - * - :math:`p_g \in [0,1]` - - Shape parameter for the negative binomial distribution shared from the single-cell reference LVM. - - ``px_o`` :math:`:= \log \left( \frac{p_g}{1 - p_g} \right)` - -``` - -## Inference - -### Single-cell reference LVM - -Stereoscope uses maximum likelihood estimation to estimate the parameters of the first LVM w.r.t. The negative binomial model of -UMI observations. This is achieved via stochastic gradient ascent on the likelihood function using the Pytorch framework. - -### Spatial transcriptomics LVM - -For the spatial transcriptomics LVM, Stereoscope uses MAP inference to estimate the parameters specific to the model. To be exact, -the only parameter given a non-uniform prior is $\eta_g$ that is posited as a gene-specific random effect distributed by a standard -Normal prior. Note, the $r_{gz}$ and $p_g$ parameters are not inferred in this step, but held fixed as the parameters shared by the -single-cell reference LVM. - -## Tasks - -### Cell type deconvolution - -Once the model is trained, one can retrieve the estimated cell type proportions in each spot using the method: - -``` ->>> proportions = spatial_model.get_proportions() ->>> st_adata.obsm["proportions"] = proportions -``` - -These proportions are computed by normalizing across all learned cell type abundances, $v_{sz}$, for a given spot $s$. -I.e., the estimated proportion of cell type $z$ for spot $s$ is $\frac{v_{sz}}{\sum_{z'} v_{sz'}}$. - -Subsequently, for a given cell type, users can plot a heatmap of the cell type proportions spatially using scanpy with: - -``` ->>> import scanpy as sc ->>> sc.p1.embedding(st_adata, basis="location", color="B cells") -``` diff --git a/src/scvi/external/__init__.py b/src/scvi/external/__init__.py index 931e5ef7f3..fdd008586a 100644 --- a/src/scvi/external/__init__.py +++ b/src/scvi/external/__init__.py @@ -4,33 +4,24 @@ from .decipher import Decipher from .diagvi import DIAGVI from .drvi import DRVI -from .gimvi import GIMVI from .joint_embedding_scvi import JointEmbeddingSCVI, JointEmbeddingVAE from .methylvi import METHYLANVI, METHYLVI from .mrvi import MRVI from .poissonvi import POISSONVI -from .resolvi import RESOLVI from .scar import SCAR from .scbasset import SCBASSET -from .scviva import SCVIVA from .solo import SOLO -from .stereoscope import RNAStereoscope, SpatialStereoscope from .sysvi import SysVI -from .tangram import Tangram from .totalanvi import TOTALANVI from .velovi import VELOVI __all__ = [ "SCAR", "SOLO", - "GIMVI", "JointEmbeddingSCVI", "JointEmbeddingVAE", "Decipher", - "RNAStereoscope", - "SpatialStereoscope", "CellAssign", - "Tangram", "TOTALANVI", "SCBASSET", "POISSONVI", @@ -40,8 +31,6 @@ "MRVI", "METHYLVI", "METHYLANVI", - "RESOLVI", - "SCVIVA", "CYTOVI", "DIAGVI", "DRVI", diff --git a/src/scvi/external/gimvi/__init__.py b/src/scvi/external/gimvi/__init__.py deleted file mode 100644 index 93037019a3..0000000000 --- a/src/scvi/external/gimvi/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from ._model import GIMVI -from ._module import JVAE - -__all__ = ["GIMVI", "JVAE"] diff --git a/src/scvi/external/gimvi/_model.py b/src/scvi/external/gimvi/_model.py deleted file mode 100644 index 9a884cca99..0000000000 --- a/src/scvi/external/gimvi/_model.py +++ /dev/null @@ -1,690 +0,0 @@ -from __future__ import annotations - -import logging -import os -import warnings -from typing import TYPE_CHECKING - -import numpy as np -import torch - -from scvi import REGISTRY_KEYS, settings -from scvi.data import AnnDataManager -from scvi.data._compat import registry_from_setup_dict -from scvi.data._constants import _MODEL_NAME_KEY, _SETUP_ARGS_KEY -from scvi.data.fields import CategoricalObsField, LayerField -from scvi.dataloaders import DataSplitter -from scvi.model._utils import _init_library_size, parse_device_args -from scvi.model.base import BaseModelClass, RNASeqMixin, VAEMixin -from scvi.train import Trainer -from scvi.train._config import merge_kwargs -from scvi.utils import setup_anndata_dsp -from scvi.utils._docstrings import devices_dsp - -from ._module import JVAE -from ._task import CyclicMultiDataLoader as TrainDL -from ._task import GIMVITrainingPlan -from ._utils import _load_legacy_saved_gimvi_files, _load_saved_gimvi_files - -if TYPE_CHECKING: - from anndata import AnnData - - from scvi.dataloaders import AnnDataLoader - -logger = logging.getLogger(__name__) - - -def _unpack_tensors(tensors): - x = tensors[REGISTRY_KEYS.X_KEY].squeeze_(0) - batch_index = tensors[REGISTRY_KEYS.BATCH_KEY].squeeze_(0) - y = tensors[REGISTRY_KEYS.LABELS_KEY].squeeze_(0) - return x, batch_index, y - - -class GIMVI(VAEMixin, RNASeqMixin, BaseModelClass): - """Joint VAE for imputing missing genes in spatial data :cite:p:`Lopez19`. - - Parameters - ---------- - adata_seq - AnnData object that has been registered via :meth:`~scvi.external.GIMVI.setup_anndata` - and contains RNA-seq data. - adata_spatial - AnnData object that has been registered via :meth:`~scvi.external.GIMVI.setup_anndata` - and contains spatial data. - n_hidden - Number of nodes per hidden layer. - generative_distributions - List of generative distribution for adata_seq data and adata_spatial data. Defaults to - ['zinb', 'nb']. - model_library_size - List of bool of whether to model library size for adata_seq and adata_spatial. Defaults to - [True, False]. - n_latent - Dimensionality of the latent space. - **model_kwargs - Keyword args for :class:`~scvi.external.gimvi.JVAE` - - Examples - -------- - >>> adata_seq = anndata.read_h5ad(path_to_anndata_seq) - >>> adata_spatial = anndata.read_h5ad(path_to_anndata_spatial) - >>> scvi.external.GIMVI.setup_anndata(adata_seq) - >>> scvi.external.GIMVI.setup_anndata(adata_spatial) - >>> vae = scvi.model.GIMVI(adata_seq, adata_spatial) - >>> vae.train(n_epochs=400) - - Notes - ----- - See further usage examples in the following tutorials: - - 1. :doc:`/tutorials/notebooks/spatial/gimvi_tutorial` - """ - - def __init__( - self, - adata_seq: AnnData, - adata_spatial: AnnData, - generative_distributions: list[str] | None = None, - model_library_size: list[bool] | None = None, - n_latent: int = 10, - **model_kwargs, - ): - warnings.warn( - "GIMVI is a spatial transcriptomics model that will be moved to the " - "scvi-tools spatial companion package `scviva-tools` starting in scvi-tools v1.5 and " - "will no longer be supported here. It will be deprecated from scvi-tools in v1.6.", - FutureWarning, - stacklevel=settings.warnings_stacklevel, - ) - super().__init__() - if adata_seq is adata_spatial: - raise ValueError( - "`adata_seq` and `adata_spatial` cannot point to the same object. " - "If you would really like to do this, make a copy of the object and pass it in as " - "`adata_spatial`." - ) - model_library_size = model_library_size or [True, False] - generative_distributions = generative_distributions or ["zinb", "nb"] - self.adatas = [adata_seq, adata_spatial] - self.adata_managers = { - "seq": self._get_most_recent_anndata_manager(adata_seq, required=True), - "spatial": self._get_most_recent_anndata_manager(adata_spatial, required=True), - } - self.registries_ = [] - for adm in self.adata_managers.values(): - self._register_manager_for_instance(adm) - self.registries_.append(adm.registry) - - seq_var_names = adata_seq.var_names - spatial_var_names = adata_spatial.var_names - - if not set(spatial_var_names) <= set(seq_var_names): - raise ValueError("spatial genes needs to be subset of seq genes") - - spatial_gene_loc = [np.argwhere(seq_var_names == g)[0] for g in spatial_var_names] - spatial_gene_loc = np.concatenate(spatial_gene_loc) - gene_mappings = [slice(None), spatial_gene_loc] - sum_stats = [adm.summary_stats for adm in self.adata_managers.values()] - n_inputs = [s["n_vars"] for s in sum_stats] - - total_genes = n_inputs[0] - - adata_seq_n_batches = sum_stats[0]["n_batch"] - adata_spatial_batch = adata_spatial.obs[ - self.adata_managers["spatial"].data_registry[REGISTRY_KEYS.BATCH_KEY].attr_key - ] - if np.min(adata_spatial_batch) == 0: - # see #2446 - # since we are combining datasets, we need to increment the batch_idx of one of the - # datasets. we only need to do this once so we check if the min is 0 - adata_spatial.obs[ - self.adata_managers["spatial"].data_registry[REGISTRY_KEYS.BATCH_KEY].attr_key - ] += adata_seq_n_batches - - n_batches = sum(s["n_batch"] for s in sum_stats) - - library_log_means = [] - library_log_vars = [] - for adata_manager in self.adata_managers.values(): - adata_library_log_means, adata_library_log_vars = _init_library_size( - adata_manager, n_batches - ) - library_log_means.append(adata_library_log_means) - library_log_vars.append(adata_library_log_vars) - - self.module = JVAE( - n_inputs, - total_genes, - gene_mappings, - generative_distributions, - model_library_size, - library_log_means, - library_log_vars, - n_batch=n_batches, - n_latent=n_latent, - **model_kwargs, - ) - - self._model_summary_string = ( - "GimVI Model with the following params: \nn_latent: {}, n_inputs: {}, n_genes: {}, " - + "n_batch: {}, generative distributions: {}" - ).format(n_latent, n_inputs, total_genes, n_batches, generative_distributions) - self.init_params_ = self._get_init_params(locals()) - - @devices_dsp.dedent - def train( - self, - max_epochs: int = 200, - accelerator: str = "auto", - devices: int | list[int] | str = "auto", - kappa: int = 5, - train_size: float | None = None, - validation_size: float | None = None, - shuffle_set_split: bool = True, - batch_size: int = 128, - datasplitter_kwargs: dict | None = None, - plan_kwargs: dict | None = None, - **kwargs, - ): - """Train the model. - - Parameters - ---------- - max_epochs - Number of passes through the dataset. If `None`, defaults to - `np.min([round((20000 / n_cells) * 400), 400])` - %(param_accelerator)s - %(param_devices)s - kappa - Scaling parameter for the discriminator loss. - train_size - Size of training set in the range [0.0, 1.0]. - validation_size - Size of the test set. If `None`, defaults to 1 - `train_size`. If - `train_size + validation_size < 1`, the remaining cells belong to a test set. - shuffle_set_split - Whether to shuffle indices before splitting. If `False`, the val, train, and test set - are split in the sequential order of the data according to `validation_size` and - `train_size` percentages. - batch_size - Minibatch size to use during training. - datasplitter_kwargs - Additional keyword arguments passed into :class:`~scvi.dataloaders.DataSplitter`. - plan_kwargs - Keyword args for model-specific Pytorch Lightning task. Keyword arguments passed - to `train()` will overwrite values present in `plan_kwargs`, when appropriate. - **kwargs - Other keyword args for :class:`~scvi.train.Trainer`. - """ - accelerator, devices, device = parse_device_args( - accelerator=accelerator, - devices=devices, - return_device="torch", - ) - datasplitter_kwargs = datasplitter_kwargs or {} - - self.trainer = Trainer( - max_epochs=max_epochs, - accelerator=accelerator, - devices=devices, - **kwargs, - ) - self.train_indices_, self.test_indices_, self.validation_indices_ = [], [], [] - train_dls, test_dls, val_dls = [], [], [] - for i, adm in enumerate(self.adata_managers.values()): - ds = DataSplitter( - adm, - train_size=train_size, - validation_size=validation_size, - batch_size=batch_size, - shuffle_set_split=shuffle_set_split, - **datasplitter_kwargs, - ) - ds.setup() - train_dls.append(ds.train_dataloader()) - test_dls.append(ds.test_dataloader()) - val = ds.val_dataloader() - val_dls.append(val) - val.mode = i - self.train_indices_.append(ds.train_idx) - self.test_indices_.append(ds.test_idx) - self.validation_indices_.append(ds.val_idx) - train_dl = TrainDL(train_dls) - - plan_kwargs = merge_kwargs(None, plan_kwargs, name="plan") - self._training_plan = GIMVITrainingPlan( - self.module, - adversarial_classifier=True, - scale_adversarial_loss=kappa, - **plan_kwargs, - ) - - if train_size == 1.0: - # circumvent the empty data loader problem if all dataset used for training - self.trainer.fit(self._training_plan, train_dl) - else: - # accepts list of val dataloaders - self.trainer.fit(self._training_plan, train_dl, val_dls) - try: - self.history_ = self.trainer.logger.history - except AttributeError: - self.history_ = None - self.module.eval() - - self.to_device(device) - self.is_trained_ = True - - def _make_scvi_dls( - self, adatas: list[AnnData] = None, batch_size: int = 128 - ) -> list[AnnDataLoader]: - if adatas is None: - adatas = self.adatas - post_list = [self._make_data_loader(ad, batch_size=batch_size) for ad in adatas] - for i, dl in enumerate(post_list): - dl.mode = i - - return post_list - - @torch.inference_mode() - def get_latent_representation( - self, - adatas: list[AnnData] = None, - deterministic: bool = True, - batch_size: int = 128, - ) -> list[np.ndarray]: - """Return the latent space embedding for each dataset. - - Parameters - ---------- - adatas - List of adata seq and adata spatial. - deterministic - If true, use the mean of the encoder instead of a Gaussian sample. - batch_size - Minibatch size for data loading into model. - """ - if adatas is None: - adatas = self.adatas - scdls = self._make_scvi_dls(adatas, batch_size=batch_size) - self.module.eval() - latents = [] - for mode, scdl in enumerate(scdls): - latent = [] - for tensors in scdl: - ( - sample_batch, - *_, - ) = _unpack_tensors(tensors) - latent.append( - self.module.sample_from_posterior_z( - sample_batch, mode, deterministic=deterministic - ) - .cpu() - .detach() - ) - - latent = torch.cat(latent).numpy() - latents.append(latent) - - return latents - - @torch.inference_mode() - def get_imputed_values( - self, - adatas: list[AnnData] = None, - deterministic: bool = True, - normalized: bool = True, - decode_mode: int | None = None, - batch_size: int = 128, - ) -> list[np.ndarray]: - """Return imputed values for all genes for each dataset. - - Parameters - ---------- - adatas - List of adata seq and adata spatial - deterministic - If true, use the mean of the encoder instead of a Gaussian sample for the latent - vector. - normalized - Return imputed normalized values or not. - decode_mode - If a `decode_mode` is given, use the encoder specific to each dataset as usual but use - the decoder of the dataset of id `decode_mode` to impute values. - batch_size - Minibatch size for data loading into model. - """ - self.module.eval() - - if adatas is None: - adatas = self.adatas - scdls = self._make_scvi_dls(adatas, batch_size=batch_size) - - imputed_values = [] - for mode, scdl in enumerate(scdls): - imputed_value = [] - for tensors in scdl: - ( - sample_batch, - batch_index, - label, - *_, - ) = _unpack_tensors(tensors) - if normalized: - imputed_value.append( - self.module.sample_scale( - sample_batch, - mode, - batch_index, - label, - deterministic=deterministic, - decode_mode=decode_mode, - ) - .cpu() - .detach() - ) - else: - imputed_value.append( - self.module.sample_rate( - sample_batch, - mode, - batch_index, - label, - deterministic=deterministic, - decode_mode=decode_mode, - ) - .cpu() - .detach() - ) - - imputed_value = torch.cat(imputed_value).numpy() - imputed_values.append(imputed_value) - - return imputed_values - - def save( - self, - dir_path: str, - prefix: str | None = None, - overwrite: bool = False, - save_anndata: bool = False, - save_kwargs: dict | None = None, - **anndata_write_kwargs, - ): - """Save the state of the model. - - Neither the trainer optimizer state nor the trainer history are saved. - Model files are not expected to be reproducibly saved and loaded across versions - until we reach version 1.0. - - Parameters - ---------- - dir_path - Path to a directory. - prefix - Prefix to prepend to saved file names. - overwrite - Overwrite existing data or not. If `False` and directory - already exists at `dir_path`, error will be raised. - save_anndata - If True, also saves the anndata - save_kwargs - Keyword arguments passed into :func:`~torch.save`. - anndata_write_kwargs - Kwargs for anndata write function - """ - if not os.path.exists(dir_path) or overwrite: - os.makedirs(dir_path, exist_ok=overwrite) - else: - raise ValueError( - f"{dir_path} already exists. Please provide an unexisting directory for saving." - ) - - file_name_prefix = prefix or "" - save_kwargs = save_kwargs or {} - - seq_adata = self.adatas[0] - spatial_adata = self.adatas[1] - if save_anndata: - seq_save_path = os.path.join(dir_path, f"{file_name_prefix}adata_seq.h5ad") - seq_adata.write(seq_save_path) - - spatial_save_path = os.path.join(dir_path, f"{file_name_prefix}adata_spatial.h5ad") - spatial_adata.write(spatial_save_path) - - # save the model state dict and the trainer state dict only - model_state_dict = self.module.state_dict() - - seq_var_names = seq_adata.var_names.astype(str).to_numpy() - spatial_var_names = spatial_adata.var_names.astype(str).to_numpy() - - # get all the user attributes - user_attributes = self._get_user_attributes() - # only save the public attributes with _ at the very end - user_attributes = {a[0]: a[1] for a in user_attributes if a[0][-1] == "_"} - - model_save_path = os.path.join(dir_path, f"{file_name_prefix}model.pt") - - torch.save( - { - "model_state_dict": model_state_dict, - "seq_var_names": seq_var_names, - "spatial_var_names": spatial_var_names, - "attr_dict": user_attributes, - }, - model_save_path, - **save_kwargs, - ) - - @classmethod - @devices_dsp.dedent - def load( - cls, - dir_path: str, - adata_seq: AnnData | None = None, - adata_spatial: AnnData | None = None, - accelerator: str = "auto", - device: int | str = "auto", - prefix: str | None = None, - backup_url: str | None = None, - ): - """Instantiate a model from the saved output. - - Parameters - ---------- - dir_path - Path to saved outputs. - adata_seq - AnnData organized in the same way as data used to train model. - It is not necessary to run :meth:`~scvi.external.GIMVI.setup_anndata`, - as AnnData is validated against the saved `scvi` setup dictionary. - AnnData must be registered via :meth:`~scvi.external.GIMVI.setup_anndata`. - adata_spatial - AnnData organized in the same way as data used to train model. - If None, will check for and load anndata saved with the model. - %(param_accelerator)s - %(param_device)s - prefix - Prefix of saved file names. - backup_url - URL to retrieve saved outputs from if not present on disk. - - Returns - ------- - Model with loaded state dictionaries. - - Examples - -------- - >>> vae = GIMVI.load(adata_seq, adata_spatial, save_path) - >>> vae.get_latent_representation() - """ - _, _, device = parse_device_args( - accelerator=accelerator, - devices=device, - return_device="torch", - ) - - ( - attr_dict, - seq_var_names, - spatial_var_names, - model_state_dict, - loaded_adata_seq, - loaded_adata_spatial, - ) = _load_saved_gimvi_files( - dir_path, - adata_seq is None, - adata_spatial is None, - prefix=prefix, - map_location=device, - backup_url=backup_url, - ) - adata_seq = loaded_adata_seq or adata_seq - adata_spatial = loaded_adata_spatial or adata_spatial - adatas = [adata_seq, adata_spatial] - var_names = [seq_var_names, spatial_var_names] - - for i, adata in enumerate(adatas): - saved_var_names = var_names[i] - user_var_names = adata.var_names.astype(str) - if not np.array_equal(saved_var_names, user_var_names): - warnings.warn( - "var_names for adata passed in does not match var_names of adata " - "used to train the model. For valid results, the vars need to be the" - "same and in the same order as the adata used to train the model.", - UserWarning, - stacklevel=settings.warnings_stacklevel, - ) - - registries = attr_dict.pop("registries_") - for adata, registry in zip(adatas, registries, strict=True): - if _MODEL_NAME_KEY in registry and registry[_MODEL_NAME_KEY] != cls.__name__: - raise ValueError("It appears you are loading a model from a different class.") - - if _SETUP_ARGS_KEY not in registry: - raise ValueError( - "Saved model does not contain original setup inputs. " - "Cannot load the original setup." - ) - - cls.setup_anndata(adata, source_registry=registry, **registry[_SETUP_ARGS_KEY]) - - # get the parameters for the class init signature - init_params = attr_dict.pop("init_params_") - - # new saving and loading, enable backwards compatibility - if "non_kwargs" in init_params.keys(): - # grab all the parameters except for kwargs (is a dict) - non_kwargs = init_params["non_kwargs"] - kwargs = init_params["kwargs"] - - # expand out kwargs - kwargs = {k: v for (i, j) in kwargs.items() for (k, v) in j.items()} - else: - # grab all the parameters except for kwargs (is a dict) - non_kwargs = {k: v for k, v in init_params.items() if not isinstance(v, dict)} - kwargs = {k: v for k, v in init_params.items() if isinstance(v, dict)} - kwargs = {k: v for (i, j) in kwargs.items() for (k, v) in j.items()} - model = cls(adata_seq, adata_spatial, **non_kwargs, **kwargs) - - for attr, val in attr_dict.items(): - setattr(model, attr, val) - - model.module.load_state_dict(model_state_dict) - model.module.eval() - model.to_device(device) - return model - - @classmethod - def convert_legacy_save( - cls, - dir_path: str, - output_dir_path: str, - overwrite: bool = False, - prefix: str | None = None, - **save_kwargs, - ) -> None: - """Converts a legacy saved GIMVI model (