Metadata-Version: 2.4
Name: annotuneagent
Version: 0.1.3
Summary: Cluster-level cell-type annotation from marker, name, and ML results
Author-email: "Zhao Li (李钊)" <leelieber@gmail.com>
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/leelieber2025/AnnoTuneAgent
Project-URL: Documentation, https://annotuneagent.readthedocs.io/
Project-URL: Changelog, https://github.com/leelieber2025/AnnoTuneAgent/blob/main/CHANGELOG.md
Project-URL: Source, https://github.com/leelieber2025/AnnoTuneAgent
Project-URL: Issues, https://github.com/leelieber2025/AnnoTuneAgent/issues
Project-URL: PyPI, https://pypi.org/project/annotuneagent/
Project-URL: Zenodo, https://doi.org/10.5281/zenodo.24092828
Keywords: single-cell,RNA-seq,cell type annotation,marker genes,bioinformatics
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: anndata>=0.11.3
Requires-Dist: numpy>=1.23
Requires-Dist: pandas>=1.4
Requires-Dist: scipy>=1.7
Requires-Dist: scanpy[leiden]>=1.10
Requires-Dist: scikit-misc>=0.1.4
Requires-Dist: numba>=0.57
Requires-Dist: scikit-learn>=1.1
Requires-Dist: matplotlib>=3.6
Requires-Dist: pyyaml>=6
Provides-Extra: scvi
Requires-Dist: scvi-tools>=1.1.3; extra == "scvi"
Provides-Extra: models
Requires-Dist: transformers; extra == "models"
Requires-Dist: jinja2>=3.1; extra == "models"
Provides-Extra: scfair
Requires-Dist: scfair>=0.8; extra == "scfair"
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: ruff<0.17,>=0.4.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx>=7.0; extra == "docs"
Requires-Dist: sphinx-book-theme>=1.1; extra == "docs"
Requires-Dist: myst-nb>=1.0; extra == "docs"
Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
Requires-Dist: sphinx-design>=0.5; extra == "docs"
Dynamic: license-file

# AnnoTuneAgent

[![PyPI version](https://img.shields.io/pypi/v/annotuneagent.svg)](https://pypi.org/project/annotuneagent/)
[![PyPI downloads](https://img.shields.io/pepy/dt/annotuneagent.svg?label=PyPI%20downloads)](https://pepy.tech/project/annotuneagent)
[![Python versions](https://img.shields.io/pypi/pyversions/annotuneagent.svg)](https://pypi.org/project/annotuneagent/)
[![Documentation Status](https://readthedocs.org/projects/annotuneagent/badge/?version=latest)](https://annotuneagent.readthedocs.io/en/latest/?badge=latest)
[![CI](https://github.com/leelieber2025/AnnoTuneAgent/actions/workflows/tests.yml/badge.svg)](https://github.com/leelieber2025/AnnoTuneAgent/actions/workflows/tests.yml)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.24092828.svg)](https://doi.org/10.5281/zenodo.24092828)

Documentation: [Read the Docs](https://annotuneagent.readthedocs.io/en/latest/).

**Cluster-level cell-type annotation for single-cell RNA-seq, with two
fine-tuned language models that run locally.**

Each cluster gets a marker score, a name-model result, and an ML-model
result. A rule-based judgment reads all three and writes the title, so every
call stays auditable.

## What is fine-tuned

One Qwen3.5-4B base (Q6_K GGUF) is shared by two independent LoRA adapters.
The adapters are not merged into the base, so they load together on a single
local `llama-server` and can be replaced without touching the base.

| Model | Task it was fine-tuned for | Input | Output |
| --- | --- | --- | --- |
| **name** | Choose a cell-type name from the marker dictionary | Cluster marker evidence and `dictionary_hits` | A name copied from `dictionary_hits` when that type's markers support the cluster |
| **ML** | Choose a name from measured cluster statistics | Differential expression, candidate marker panels, log fold change, fraction of cells expressing each gene inside and outside the cluster, panel summaries | A name copied from those candidates |

Both models are trained to **copy** a name from the candidates they are
shown, not to free-write one. A name outside the candidate list cannot become
a title, and a name with no marker that passes expression and specificity
validation is flagged (`no_marker_passed`) instead of being trusted silently.

The weights are on the Hugging Face Hub:
[lieberlee/annotune-qwen3.5-4b-gguf](https://huggingface.co/lieberlee/annotune-qwen3.5-4b-gguf)
(preview). Inference is local: no cluster data leaves the machine.

## Pipeline

```text
raw counts ─ prepare ─▶ QC, doublets, normalization, batch correction, Leiden
           ─ run ─────▶ per cluster:
                          1. marker score        (built-in human / mouse dictionary)
                          2. name model          (LoRA, copies from dictionary_hits)
                          3. ML model            (LoRA, copies from measured candidates)
                          ▼
                        judgment ─▶ title + status + reason + hint
                          ▼
                        validated marker panels, dotplot, report, annotated h5ad
```

- **Judgment, not voting.** Agreement among the three results gives a clear
  title. Disagreement, mixed programs, and unvalidated names are kept and
  flagged with a reason (`mixed_programs`, `near_clear_cluster`,
  `subcluster_labels`, `no_marker_passed`) so you can review them.
- **Marker evidence is checked, not quoted.** Panels are selected from cited
  dictionary markers and validated against measured expression and
  specificity in your data.
- **Every title is traceable.** The report records each model's answer, the
  panel, and the reason a cluster was or was not settled.

The models run when `models` is passed (or the CLI adapter flags are given).
Without them, `run` uses the marker score alone.

## Install

Python 3.10–3.13.

```bash
pip install annotuneagent
```

Local GGUF models need `pip install 'annotuneagent[models]'`, which installs
the tokenizer stack (`transformers` and `jinja2`). Optional methods:
AmbiDose (`denoise=True`, [install](https://github.com/leelieber2025/AmbiDose)),
scVI and SOLO (`annotuneagent[scvi]`), scFair (`annotuneagent[scfair]`),
Harmony, and Scanorama. If you select one that is missing, `prepare` prints
the install command and leaves the matrix unchanged. See
[Installation](docs/installation.md).

From a checkout:

```bash
git clone https://github.com/leelieber2025/AnnoTuneAgent.git
cd AnnoTuneAgent
pip install -e ".[dev]"
```

## First run

`prepare` expects raw integer counts. `run` expects finite, nonnegative,
library-size-normalized natural log1p expression with Scanpy's
`adata.uns["log1p"]`, which `prepare` writes.

```python
import annotuneagent as ata

adata = ata.read_10x("outs", ["sample_a", "sample_b"])
adata = ata.prepare(adata, species="human", batch_key="sample")
report = ata.run(adata, "leiden", species="human", subcluster=False)
```

`read_10x` takes one filtered matrix per sample folder (`*.h5`, or the mtx
directory when there is no h5). Scrublet and SOLO run per sample on those
filtered counts. `doublet_chip="10k"` (default) uses 0.8% multiplets per
1,000 recovered cells, and `"20k"` uses 0.4%.

`geo_info("GSE341410")` lists the samples in a GEO series. `read_geo` then
downloads the ones you name, and `groups` sets the condition in `obs["group"]`.
[docs/tutorials/gse266598_annotation.ipynb](docs/tutorials/gse266598_annotation.ipynb)
follows one series from the GEO page through annotation.

To score a caller-supplied list instead of the built-in dictionary:

```python
records = ata.annotate(
    adata,
    cluster_key="leiden",
    hypotheses=[
        ata.Hypothesis(
            "CD4-positive, alpha-beta T cell",
            positive=("CD3E", "CD4"),
            negative=("CD8A",),
        ),
        ata.Hypothesis(
            "CD8-positive, alpha-beta T cell",
            positive=("CD3E", "CD8A"),
            negative=("CD4",),
        ),
    ],
)
```

A label needs positive marker enrichment. A negative marker counts against a
hypothesis only when the cluster is enriched for it relative to the other
cells. Scores are operational, not probabilities.

The built-in table is `markers.tsv`: human and mouse, each with a
tissue-agnostic core and organ pages. Tissue rows stay hidden until `tissue`
matches. Symbols are not translated across species. `evaluate` compares
records with a per-cell author label: the reference for a cluster is the
majority label, a tied majority is left unmatched, and a parent name counts
as a coarse match.

```bash
annotuneagent \
  --input clustered.h5ad \
  --cluster-key leiden \
  --species human \
  --output result.json
```

`--name-adapter`, `--ml-adapter`, and `--base` run the name and ML models on
one local server and require `--output-h5ad`. The weights are on the Hugging
Face Hub: [lieberlee/annotune-qwen3.5-4b-gguf](https://huggingface.co/lieberlee/annotune-qwen3.5-4b-gguf).
The tokenizer is installed with the package. See
[docs/tutorials/load_model.ipynb](docs/tutorials/load_model.ipynb).

## Status

**0.1.3 (Alpha).** Import as `import annotuneagent as ata`.

## Citation

For the software record, cite the [Zenodo DOI](https://doi.org/10.5281/zenodo.24092828).
Pin the package version used in the analysis, for example `annotuneagent==0.1.3`.

## License

Software: [Apache License 2.0](LICENSE).

## Author

**Zhao Li (李钊)**
Email: [leelieber@gmail.com](mailto:leelieber@gmail.com)
