Source code for mantispy.get._accessors

"""Accessors that turn a mantispy AnnData into plain Python objects."""

from __future__ import annotations

from collections.abc import Sequence

import numpy as np
import pandas as pd
from anndata import AnnData

from mantispy._core._reduce import get_matrix
from mantispy._core._utils import as_frame, feature_mask, reference_mask
from mantispy._core.features import canonical_channel


[docs] def features( adata: AnnData, object: str | None = None, feature_group: str | None = None, channel: str | None = None, key: str | None = None, canonical_channels: bool = False, ) -> list[str]: """Names of the features matching every filter given. Args: adata: Object to query. object: Exact matches against the parsed ``var`` columns of the same name. feature_group: Exact matches against the parsed ``var`` columns of the same name. channel: Matches any component of a multi-channel value, so ``"DNA"`` selects ``"DNA|ER"`` colocalization features too. key: Restrict to features where this boolean ``var`` column is true, e.g. ``"selected"``. canonical_channels: Compare channels through ``canonical_channel``, so that ``channel="DNA"`` also matches a dataset whose nuclear stain is named ``Hoechst`` or ``DAPI``. Returns: Feature names, in ``var`` order. """ mask = np.ones(adata.n_vars, dtype=bool) if object is not None: mask &= (adata.var["object"] == object).to_numpy() if feature_group is not None: mask &= (adata.var["feature_group"] == feature_group).to_numpy() if channel is not None: wanted = canonical_channel(channel) if canonical_channels else channel mask &= np.array( [ wanted in str(canonical_channel(value) if canonical_channels else value).split("|") for value in adata.var["channel"] ], dtype=bool, ) if key is not None: if key not in adata.var: raise KeyError(f"var has no column {key!r}") mask &= feature_mask(adata, key) return adata.var_names[mask].tolist()
[docs] def to_dataframe( adata: AnnData, layer: str | None = None, metadata: bool = True, features: Sequence[str] | None = None, ) -> pd.DataFrame: """Wide, pycytominer-shaped frame: ``Metadata_`` columns first, then features. Use it to pass profiles to pycytominer, cytominer-eval and other tools that expect a flat table. """ names = list(adata.var_names) if features is None else list(features) matrix = get_matrix(adata, layer) if features is not None: positions = np.asarray(adata.var_names.get_indexer(pd.Index(names))) if (positions < 0).any(): unknown = [name for name, position in zip(names, positions, strict=True) if position < 0] raise KeyError(f"features not in var_names: {unknown[:5]}") matrix = matrix[:, positions] values = pd.DataFrame(matrix, index=adata.obs_names, columns=names) if not metadata: return values obs = as_frame(adata.obs) meta_columns = [column for column in obs.columns if column.startswith("Metadata_")] return pd.concat([obs[meta_columns], values], axis=1)
[docs] def controls(adata: AnnData, kind: str = "negcon") -> np.ndarray: """Boolean mask of control rows. ``"negcon"`` reads ``Metadata_Control``; ``"poscon"`` reads ``Metadata_Control_Type == "poscon"`` and is all-``False`` when that column is absent. """ if kind == "negcon": return reference_mask(adata, "negcon") if kind == "poscon": if "Metadata_Control_Type" not in adata.obs: return np.zeros(adata.n_obs, dtype=bool) return (as_frame(adata.obs)["Metadata_Control_Type"] == "poscon").to_numpy(dtype=bool) raise ValueError(f"kind must be 'negcon' or 'poscon', got {kind!r}")