Metadata-Version: 2.4
Name: cellconsensus
Version: 1.0.0
Summary: Hierarchical unsupervised cell type annotation from consensus markers
Author: tansey-lab
Author-email: Antoine de Mathelin <antoine.demat@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/tansey-lab/cellconsensus
Project-URL: Repository, https://github.com/tansey-lab/cellconsensus
Project-URL: Issues, https://github.com/tansey-lab/cellconsensus/issues
Keywords: single-cell,scRNA-seq,cell-type-annotation,consensus-markers,bioinformatics
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Requires-Dist: pandas
Requires-Dist: scanpy
Requires-Dist: scipy
Requires-Dist: pynndescent
Dynamic: license-file

# CellConsensus

Hierarchical unsupervised cell type annotation from consensus marker genes — three levels of granularity, no reference atlas required, plus optional cancer scoring.

## Installation

```bash
pip install git+https://github.com/tansey-lab/cellconsensus.git
```

## Usage

```python
from cellconsensus import CellConsensus
import scanpy as sc

adata = sc.read_h5ad("my_data.h5ad")

cc = CellConsensus()
cc.fit(adata, include_cancer=True, cancer_types=["lung_adenocarcinoma"])

labels_lvl1 = cc.predict(level=1)   # "T cell", "myeloid cell", ...
labels_lvl2 = cc.predict(level=2)   # "CD4+ T cell", "macrophage", ...
labels_lvl3 = cc.predict(level=3)   # "memory CD4+ T cell", ...

# Per-cell × per-type score matrix (DataFrame, n_cells x n_lvl1_types)
S = cc.score_matrix(level=1)

# Save / reload
cc.save("model.pkl")
cc2 = CellConsensus.load("model.pkl")
```

`cancer_types` accepts any key from `cellconsensus.list_cancer_types()` (120 entries including `lung_adenocarcinoma`, `breast_carcinoma`, `melanoma`, …). Omit `include_cancer` for normal-tissue-only fits.

## Example: 10k PBMC

[`examples/pbmc10k_example.py`](examples/pbmc10k_example.py) runs CellConsensus on 10x Genomics' public 10k PBMC (v3) dataset (~11.5k cells after light QC) and produces the figures below. It downloads the data on first run.

```python
import scanpy as sc
from cellconsensus import CellConsensus

adata = sc.read_10x_h5("pbmc_10k_v3_filtered_feature_bc_matrix.h5")
adata.var_names_make_unique()

cc = CellConsensus()
cc.fit(adata)
```

CellConsensus predictions at the three levels of granularity:

![10k PBMC cell types](assets/pbmc_cell_types.png)

Level-1 score matrix (`cc.score_matrix(level=1)`), one panel per candidate type — the blood lineages (T, myeloid, B/plasma, erythroid/megakaryocyte) light up while irrelevant tissue types stay dark:

![10k PBMC level-1 scores](assets/pbmc_score_lvl1.png)

## Custom gene signatures

Score cells against any user-supplied marker list — pooled the same way as the built-in references (NN-graph smoothing for `ccc`, per-cluster averaging for `precomputed`), so the result is directly comparable to entries of `score_matrix`.

```python
sig = {"WT1": 10, "CCND1": 8, "CD99": 7, "NCAM1": 6, "MKI67": 5}
df = cc.predict_gene_set(list(sig.keys()), weights=sig, name="my_sig")
adata.obs["my_sig_score"] = df["my_sig"].values
```

`weights` accepts a list, a dict (missing entries default to 1.0), or `None` for uniform. Negative weights are allowed (anti-markers).

## Bringing your own clusters

Already have clusters (Leiden, or anything else)? Pass `clustering="precomputed"` with the `obs` column that holds the labels. CellConsensus uses the **same three-level taxonomy** as the default `ccc` mode — it just averages the marker scores across all cells in each cluster (no NN smoothing) and labels every cluster by the argmax. Clusters are never split: each one gets a single label per level, gaining precision from level 1 to level 3.

```python
import scanpy as sc

sc.pp.neighbors(adata)
sc.tl.leiden(adata, key_added="my_clusters")   # your clustering, your way

cc = CellConsensus(clustering="precomputed", cluster_key="my_clusters")
cc.fit(adata)
cc.predict(level=1)   # "T cell", "myeloid cell", ...
cc.predict(level=3)   # "memory CD4+ T cell", ...
```
