"""Per-layer library-size normalization.""" from __future__ import annotations import numpy as np from anndata import AnnData from scipy.sparse import issparse from .._constants import UNSPLICED, SPLICED from .._utils import log_params def normalize_layers( adata: AnnData, target_sum: float | None = None, layers: tuple[str, ...] = (SPLICED, UNSPLICED), ) -> None: """Library-size normalize spliced and unspliced layers independently. Each cell's counts in each layer are divided by the cell's total counts in that layer and multiplied by *target_sum* (defaults to the median library size across cells for that layer). Modifies *adata* in place — layers are converted to dense float32. Parameters ---------- adata Annotated data matrix. target_sum Target total counts per cell. If ``None``, use the median. layers Which layers to normalize. """ for layer in layers: mat = adata.layers[layer] if issparse(mat): mat = np.asarray(mat.todense(), dtype=np.float32) else: mat = np.asarray(mat, dtype=np.float32) lib_sizes = mat.sum(axis=1, keepdims=True) lib_sizes = np.clip(lib_sizes, 1e-10, None) if target_sum is None: ts = np.median(lib_sizes) else: ts = target_sum mat = mat / lib_sizes * ts adata.layers[layer] = mat log_params(adata, "normalize_layers", { "target_sum": target_sum, "layers": list(layers), })