annotuneagent 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- annotuneagent/__init__.py +68 -0
- annotuneagent/__main__.py +6 -0
- annotuneagent/_version.py +19 -0
- annotuneagent/annotate.py +93 -0
- annotuneagent/arbitrate.py +141 -0
- annotuneagent/cli.py +366 -0
- annotuneagent/decide.py +725 -0
- annotuneagent/evaluate.py +121 -0
- annotuneagent/evidence.py +606 -0
- annotuneagent/granularity/__init__.py +28 -0
- annotuneagent/granularity/cl_dag.py +179 -0
- annotuneagent/granularity/ontology_map.py +108 -0
- annotuneagent/granularity/relations.py +96 -0
- annotuneagent/hypotheses.py +38 -0
- annotuneagent/local_lora.py +706 -0
- annotuneagent/marker_plot.py +172 -0
- annotuneagent/markers.py +206 -0
- annotuneagent/markers.tsv +378 -0
- annotuneagent/mltools.py +128 -0
- annotuneagent/ontology/cl_isa.json +1 -0
- annotuneagent/ontology/granularity_policy.yaml +8 -0
- annotuneagent/ontology/ontology_map.jsonl +198 -0
- annotuneagent/prepare.py +1385 -0
- annotuneagent/py.typed +0 -0
- annotuneagent/read.py +76 -0
- annotuneagent/tokenizer/LICENSE +202 -0
- annotuneagent/tokenizer/merges.txt +247587 -0
- annotuneagent/tokenizer/tokenizer.json +495920 -0
- annotuneagent/tokenizer/tokenizer_config.json +305 -0
- annotuneagent/tokenizer/vocab.json +248046 -0
- annotuneagent/votes.py +83 -0
- annotuneagent/workflow.py +2037 -0
- annotuneagent-0.1.0.dist-info/METADATA +208 -0
- annotuneagent-0.1.0.dist-info/RECORD +38 -0
- annotuneagent-0.1.0.dist-info/WHEEL +5 -0
- annotuneagent-0.1.0.dist-info/entry_points.txt +2 -0
- annotuneagent-0.1.0.dist-info/licenses/LICENSE +201 -0
- annotuneagent-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Copyright 2026 Zhao Li (李钊)
|
|
2
|
+
# Licensed under the Apache License, Version 2.0
|
|
3
|
+
|
|
4
|
+
"""Cluster-level comparison against author labels.
|
|
5
|
+
|
|
6
|
+
Counts are the result. A rate that drops ``uncertain`` clusters is not reported,
|
|
7
|
+
because refusing a label is part of the outcome.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from collections import Counter
|
|
13
|
+
|
|
14
|
+
from anndata import AnnData
|
|
15
|
+
|
|
16
|
+
from .evidence import cluster_ids
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def majority_label(labels: list[str]) -> str | None:
|
|
20
|
+
"""Most common label. Equal top counts mean the reference is ambiguous."""
|
|
21
|
+
if not labels:
|
|
22
|
+
return None
|
|
23
|
+
counts = Counter(labels)
|
|
24
|
+
ranked = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
|
|
25
|
+
if len(ranked) > 1 and ranked[0][1] == ranked[1][1]:
|
|
26
|
+
return None
|
|
27
|
+
return ranked[0][0]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _coarse(predicted: str, reference: str, parents: dict[str, str]) -> bool:
|
|
31
|
+
if predicted == reference:
|
|
32
|
+
return True
|
|
33
|
+
if parents.get(predicted) == reference:
|
|
34
|
+
return True
|
|
35
|
+
if parents.get(reference) == predicted:
|
|
36
|
+
return True
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def evaluate(
|
|
41
|
+
records: list[dict[str, object]],
|
|
42
|
+
adata: AnnData,
|
|
43
|
+
cluster_key: str,
|
|
44
|
+
label_key: str,
|
|
45
|
+
*,
|
|
46
|
+
parents: dict[str, str] | None = None,
|
|
47
|
+
prediction_key: str | None = None,
|
|
48
|
+
) -> dict[str, object]:
|
|
49
|
+
"""Compare one record per cluster with the majority author label in that cluster.
|
|
50
|
+
|
|
51
|
+
``parents`` maps a label to the coarser label that still counts as a match.
|
|
52
|
+
Sibling types that share a parent do not match each other. When a workflow
|
|
53
|
+
wrote cell labels, ``prediction_key`` evaluates those final labels.
|
|
54
|
+
"""
|
|
55
|
+
if label_key not in adata.obs.columns:
|
|
56
|
+
raise KeyError(f"obs has no column {label_key!r}")
|
|
57
|
+
if prediction_key is not None and prediction_key not in adata.obs.columns:
|
|
58
|
+
raise KeyError(f"obs has no column {prediction_key!r}")
|
|
59
|
+
parent_map = {} if parents is None else parents
|
|
60
|
+
by_cluster = {str(record["cluster"]): record for record in records}
|
|
61
|
+
n_exact = 0
|
|
62
|
+
n_coarse = 0
|
|
63
|
+
n_ambiguous = 0
|
|
64
|
+
n_label = 0
|
|
65
|
+
n_uncertain = 0
|
|
66
|
+
n_mixed = 0
|
|
67
|
+
per_cluster: list[dict[str, object]] = []
|
|
68
|
+
# A cell with no author label takes no part in the majority.
|
|
69
|
+
author = adata.obs[label_key]
|
|
70
|
+
known = author.notna()
|
|
71
|
+
groups = adata.obs[cluster_key].astype(str)
|
|
72
|
+
for cluster in cluster_ids(adata, cluster_key):
|
|
73
|
+
record = by_cluster[cluster]
|
|
74
|
+
status = str(record["status"])
|
|
75
|
+
predicted = record["cell_type"]
|
|
76
|
+
if prediction_key is not None:
|
|
77
|
+
labels = adata.obs.loc[groups == cluster, prediction_key]
|
|
78
|
+
names = labels.dropna().astype(str).unique()
|
|
79
|
+
if len(names) == 1 and not labels.isna().any():
|
|
80
|
+
status, predicted = "label", str(names[0])
|
|
81
|
+
elif len(names) == 0:
|
|
82
|
+
status, predicted = "uncertain", None
|
|
83
|
+
else:
|
|
84
|
+
status, predicted = "mixed", None
|
|
85
|
+
if status == "label":
|
|
86
|
+
n_label += 1
|
|
87
|
+
elif status == "uncertain":
|
|
88
|
+
n_uncertain += 1
|
|
89
|
+
elif status == "mixed":
|
|
90
|
+
n_mixed += 1
|
|
91
|
+
reference = majority_label(author[(groups == cluster) & known].astype(str).tolist())
|
|
92
|
+
exact = False
|
|
93
|
+
coarse = False
|
|
94
|
+
if reference is None:
|
|
95
|
+
n_ambiguous += 1
|
|
96
|
+
elif status == "label" and isinstance(predicted, str):
|
|
97
|
+
exact = predicted == reference
|
|
98
|
+
coarse = _coarse(predicted, reference, parent_map)
|
|
99
|
+
n_exact += int(exact)
|
|
100
|
+
n_coarse += int(coarse)
|
|
101
|
+
per_cluster.append(
|
|
102
|
+
{
|
|
103
|
+
"cluster": cluster,
|
|
104
|
+
"status": status,
|
|
105
|
+
"predicted": predicted,
|
|
106
|
+
"reference": reference,
|
|
107
|
+
"exact": exact,
|
|
108
|
+
"coarse": coarse,
|
|
109
|
+
}
|
|
110
|
+
)
|
|
111
|
+
n_clusters = len(per_cluster)
|
|
112
|
+
return {
|
|
113
|
+
"n_clusters": n_clusters,
|
|
114
|
+
"n_label": n_label,
|
|
115
|
+
"n_uncertain": n_uncertain,
|
|
116
|
+
"n_mixed": n_mixed,
|
|
117
|
+
"n_ambiguous_reference": n_ambiguous,
|
|
118
|
+
"n_exact": n_exact,
|
|
119
|
+
"n_coarse": n_coarse,
|
|
120
|
+
"clusters": per_cluster,
|
|
121
|
+
}
|