scPTR / src /scptr /preprocessing /_smooth.py
bryan7264's picture
Add files using upload-large-folder tool
925ee3b verified
Raw
History Blame Contribute Delete
2.85 kB
"""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)
# Compute bandwidths
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)
# Process in gene batches
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,
})