You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,7 +6,7 @@
6
6
2. Generates a structured description for every label (or part) with an LLM.
7
7
3. Embeds those descriptions with a configured embedding model.
8
8
4. Matches each embedding to the closest CL term.
9
-
5. Scores each author/algorithm pair using an ontology-aware similarity metric (default: a Gaussian kernel on the cosine similarity of the CL term embeddings). Compound pairs use Hungarian bipartite matching with an optional coverage penalty when part counts differ.
9
+
5. Scores each author/algorithm pair using an ontology-aware similarity metric (default: a Gaussian kernel on the cosine similarity of the CL term embeddings). Compound pairs default to the maximum entry in the part-by-part score matrix (`compound_scoring="max"`); set `compound_scoring="hungarian_mean"` for Hungarian assignment mean with a coverage penalty when part counts differ.
10
10
6. Returns a tidy DataFrame with one row per `(algorithm, pair_index)`.
11
11
12
12
Updated ReadMe: [cyteonto/README.md](cyteonto/README.md). Process flow and file layout: [docs/WORKFLOW.md](docs/WORKFLOW.md), [docs/FILE_MANAGEMENT.md](docs/FILE_MANAGEMENT.md).
Copy file name to clipboardExpand all lines: cyteonto/README.md
+44-32Lines changed: 44 additions & 32 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ Given two parallel lists of cell type labels (one from the study author, one fro
8
8
2. Generates a structured description for each label or part using an LLM.
9
9
3. Embeds those descriptions with a configured embedding model.
10
10
4. Matches each embedding to the closest CL term via cosine similarity.
11
-
5. Scores each author/algorithm pair using an ontology-aware similarity metric (default: kernelised cosine on the CL term embeddings). Compound pairs use Hungarian match mean with a coverage penalty when part counts differ.
11
+
5. Scores each author/algorithm pair using an ontology-aware similarity metric (default: kernelised cosine on the CL term embeddings). Compound pairs default to the maximum entry in the part-by-part score matrix (`compound_scoring="max"`); set `compound_scoring="hungarian_mean"` for Hungarian assignment mean with a coverage penalty when part counts differ.
12
12
6. Returns a tidy `pandas.DataFrame` with per-pair scores.
13
13
14
14
All LLM descriptions and embeddings are persisted on disk and reused across runs.
@@ -98,6 +98,7 @@ df = await cyto.compare(
98
98
},
99
99
run_id="sample_run_001", # optional; auto-generated UUID if omitted
100
100
metric="cosine_kernel",
101
+
compound_scoring="max", # default; use "hungarian_mean" for assignment mean
101
102
)
102
103
print(df)
103
104
print("run_id used:", df["run_id"].iloc[0])
@@ -122,15 +123,15 @@ One row per `(algorithm, pair_index)`:
122
123
|`run_id`| str | The `run_id` passed to `compare`. |
123
124
|`algorithm`| str | Algorithm key from the `algorithms` mapping. |
124
125
|`pair_index`| int | Position inside the label list, starting at 0. |
125
-
|`author_label`| str | The author label for this pair. |
126
-
|`algorithm_label`| str | The algorithm label for this pair. |
127
-
|`author_ontology_id`| str | Best CL match for the author label. For compound pairs, semicolon-separated ids from the Hungarian assignment only. Empty string if unmatched. |
126
+
|`author_label`| str | The author label for this pair (lowercased at compare entry).|
127
+
|`algorithm_label`| str | The algorithm label for this pair (lowercased at compare entry).|
128
+
|`author_ontology_id`| str | Best CL match per author part, semicolon-separated in part order. Empty string if unmatched. |
128
129
|`author_ontology_name`| str | Primary CSV label (or OWL fallback) for each id in `author_ontology_id`. |
129
-
|`author_embedding_similarity`| float | Mean cosine similarity between author part embeddings and their CL matches. |
130
-
|`algorithm_ontology_id`| str | Best CL match for the algorithm label. Same compound rules as author. |
130
+
|`author_embedding_similarity`| float \| str | Cosine similarity of each author part to its CL match. Single float when one part; semicolon-separated floats when multiple parts. |
131
+
|`algorithm_ontology_id`| str | Best CL match per algorithm part, same rules as author. |
131
132
|`algorithm_ontology_name`| str | Names for ids in `algorithm_ontology_id`. |
132
-
|`algorithm_embedding_similarity`| float | Mean cosine similarity between algorithm part embeddings and their CL matches. |
133
-
|`cytescore_similarity`| float | Score under the chosen `metric`; `0.0` if either side is unmatched. |
133
+
|`algorithm_embedding_similarity`| float \| str | Same shape as author embedding similarity, for algorithm parts. |
134
+
|`cytescore_similarity`| float | Score under the chosen `metric` / compound reducer; `0.0` if either side is unmatched. |
134
135
|`similarity_method`| str |`cytescore`, `cytescore_compound`, `string_similarity`, `partial_match`, `no_matches`, or `empty`. |
135
136
136
137
---
@@ -145,7 +146,7 @@ Read once on import, via `python-dotenv`:
1. Checks that `cell_ontology/cell_to_cell_ontology.csv` and `cell_ontology/cl.owl` exist.
242
+
1. Checks that `cell_ontology/cell_to_cell_ontology.csv`(and preferably the enriched CSV) and `cell_ontology/cl.owl` exist. Lookups prefer `cell_to_cell_ontology_enriched.csv` when present.
242
243
2. Computes ontology paths from `llm.to_artifact_key()` and `embedding.to_artifact_key()`.
243
244
3. If `force_regenerate=True`, unlinks both the ontology embeddings NPZ and the descriptions JSON before proceeding.
244
245
4. Loads the descriptions JSON (if present) and drops any blank entries so they can be retried.
- Every value in `algorithms` must be the same length as `author_labels`. Mismatched lengths raise `ValueError`.
270
272
- Algorithm names must be unique. `"author"` is reserved and rejected.
273
+
-`compound_scoring` must be `"max"` or `"hungarian_mean"`; any other value raises `ValueError`.
274
+
- Non-empty author and algorithm labels (and decomposed parts) are lowercased before decompose/describe/embed so casing does not split caches.
271
275
-`run_id` is used as a cache namespace on disk. Reusing the same id reuses cached author and algorithm embeddings when the labels match. If you pass `None`, a UUID of the form `run-<uuid4>` is generated and logged; the same value is written into every result row so you can recover it later.
272
276
273
277
Call flow:
274
278
275
-
1.`_resolve_label_parts` decomposes unique labels via LLM (cached per `run_id` under `decompositions/`).
276
-
2.`_embed_user_labels` on the union of sanitized parts per side (author, then each algorithm).
277
-
3.`_match` returns the best CL id and similarity for each part.
278
-
4.`_ensure_similarity()` lazy-loads the OWL file and the ontology embeddings into `OntologySimilarity`.
279
-
5. For each aligned pair index:
279
+
1. Lowercase non-empty labels on both sides.
280
+
2.`_resolve_label_parts` decomposes unique labels via LLM (cached per `run_id` under `decompositions/`). Compound parts are lowercased.
281
+
3.`_embed_user_labels` on the union of sanitized parts per side (author, then each algorithm).
282
+
4.`_match` returns the best CL id and similarity for each part.
283
+
5.`_ensure_similarity()` lazy-loads the OWL file and the ontology embeddings into `OntologySimilarity`.
284
+
6. For each aligned pair index:
280
285
-**Simple pair** (one part on each side): single `OntologySimilarity.similarity` call; `similarity_method` from `_method_for`.
281
-
-**Compound pair** (more than one part on either side): build an m×n score matrix, run Hungarian max-weight matching, average assigned scores; multiply by `min(m,n)/max(m,n)` when `m ≠ n`; `similarity_method = cytescore_compound`.
282
-
6. Rows are concatenated into a DataFrame with columns from `RESULT_COLUMNS`.
286
+
-**Compound pair** (more than one part on either side): build an m×n score matrix`S`, then reduce with `compound_scoring` (see below); `similarity_method = cytescore_compound`.
287
+
7. Rows are concatenated into a DataFrame with columns from `RESULT_COLUMNS`. Ontology ids/names and embedding similarities list **all** parts in order (not only Hungarian-matched pairs).
283
288
284
289
### Compound label scoring
285
290
286
291
Mixture labels such as doublets or mixed populations are poor matches when embedded as a single string. `compare` therefore:
287
292
288
-
1. Calls `decompose_labels` to split a label into one or more cell-type parts (semicolon-separated synonyms stay as one part).
293
+
1. Calls `decompose_labels` to split a label into one or more cell-type parts (semicolon-separated synonyms stay as one part; compound part names are lowercased).
289
294
2. Embeds and matches each unique part.
290
-
3. Scores compound pairs with **Hungarian match mean**:
291
-
- Build matrix `S` where `S[i,j]` is the cytescore between author part `i` and algorithm part `j`.
292
-
- Pick `k = min(m,n)` one-to-one assignments that maximize total score.
293
-
-`match_mean` = mean of assigned cell scores.
294
-
- If `m ≠ n`, multiply by coverage `min(m,n) / max(m,n)`.
295
-
4. Writes `similarity_method = cytescore_compound`. Ontology ids and names list only the matched assignment pairs (semicolon-separated when `k > 1`).
295
+
3. Builds matrix `S` where `S[i,j]` is the cytescore between author part `i` and algorithm part `j`.
-**`"hungarian_mean"`:** pick `k = min(m,n)` one-to-one assignments that maximize total score; take the mean of those `k` scores; if `m ≠ n`, multiply by coverage `min(m,n) / max(m,n)`.
299
+
5. Writes `similarity_method = cytescore_compound`. Result ontology ids/names and per-part embedding similarities include every part on that side.
296
300
297
-
Illustrative scores (see `notebooks/quick_tutorial.ipynb`):
301
+
Illustrative scores with `compound_scoring="hungarian_mean"`(see `notebooks/quick_tutorial.ipynb`):
298
302
299
303
| Scenario | m×n | Typical score |
300
304
|----------|-----|---------------|
@@ -304,6 +308,8 @@ Illustrative scores (see `notebooks/quick_tutorial.ipynb`):
304
308
| Partial overlap, extra author type | 3×2 |~0.35 |
305
309
| Author doublet vs single type | 2×1 | best match × 0.5 |
306
310
311
+
With the default `"max"`, the same matrices report the highest single cell of `S` instead (for example 2×1 with scores `[0.6, 0.2]` yields `0.6`).
312
+
307
313
### `compare_anndata` (async)
308
314
309
315
Pulls label lists out of `adata.obs[target_columns]` for each AnnData object and delegates to `compare`. Skips any object missing `author_column` and warns for missing target columns.
`describe.decompose_label` and `describe.decompose_labels` call a dedicated LLM agent with `output_type=LabelDecomposition`. The model decides whether a label names multiple cell types (doublets, mixed populations) and returns sanitized parts. Semicolon-separated synonyms stay as one part. Results are cached per `run_id` under `user_files/decompositions/<run_id>/decompositions_<llmKey>.json`.
427
+
`describe.decompose_label` and `describe.decompose_labels` call a dedicated LLM agent with `output_type=LabelDecomposition`. The model decides whether a label names multiple cell types (doublets, mixed populations) and returns sanitized parts in lowercase. Semicolon-separated synonyms stay as one part. Results are cached per `run_id` under `user_files/decompositions/<run_id>/decompositions_<llmKey>.json`.
422
428
423
429
Tune description batching by editing the constants at the top of `describe.py` or by passing `second_pass_wait_seconds` explicitly; the other values are module-level for now.
424
430
@@ -448,7 +454,7 @@ Tune description batching by editing the constants at the top of `describe.py` o
│ ├── cell_to_cell_ontology_enriched.csv shipped or built by setup.py
552
+
│ └── cl.owl shipped
546
553
└── embedding/
547
554
├── cell_ontology/
548
555
│ └── embeddings_<llmKey>_<embdKey>.npz
@@ -563,6 +570,10 @@ Set via `PathConfig(data_dir=..., user_dir=...)`:
563
570
└── decompositions_<llmKey>.json
564
571
```
565
572
573
+
`OntologyMapping` prefers the enriched CSV when it exists. That file keeps the original `label` column for display and adds `label_normalized` (lowercase) for lookup and ontology description inputs. Rows that share the same `(ontology_id, label_normalized)` after lowercasing are deduplicated (first kept).
574
+
575
+
`uv run python cyteonto/setup.py` downloads the original CSV, OWL, primary ontology descriptions/embeddings (required), backup pair artifacts (optional; failures warn and continue), and the enriched CSV (optional; builds locally from the original if the CDN file is missing).
- Duplicate algorithm name or an algorithm named `"author"`: `compare` raises `ValueError`.
605
617
- Ontology match below `min_match_similarity`: CL id stored as empty string, `similarity_method` becomes `partial_match` or `no_matches`.
606
618
- Both labels empty on a pair: `similarity_method = empty`, ontology fields blank.
@@ -616,7 +628,7 @@ When `use_cache=False` is passed to `compare`, all cache lookups are skipped and
616
628
-**New provider**: add a URL to `_PROVIDER_URL` in `embed.py`, extend the `EmbdProvider``Literal` in `models.py`, and adjust `_headers` / `_build_payload` / `_extract_embedding` if the request or response shape differs.
617
629
-**New similarity metric**: implement a helper in `OntologySimilarity`, add a branch in `OntologySimilarity.similarity`, and update the table above.
618
630
-**Alternative prompt**: edit `describe._build_prompt`. The existing prompt lists every `CellDescription` field with a soft length cap and a neutral tone; keep the same structure to avoid anchoring bias.
619
-
-**Different ontology file**: pass a custom `data_dir`. The package expects the two files under `<data_dir>/cell_ontology/` to be named exactly `cl.owl` and `cell_to_cell_ontology.csv`.
631
+
-**Different ontology file**: pass a custom `data_dir`. The package expects `cl.owl` and `cell_to_cell_ontology.csv` under `<data_dir>/cell_ontology/`. Prefer also shipping or generating `cell_to_cell_ontology_enriched.csv` via `setup.py`.
620
632
621
633
---
622
634
@@ -626,7 +638,7 @@ Already declared in the project `pyproject.toml`:
626
638
627
639
-`pydantic`, `pydantic-ai`
628
640
-`aiohttp`, `tenacity`
629
-
-`numpy`, `pandas`, `scikit-learn`
641
+
-`numpy`, `pandas`, `scikit-learn` (Hungarian assignment uses `scipy.optimize`, pulled in transitively)
0 commit comments