Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docs/api/scanpy_gpu.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,20 @@

These functions offer accelerated near drop-in replacements for common tools provided by [`scanpy`](https://scanpy.readthedocs.io/en/stable/api/index.html) {cite}`Wolf2018`.

## Scanpy backend

With Scanpy versions that support computational backends, RAPIDS-singlecell is
available as the `rapids-singlecell` backend with the aliases `cuda`, `rapids`,
and `rapids_singlecell`.

```python
import scanpy as sc

sc.settings.backend = "cuda"
```

The backend exposes RAPIDS-singlecell's `pp` and `tl` functions, plus {func}`rapids_singlecell.get.aggregate`, for Scanpy's backend dispatcher.

## Preprocessing `pp`
Filtering of highly-variable genes, batch-effect correction, per-cell normalization.

Expand Down
14 changes: 14 additions & 0 deletions docs/api/squidpy_gpu.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,20 @@
{mod}`squidpy.gr` is a tool for the analysis of spatial molecular data {cite}`Palla2022`.
{mod}`rapids_singlecell.gr` accelerates some of these functions.

## Squidpy backend

With Squidpy versions that support computational backends, RAPIDS-singlecell is
available as the `rapids-singlecell` backend with the aliases `cuda`, `rapids`,
and `rapids_singlecell`.

```python
import squidpy as sq

sq.settings.backend = "cuda"
```

The backend exposes RAPIDS-singlecell's {mod}`rapids_singlecell.gr` functions for Squidpy's backend dispatcher.

```{eval-rst}
.. module:: rapids_singlecell.gr
.. currentmodule:: rapids_singlecell
Expand Down
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"pylibraft",
"dask",
"cuvs",
"spatialdata",
]
default_role = "literal"
napoleon_google_docstring = False
Expand Down Expand Up @@ -128,6 +129,7 @@
"statsmodels": ("https://www.statsmodels.org/stable/", None),
"omnipath": ("https://omnipath.readthedocs.io/en/latest/", None),
"dask": ("https://docs.dask.org/en/stable/", None),
"spatialdata": ("https://spatialdata.scverse.org/en/stable/", None),
}

# List of patterns, relative to source directory, that match files and
Expand Down
4 changes: 4 additions & 0 deletions docs/release-notes/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
```{include} /release-notes/0.16.0.md
```

## Version 0.15.0
```{include} /release-notes/0.15.3.md
```

## Version 0.15.0
```{include} /release-notes/0.15.2.md
```
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ dev = [
"pre-commit",
]

[project.entry-points."scanpy.backends"]
rapids-singlecell = "rapids_singlecell._backends.scanpy"

[project.entry-points."squidpy.backends"]
rapids-singlecell = "rapids_singlecell._backends.squidpy"

[project.urls]
Documentation = "https://rapids-singlecell.readthedocs.io"
Source = "https://github.com/scverse/rapids_singlecell"
Expand Down
1 change: 1 addition & 0 deletions src/rapids_singlecell/_backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from __future__ import annotations
160 changes: 160 additions & 0 deletions src/rapids_singlecell/_backends/scanpy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from rapids_singlecell.get import aggregate
from rapids_singlecell.preprocessing import (
bbknn,
calculate_qc_metrics,
filter_cells,
filter_genes,
filter_highly_variable,
flag_gene_family,
harmony_integrate,
highly_variable_genes,
neighbors,
normalize_pearson_residuals,
normalize_total,
regress_out,
scrublet,
scrublet_simulate_doublets,
)
from rapids_singlecell.preprocessing import log1p as _log1p
from rapids_singlecell.preprocessing import pca as _pca
from rapids_singlecell.preprocessing import scale as _scale
from rapids_singlecell.preprocessing._pca import _empty
from rapids_singlecell.tools import (
diffmap,
draw_graph,
embedding_density,
kmeans,
leiden,
louvain,
rank_genes_groups,
rank_genes_groups_logreg,
score_genes,
score_genes_cell_cycle,
tsne,
umap,
)

if TYPE_CHECKING:
from anndata import AnnData
from numpy.typing import DTypeLike, NDArray

name = "rapids-singlecell"
aliases = ["cuda", "rapids", "rapids_singlecell"]


def log1p(
data: AnnData,
*,
base: float | None = None,
layer: str | None = None,
obsm: str | None = None,
inplace: bool = True,
copy: bool = False,
):
return _log1p(
data,
base=base,
layer=layer,
obsm=obsm,
inplace=inplace,
copy=copy,
)


def pca(
data: AnnData,
n_comps: int | None = None,
*,
layer: str | None = None,
zero_center: bool = True,
svd_solver: str | None = None,
chunked: bool = False,
chunk_size: int | None = None,
rng=None,
mask_var: NDArray | str | None = _empty,
dtype: DTypeLike = "float32",
key_added: str | None = None,
copy: bool = False,
random_state: int | None = 0,
use_highly_variable: bool | None = None,
**kwargs,
) -> None | AnnData:
if rng is not None:
random_state = rng
return _pca(
data,
n_comps=n_comps,
layer=layer,
zero_center=zero_center,
svd_solver=svd_solver,
random_state=random_state,
mask_var=mask_var,
use_highly_variable=use_highly_variable,
dtype=dtype,
chunked=chunked,
chunk_size=chunk_size,
key_added=key_added,
copy=copy,
**kwargs,
)


def scale(
data: AnnData,
*,
zero_center: bool = True,
max_value: float | None = None,
copy: bool = False,
layer: str | None = None,
obsm: str | None = None,
mask_obs: NDArray | str | None = None,
inplace: bool = True,
):
return _scale(
data,
zero_center=zero_center,
max_value=max_value,
copy=copy,
layer=layer,
obsm=obsm,
mask_obs=mask_obs,
inplace=inplace,
)


__all__ = [
"aggregate",
"bbknn",
"calculate_qc_metrics",
"diffmap",
"draw_graph",
"embedding_density",
"filter_cells",
"filter_genes",
"filter_highly_variable",
"flag_gene_family",
"harmony_integrate",
"highly_variable_genes",
"kmeans",
"leiden",
"log1p",
"louvain",
"neighbors",
"normalize_pearson_residuals",
"normalize_total",
"pca",
"rank_genes_groups",
"rank_genes_groups_logreg",
"regress_out",
"scale",
"score_genes",
"score_genes_cell_cycle",
"scrublet",
"scrublet_simulate_doublets",
"tsne",
"umap",
]
86 changes: 86 additions & 0 deletions src/rapids_singlecell/_backends/squidpy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from __future__ import annotations

from typing import Any, Literal

from anndata import AnnData

from rapids_singlecell.squidpy_gpu import calculate_niche as _calculate_niche
from rapids_singlecell.squidpy_gpu import co_occurrence, ligrec, spatial_autocorr

name = "rapids-singlecell"
aliases = ["cuda", "rapids", "rapids_singlecell"]


def calculate_niche( # noqa: PLR0917
data: AnnData,
flavor: Literal["neighborhood", "utag", "cellcharter", "spatialleiden"],
library_key: str | None = None,
mask: Any | None = None,
groups: str | None = None,
n_neighbors: int | None = None,
resolutions: float
| tuple[float, float]
| list[float | tuple[float, float]]
| None = None,
min_niche_size: int | None = None,
scale: bool = True, # noqa: FBT001, FBT002
abs_nhood: bool = False, # noqa: FBT001, FBT002
distance: int | None = None,
n_hop_weights: list[float] | None = None,
aggregation: str | None = None,
n_components: int | None = None,
random_state: int = 42,
spatial_connectivities_key: str = "spatial_connectivities",
latent_connectivities_key: str = "connectivities",
layer_ratio: float = 1.0,
n_iterations: int = -1,
use_weights: bool | tuple[bool, bool] = True, # noqa: FBT001, FBT002
use_rep: str | None = None,
inplace: bool = True, # noqa: FBT001, FBT002
*,
table_key: str | None = None,
) -> AnnData | None:
"""Adapt Squidpy's niche API to RAPIDS SingleCell's GPU implementation."""
if not isinstance(data, AnnData):
raise TypeError(
"The RAPIDS SingleCell backend currently supports AnnData inputs only."
)
if flavor == "spatialleiden":
raise NotImplementedError(
"The RAPIDS SingleCell backend does not support flavor='spatialleiden'."
)
if library_key is not None or mask is not None or table_key is not None:
raise NotImplementedError(
"library_key, mask, and table_key are not supported by the RAPIDS backend."
)
if (
latent_connectivities_key != "connectivities"
or layer_ratio != 1.0
or n_iterations != -1
or use_weights is not True
):
raise NotImplementedError(
"The selected niche parameters are not supported by the RAPIDS backend."
)

return _calculate_niche(
data,
flavor=flavor,
groups=groups,
n_neighbors=15 if n_neighbors is None else n_neighbors,
resolutions=(0.5,) if resolutions is None else resolutions,
min_niche_size=min_niche_size,
scale=scale,
abs_nhood=abs_nhood,
distance=distance,
n_hop_weights=n_hop_weights,
aggregation="mean" if aggregation is None else aggregation,
n_components=10 if n_components is None else n_components,
random_state=random_state,
spatial_connectivities_key=spatial_connectivities_key,
use_rep=use_rep,
copy=not inplace,
)


__all__ = ["calculate_niche", "co_occurrence", "ligrec", "spatial_autocorr"]
5 changes: 5 additions & 0 deletions src/rapids_singlecell/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from scipy.sparse import csc_matrix as csc_matrix_cpu
from scipy.sparse import csr_matrix as csr_matrix_cpu

try:
from spatialdata import SpatialData
except ImportError:
SpatialData = None


def _meta_dense(dtype):
return cp.zeros([0], dtype=dtype)
Expand Down
5 changes: 4 additions & 1 deletion src/rapids_singlecell/squidpy_gpu/_autocorr.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from scipy import sparse
from statsmodels.stats.multitest import multipletests

from rapids_singlecell._compat import SpatialData
from rapids_singlecell.preprocessing._utils import _sparse_to_dense

from ._gearysc import _gearys_C_cupy
Expand Down Expand Up @@ -49,7 +50,7 @@ def _to_cupy(vals, *, use_sparse: bool, dtype):


def spatial_autocorr(
adata: AnnData,
adata: AnnData | SpatialData,
*,
connectivity_key: str = "spatial_connectivities",
genes: str | Sequence[str] | None = None,
Expand Down Expand Up @@ -118,6 +119,8 @@ def spatial_autocorr(
DataFrame containing the autocorrelation scores, p-values, and corrected p-values for each gene. \
If `copy` is False, the results are stored in `adata.uns` and None is returned.
"""
if SpatialData is not None and isinstance(adata, SpatialData):
adata = adata.table
if genes is None:
if "highly_variable" in adata.var:
genes = adata[:, adata.var["highly_variable"]].var_names.values
Expand Down
5 changes: 4 additions & 1 deletion src/rapids_singlecell/squidpy_gpu/_co_oc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy as np
from cuml.metrics import pairwise_distances

from rapids_singlecell._compat import SpatialData
from rapids_singlecell._cuda import _cooc_cuda as _co
from rapids_singlecell._utils import (
_calculate_blocks_per_pair,
Expand All @@ -21,7 +22,7 @@


def co_occurrence(
adata: AnnData,
adata: AnnData | SpatialData,
cluster_key: str,
*,
spatial_key: str = "spatial",
Expand Down Expand Up @@ -65,6 +66,8 @@ def co_occurrence(
computed at ``interval``.
"""

if SpatialData is not None and isinstance(adata, SpatialData):
adata = adata.table
_assert_categorical_obs(adata, key=cluster_key)
_assert_spatial_basis(adata, key=spatial_key)
spatial = cp.array(adata.obsm[spatial_key]).astype(np.float32)
Expand Down
Loading
Loading