Spaces:
Sleeping
Sleeping
RootScope v4: manuscript model (LightGBM per round, 3 seeds, layer + soft-neighbor context, radial prior)
bb112f6 verified Download rootscope/report.py from ct-tranchau/Rootscope: direct link, hf CLI and curl.
- Browser
- Download file 5.58 kB
-
https://huggingface.co/spaces/ct-tranchau/Rootscope/resolve/main/rootscope/report.py
- Command line
-
hf download hf://spaces/ct-tranchau/Rootscope/rootscope/report.py
-
curl -L -o report.py https://huggingface.co/spaces/ct-tranchau/Rootscope/resolve/main/rootscope/report.py
5.58 kB
| """Per-image report: how far to trust the predictions. | |
| Numbers come from the 26 held-out sections of the manuscript (11,043 | |
| labelled cells, accuracy 0.886 with the v4.1 geometry hybrid; 0.881 for the | |
| v4.0 full-feature model alone). The classifier's probability is optimistic, | |
| so it is mapped to three reliability tiers with the accuracy actually | |
| observed in each tier rather than reported as is. | |
| """ | |
| import json | |
| # confidence tier -> (lower bound, observed accuracy on the held-out sections, | |
| # share of cells in the tier) | |
| TIERS = [("high", 0.99, 0.932, 0.77), ("medium", 0.90, 0.787, 0.13), ("low", 0.0, 0.653, 0.10)] | |
| # per class on the held-out sections: precision, recall, F1 | |
| CLASS_PERF = { | |
| "root_cap": (0.962, 0.995, 0.978), "epidermis": (0.834, 0.959, 0.892), | |
| "exodermis": (0.930, 0.881, 0.905), "cortex": (0.975, 0.900, 0.936), | |
| "endodermis": (0.903, 0.893, 0.898), "pericycle": (0.905, 0.837, 0.870), | |
| "stele": (0.789, 0.904, 0.842), "xylem": (0.823, 0.630, 0.714), | |
| "phloem": (0.700, 0.681, 0.690), | |
| } | |
| # accuracy of a low-confidence (< 0.9) call, per predicted class | |
| LOW_CONF_ACC = {"cortex": 0.89, "endodermis": 0.73, "epidermis": 0.44, "exodermis": 0.70, | |
| "pericycle": 0.65, "phloem": 0.42, "root_cap": 0.69, "stele": 0.55, "xylem": 0.43} | |
| def reliability(conf): | |
| for name, lo, _, _ in TIERS: | |
| if conf >= lo: | |
| return name | |
| return "low" | |
| def build(df, exo, species_row=None, species_query=None, n_smoothed=0, um_per_px=None): | |
| """Return the report as a dict; ``exo`` is dict(status, reason, evidence).""" | |
| n = len(df) | |
| tiers = df["reliability"].value_counts().to_dict() | |
| tier_rows = [dict(tier=t, cells=int(tiers.get(t, 0)), | |
| share=round(tiers.get(t, 0) / n, 3) if n else 0.0, | |
| accuracy_on_held_out=acc) | |
| for t, _, acc, _ in TIERS] | |
| expected = (sum(tiers.get(t, 0) * acc for t, _, acc, _ in TIERS) / n) if n else None | |
| warnings = [] | |
| if exo["status"] == "uncertain": | |
| warnings.append("Exodermis status is uncertain; see exodermis.reason.") | |
| if species_query and species_row is None: | |
| warnings.append(f"Species '{species_query}' is not in the species table; " | |
| f"the exodermis decision used the image evidence only.") | |
| if species_row is not None and species_row.get("in_training") != "yes": | |
| warnings.append("Species is not in the training set; accuracy on it is unknown. " | |
| "Held-out accuracy on unseen species was about 0.70 in the manuscript.") | |
| low_share = tiers.get("low", 0) / n if n else 0 | |
| if low_share > 0.12: | |
| warnings.append(f"{100 * low_share:.0f}% of cells are low confidence (held-out share 10%); " | |
| f"the image may be unlike the training data (staining, scale, section quality).") | |
| counts = df["predicted_cell_type"].value_counts() | |
| classes = [dict(cell_type=c, cells=int(counts[c]), | |
| low_confidence=int(((df.predicted_cell_type == c) & (df.reliability == "low")).sum()), | |
| held_out_precision=CLASS_PERF[c][0], held_out_recall=CLASS_PERF[c][1]) | |
| for c in counts.index if c in CLASS_PERF] | |
| rep = dict( | |
| n_cells=n, um_per_px=um_per_px, | |
| expected_accuracy=round(expected, 3) if expected is not None else None, | |
| reliability=tier_rows, classes=classes, | |
| exodermis=dict(status=exo["status"], reason=exo["reason"], **exo["evidence"]), | |
| species=(None if species_row is None else | |
| {k: species_row.get(k) for k in ("binomial", "family", "match", "in_training", | |
| "exodermis_in_training", "exodermis_literature", | |
| "note", "source")}), | |
| layer_smoothing_changed=int(n_smoothed), | |
| warnings=warnings, | |
| basis="26 held-out sections, 11,043 labelled cells, accuracy 0.886 (95% CI 0.864 to 0.909)", | |
| ) | |
| return rep | |
| def to_text(rep): | |
| L = [f"Cells: {rep['n_cells']}", | |
| f"Expected accuracy: {rep['expected_accuracy']} " | |
| f"(from the confidence tiers; held-out accuracy 0.886)", | |
| "Reliability tiers (share of cells -> accuracy observed on held-out sections):"] | |
| for t in rep["reliability"]: | |
| L.append(f" {t['tier']:6s} {t['cells']:5d} cells ({100 * t['share']:.0f}%) -> {t['accuracy_on_held_out']:.2f}") | |
| e = rep["exodermis"] | |
| L.append(f"Exodermis: {e['status']} ({e['reason']})") | |
| if rep["species"]: | |
| s = rep["species"] | |
| lit = f"; literature: {s['exodermis_literature']}" if s.get("exodermis_literature") else "" | |
| L.append(f"Species: {s['binomial']} ({s['family']}, {s['match']} match; " | |
| f"in training: {s['in_training']}{lit})") | |
| if s.get("note"): | |
| L.append(f" {s['note']}") | |
| L.append(f"Cells changed by layer smoothing: {rep['layer_smoothing_changed']}") | |
| L.append("Per class (cells, low-confidence cells, held-out precision / recall):") | |
| for c in rep["classes"]: | |
| L.append(f" {c['cell_type']:11s} {c['cells']:5d} low {c['low_confidence']:4d} " | |
| f"{c['held_out_precision']:.2f} / {c['held_out_recall']:.2f}") | |
| for w in rep["warnings"]: | |
| L.append(f"WARNING: {w}") | |
| return "\n".join(L) | |
| def write(rep, path_json): | |
| with open(path_json, "w") as f: | |
| json.dump(rep, f, indent=2) | |
| txt = str(path_json).rsplit(".", 1)[0] + ".txt" | |
| with open(txt, "w") as f: | |
| f.write(to_text(rep) + "\n") | |
| return txt | |