| """kNN Gaussian kernel smoothing of expression layers.""" |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
| from anndata import AnnData |
| from scipy.sparse import issparse |
|
|
| from .._constants import ( |
| UNSPLICED, |
| SPLICED, |
| SMOOTHED_UNSPLICED, |
| SMOOTHED_SPLICED, |
| DEFAULT_BANDWIDTH, |
| DEFAULT_GENE_BATCH_SIZE, |
| ) |
| from .._utils import log_params, to_dense_float32 |
| from .._numba_kernels import _smooth_kernel, _compute_adaptive_bandwidths |
|
|
|
|
| def smooth_layers( |
| adata: AnnData, |
| bandwidth: str | float = DEFAULT_BANDWIDTH, |
| gene_batch_size: int = DEFAULT_GENE_BATCH_SIZE, |
| layers: tuple[str, str] = (UNSPLICED, SPLICED), |
| out_layers: tuple[str, str] = (SMOOTHED_UNSPLICED, SMOOTHED_SPLICED), |
| ) -> None: |
| """Smooth unspliced and spliced layers using kNN Gaussian kernel. |
| |
| Uses the precomputed kNN graph in ``adata.obsp['distances']``. |
| Gene-batched for memory control on large datasets. |
| |
| Parameters |
| ---------- |
| adata |
| Annotated data matrix with precomputed neighbor graph. |
| bandwidth |
| ``'adaptive'`` (default) uses the median neighbor distance per |
| cell. A float value sets a fixed bandwidth for all cells. |
| gene_batch_size |
| Number of genes to process per batch. |
| layers |
| Input layer names ``(unspliced, spliced)``. |
| out_layers |
| Output layer names ``(Mu, Ms)``. |
| """ |
| if "distances" not in adata.obsp: |
| raise ValueError( |
| "Neighbor graph not found. Run scptr.pp.neighbors() first." |
| ) |
|
|
| dist_mat = adata.obsp["distances"] |
| if not issparse(dist_mat): |
| from scipy.sparse import csr_matrix |
| dist_mat = csr_matrix(dist_mat) |
| dist_mat = dist_mat.tocsr() |
|
|
| indices = dist_mat.indices.astype(np.int32) |
| indptr = dist_mat.indptr.astype(np.int32) |
| distances = dist_mat.data.astype(np.float32) |
|
|
| |
| if bandwidth == "adaptive": |
| bandwidths = _compute_adaptive_bandwidths(distances, indptr) |
| else: |
| bandwidths = np.full(adata.n_obs, float(bandwidth), dtype=np.float32) |
|
|
| n_obs, n_vars = adata.n_obs, adata.n_vars |
|
|
| for in_layer, out_layer in zip(layers, out_layers): |
| data = to_dense_float32(adata.layers[in_layer]) |
| result = np.empty_like(data) |
|
|
| |
| for start in range(0, n_vars, gene_batch_size): |
| end = min(start + gene_batch_size, n_vars) |
| batch_in = np.ascontiguousarray(data[:, start:end]) |
| batch_out = np.empty_like(batch_in) |
|
|
| _smooth_kernel( |
| batch_in, indices, indptr, distances, bandwidths, batch_out |
| ) |
| result[:, start:end] = batch_out |
|
|
| adata.layers[out_layer] = result |
|
|
| log_params(adata, "smooth_layers", { |
| "bandwidth": bandwidth, |
| "gene_batch_size": gene_batch_size, |
| }) |
|
|