stevafernandes commited on
Commit
d79f8b5
·
verified ·
1 Parent(s): c9b579e

Deploy CellQuant

Browse files
README.md CHANGED
@@ -37,7 +37,10 @@ Every nucleus receives exactly one of the four *Cell* labels.
37
  refinement stage keeps 5-pixel puncta.
38
  * **Instances** – nucleus foreground comes from the class map; markers (distance > 0.5) and the
39
  watershed landscape come from the predicted distance map, which has one peak per nucleus and
40
- a valley between touching ones; puncta are connected components (`cellquant/postprocess.py`).
 
 
 
41
  Checkpoints without the distance channel fall back to a watershed on the interior map.
42
  * **Labels** – antibody status is the mean probability inside each nucleus; a nucleus is
43
  "Nostril" when a punctum classified "in nucleus" lies inside it (the annotation protocol;
@@ -46,7 +49,7 @@ Every nucleus receives exactly one of the four *Cell* labels.
46
  * **Training data** – nine 1024×1024 RGB images with 1,569 nucleus outlines and 1,057
47
  punctum outlines, parsed directly from the CVAT zip exports (`cellquant/annotations.py`).
48
 
49
- ## Validation (see `reports/SUMMARY.md`, `reports/cv_v2/cv_report.md` and `reports/cv_v2/comparison.md`)
50
 
51
  Leave-one-image-out cross-validation: nine models, each tested on an image it never saw, and
52
  each image tested 23 times (repeat runs, TTA settings, 8 rotations/flips, 6 photometric
@@ -54,21 +57,22 @@ perturbations, rule variants). Micro-averaged over the nine held-out images:
54
 
55
  | Measure | Precision | Recall | F1 |
56
  |---|---:|---:|---:|
57
- | Nucleus detection, IoU ≥ 0.5 | 75.8% | 85.5% | 80.4% |
58
- | Nucleus detection, centroid inside the annotated nucleus | 83.7% | 94.5% | 88.8% |
59
  | Punctum detection (centroid within 6 px) | 76.4% | 84.8% | 80.4% |
60
 
61
- Among matched nuclei the antibody +/− call is right for 93.5% and the Nostril attribute for
62
- 95.6%; puncta are classified in-nucleus vs external correctly 93.4% of the time. Scores vary by
63
  at most ±3 F1 points across the 14 rotated/perturbed runs of each image, and repeated runs are
64
- bit-identical. The weak spots are antibody-negative nuclei (F1 38%; only 110 examples, 62 of
65
  them in one image, and the class-balanced loss trades their precision for recall), the five-example
66
- "Cell, Nostril, Antibody −" class, dense clusters of touching nuclei (still the main source of misses,
67
- although the distance-map watershed of this version recovers many that the first version merged), and
68
- dim nuclei the annotator skipped, which the model tends to count (about 13 % more nuclei than annotated).
69
- The previous version (v1: 2-px border, watershed on the interior map, unweighted antibody loss) scored
70
- 77.9% nucleus F1, 87.5% by the centroid rule and 79.9% puncta F1 on the same protocol; `reports/cv/`
71
- keeps its report. The shipped `weights/cellquant.pt` was trained on all nine images with the v2 recipe.
 
72
 
73
  ## Using the Space
74
 
@@ -137,16 +141,21 @@ Retrain when more annotated images are available (put the TIFs and the CVAT zips
137
 
138
  ```bash
139
  python train.py --data-dir /path/to/folder --out weights/cellquant.pt --iters 3000
140
- python evaluate.py cv --data-dir /path/to/folder --runs-dir runs/cv_v2 --iters 3000
 
141
  ```
142
 
 
 
 
143
  Training uses Apple-silicon MPS, CUDA or CPU automatically.
144
 
145
  ## Limitations
146
 
147
  * The annotator skipped faint / out-of-focus nuclei; the model learned that criterion only
148
  approximately, so very dim nuclei may be counted or not. Raise *Minimum nucleus area* or the
149
- thresholds in *Advanced* to tune for your images.
 
150
  * "Cell, Nostril, Antibody −" had only five training examples; its predictions are the
151
  product of the (well-trained) antibody and punctum heads rather than a learned class.
152
  * Images must have the same channel convention (blue nuclei, green antibody, red puncta) and a
 
37
  refinement stage keeps 5-pixel puncta.
38
  * **Instances** – nucleus foreground comes from the class map; markers (distance > 0.5) and the
39
  watershed landscape come from the predicted distance map, which has one peak per nucleus and
40
+ a valley between touching ones; a nucleus whose peak predicted distance stays below 0.64 is
41
+ dropped (the distance head is trained to reach 1 in every annotated nucleus and 0 elsewhere,
42
+ so a low peak marks a faint blob the annotator would have skipped; `PostConfig.min_peak_dist`,
43
+ 0 switches it off); puncta are connected components (`cellquant/postprocess.py`).
44
  Checkpoints without the distance channel fall back to a watershed on the interior map.
45
  * **Labels** – antibody status is the mean probability inside each nucleus; a nucleus is
46
  "Nostril" when a punctum classified "in nucleus" lies inside it (the annotation protocol;
 
49
  * **Training data** – nine 1024×1024 RGB images with 1,569 nucleus outlines and 1,057
50
  punctum outlines, parsed directly from the CVAT zip exports (`cellquant/annotations.py`).
51
 
52
+ ## Validation (see `reports/SUMMARY.md`, `reports/cv_v3/cv_report.md` and `reports/cv_v3/comparison.md`)
53
 
54
  Leave-one-image-out cross-validation: nine models, each tested on an image it never saw, and
55
  each image tested 23 times (repeat runs, TTA settings, 8 rotations/flips, 6 photometric
 
57
 
58
  | Measure | Precision | Recall | F1 |
59
  |---|---:|---:|---:|
60
+ | Nucleus detection, IoU ≥ 0.5 | 81.1% | 84.2% | 82.6% |
61
+ | Nucleus detection, centroid inside the annotated nucleus | 88.8% | 92.2% | 90.5% |
62
  | Punctum detection (centroid within 6 px) | 76.4% | 84.8% | 80.4% |
63
 
64
+ Among matched nuclei the antibody +/− call is right for 93.8% and the Nostril attribute for
65
+ 95.7%; puncta are classified in-nucleus vs external correctly 93.4% of the time. Scores vary by
66
  at most ±3 F1 points across the 14 rotated/perturbed runs of each image, and repeated runs are
67
+ bit-identical. The weak spots are antibody-negative nuclei (F1 40%; only 110 examples, 62 of
68
  them in one image, and the class-balanced loss trades their precision for recall), the five-example
69
+ "Cell, Nostril, Antibody −" class, and dense clusters of touching nuclei (still the main source of misses,
70
+ although the distance-map watershed recovers many that the first version merged). The model predicts
71
+ 1629 nuclei for the 1569 annotated ones across the nine held-out images. Earlier versions on the same
72
+ protocol: v1 (2-px border, watershed on the interior map, unweighted antibody loss) 77.9% nucleus F1,
73
+ 87.5% by the centroid rule, 79.9% puncta F1 (`reports/cv/`); v2 (this model without the peak-distance
74
+ rule) 80.4%, 88.8% and 80.4% (`reports/cv_v2/`). The shipped `weights/cellquant.pt` was trained on all nine
75
+ images with the v2 recipe; the peak-distance rule is post-processing only.
76
 
77
  ## Using the Space
78
 
 
141
 
142
  ```bash
143
  python train.py --data-dir /path/to/folder --out weights/cellquant.pt --iters 3000
144
+ python evaluate.py cv --data-dir /path/to/folder --runs-dir runs/cv_v3 --iters 3000
145
+ python scripts/rule_study.py --data-dir /path/to/folder --runs-dir runs/cv_v2 --out runs/cv_v3/rule_study --weights weights/cellquant.pt
146
  ```
147
 
148
+ The last line re-chooses the peak-distance threshold from the training-set fit (per fold and for the
149
+ all-image model); copy the printed value into `PostConfig.min_peak_dist`.
150
+
151
  Training uses Apple-silicon MPS, CUDA or CPU automatically.
152
 
153
  ## Limitations
154
 
155
  * The annotator skipped faint / out-of-focus nuclei; the model learned that criterion only
156
  approximately, so very dim nuclei may be counted or not. Raise *Minimum nucleus area* or the
157
+ thresholds in *Advanced* to tune for your images; `PostConfig(min_peak_dist=...)` (default
158
+ 0.64, 0 = off) sets how confident the distance head must be before a nucleus is counted.
159
  * "Cell, Nostril, Antibody −" had only five training examples; its predictions are the
160
  product of the (well-trained) antibody and punctum heads rather than a learned class.
161
  * Images must have the same channel convention (blue nuclei, green antibody, red puncta) and a
cellquant/__init__.py CHANGED
@@ -3,4 +3,4 @@
3
  Trained on CVAT polygon annotations of 1024x1024 RGB images (blue = DAPI nuclei,
4
  green = antibody stain such as Iba1 or Tmem119, red = puncta).
5
  """
6
- __version__ = "1.1.0"
 
3
  Trained on CVAT polygon annotations of 1024x1024 RGB images (blue = DAPI nuclei,
4
  green = antibody stain such as Iba1 or Tmem119, red = puncta).
5
  """
6
+ __version__ = "1.2.0"
cellquant/postprocess.py CHANGED
@@ -18,6 +18,12 @@ class PostConfig:
18
  # P(interior)) > thr -> watershed markers
19
  min_marker_area: int = 12 # px
20
  min_cell_area: int = 50 # px (smallest traced nucleus in the training set is 35 px)
 
 
 
 
 
 
21
  nos_fg_thr: float = 0.5 # 1 - P(background) > thr -> punctum foreground
22
  min_nos_area: int = 3 # px
23
  antibody_thr: float = 0.5
@@ -53,7 +59,9 @@ def segment_cells(p_cell: np.ndarray, cfg: PostConfig, dist: np.ndarray | None =
53
  Foreground always comes from the class map. Markers and the watershed
54
  landscape come from ``dist`` (the predicted normalised distance to the
55
  outline, which has one peak per nucleus and a valley between touching ones)
56
- when the model provides it, otherwise from P(interior)."""
 
 
57
  p_int, p_bor = p_cell[1], p_cell[2]
58
  fg = (p_int + p_bor) > cfg.cell_fg_thr
59
  land = p_int if dist is None else dist
@@ -78,10 +86,33 @@ def segment_cells(p_cell: np.ndarray, cfg: PostConfig, dist: np.ndarray | None =
78
  base = inst.max()
79
  for j, oid in enumerate(orphan_ids, start=1):
80
  inst[orphan == oid] = base + j
 
 
81
  inst = _fill_and_filter(inst, cfg.min_cell_area)
82
  return inst
83
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def _fill_and_filter(inst: np.ndarray, min_area: int) -> np.ndarray:
86
  """Fill holes inside every instance, drop instances below ``min_area`` and
87
  keep only the largest connected piece of each instance; relabel 1..n."""
 
18
  # P(interior)) > thr -> watershed markers
19
  min_marker_area: int = 12 # px
20
  min_cell_area: int = 50 # px (smallest traced nucleus in the training set is 35 px)
21
+ min_peak_dist: float = 0.64 # drop a nucleus whose peak predicted distance is below this (0 = off;
22
+ # only applied when the model provides the distance map). The distance
23
+ # head is trained to reach 1 inside every annotated nucleus and to stay
24
+ # at 0 elsewhere, so a low peak marks a blob the annotator would skip.
25
+ # 0.64 maximises nucleus F1 on the training-set fit of the shipped model
26
+ # (scripts/rule_study.py); see reports/cv_v3/.
27
  nos_fg_thr: float = 0.5 # 1 - P(background) > thr -> punctum foreground
28
  min_nos_area: int = 3 # px
29
  antibody_thr: float = 0.5
 
59
  Foreground always comes from the class map. Markers and the watershed
60
  landscape come from ``dist`` (the predicted normalised distance to the
61
  outline, which has one peak per nucleus and a valley between touching ones)
62
+ when the model provides it, otherwise from P(interior). With ``dist`` given,
63
+ instances whose peak distance is below ``cfg.min_peak_dist`` are dropped
64
+ (see PostConfig)."""
65
  p_int, p_bor = p_cell[1], p_cell[2]
66
  fg = (p_int + p_bor) > cfg.cell_fg_thr
67
  land = p_int if dist is None else dist
 
86
  base = inst.max()
87
  for j, oid in enumerate(orphan_ids, start=1):
88
  inst[orphan == oid] = base + j
89
+ if dist is not None and cfg.min_peak_dist > 0:
90
+ inst = drop_low_peaks(inst, dist, cfg.min_peak_dist)
91
  inst = _fill_and_filter(inst, cfg.min_cell_area)
92
  return inst
93
 
94
 
95
+ def peak_dist(inst: np.ndarray, dist: np.ndarray) -> np.ndarray:
96
+ """Maximum of ``dist`` inside every instance (index 0 = background, set to 0)."""
97
+ n = int(inst.max())
98
+ out = np.zeros(n + 1, dtype=np.float64)
99
+ if n:
100
+ out[1:] = ndi.maximum(dist, inst, np.arange(1, n + 1))
101
+ return out
102
+
103
+
104
+ def drop_low_peaks(inst: np.ndarray, dist: np.ndarray, thr: float) -> np.ndarray:
105
+ """Zero every instance whose peak predicted distance is below ``thr``
106
+ (ids are not renumbered; _fill_and_filter does that)."""
107
+ pk = peak_dist(inst, dist)
108
+ low = pk < thr
109
+ low[0] = False
110
+ if low.any():
111
+ inst = inst.copy()
112
+ inst[low[inst]] = 0
113
+ return inst
114
+
115
+
116
  def _fill_and_filter(inst: np.ndarray, min_area: int) -> np.ndarray:
117
  """Fill holes inside every instance, drop instances below ``min_area`` and
118
  keep only the largest connected piece of each instance; relabel 1..n."""
evaluate.py CHANGED
@@ -26,6 +26,7 @@ import os
26
  import subprocess
27
  import sys
28
  import time
 
29
 
30
  import numpy as np
31
  from PIL import Image
@@ -40,6 +41,35 @@ from cellquant.postprocess import PostConfig, Result, analyze, render_overlay #
40
  DIHEDRAL = [(k, f) for f in (False, True) for k in range(4)]
41
 
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  def fwd_np(a: np.ndarray, k: int, flip: bool) -> np.ndarray:
44
  if flip:
45
  a = a[..., ::-1]
@@ -146,9 +176,10 @@ def inst_hash(res: Result) -> str:
146
  return h.hexdigest()[:16]
147
 
148
 
149
- def test_image(weights: str, img: np.ndarray, R, gt_counts: dict, device: str, out_prefix: str | None = None) -> dict:
150
- cfg = PostConfig()
151
- out: dict = {"runs": {}}
 
152
  p1 = Predictor(weights, device=device, tta=1)
153
  # base + determinism
154
  hashes = []
@@ -181,7 +212,7 @@ def test_image(weights: str, img: np.ndarray, R, gt_counts: dict, device: str, o
181
  # post-processing rule variants (same probabilities as the base run)
182
  for nr in ("head", "geometric"):
183
  for cr in ("head", "contains", "either"):
184
- res = analyze(base_probs, PostConfig(nostril_rule=nr, cellnos_rule=cr))
185
  out["runs"][f"rule_nos={nr}_cell={cr}"] = score(R, res, gt_counts)
186
  return out
187
 
@@ -318,6 +349,7 @@ def cmd_cv(a):
318
  samples, _ = discover(a.data_dir)
319
  keys = a.keys.split(",") if a.keys else [s.key for s in samples]
320
  os.makedirs(a.runs_dir, exist_ok=True)
 
321
  per_key = {}
322
  for s in samples:
323
  if s.key not in keys:
@@ -344,7 +376,11 @@ def cmd_cv(a):
344
  img = load_image(s.image_path)
345
  R = rasterize(s.annotation, img.shape[0], img.shape[1])
346
  t = time.time()
347
- res = test_image(ck, img, R, s.annotation.counts(), a.device, out_prefix=os.path.join(a.runs_dir, s.key))
 
 
 
 
348
  res["train_keys"] = list(meta.get("train_keys", []))
349
  res["checkpoint"] = os.path.basename(ck)
350
  per_key[s.key] = res
@@ -355,6 +391,7 @@ def cmd_cv(a):
355
  agg = aggregate(per_key)
356
  totals = {lab: sum(s.annotation.count(lab) for s in samples) for lab in ALL_LABELS}
357
  agg["dataset_totals"] = totals
 
358
  with open(os.path.join(a.runs_dir, "cv_results.json"), "w") as f:
359
  json.dump({"aggregate": agg, "per_image": per_key}, f, indent=1)
360
  n_nuc = sum(totals[c] for c in CELL_LABELS)
@@ -368,19 +405,20 @@ def cmd_cv(a):
368
  def cmd_final(a):
369
  samples, unann = discover(a.data_dir)
370
  os.makedirs(a.out_dir, exist_ok=True)
 
371
  pred = Predictor(a.weights, device=a.device, tta=1)
372
- rows = {}
373
  for s in samples:
374
  img = load_image(s.image_path)
375
  R = rasterize(s.annotation, img.shape[0], img.shape[1])
376
- res, _ = pred.predict(img, PostConfig())
377
  rows[s.key] = score(R, res, s.annotation.counts())
378
  Image.fromarray(render_overlay(img, res)).save(os.path.join(a.out_dir, f"{s.key}_pred.png"))
379
  b = rows[s.key]["summary"]
380
  print(f"[{s.key}] TRAINING-SET fit: nuclei F1 {b['cell_det_f1']:.3f} attr {b['cell_attr_both']:.3f} puncta F1 {b['nos_det_f1']:.3f}")
381
  for p in unann:
382
  img = load_image(p)
383
- res, _ = pred.predict(img, PostConfig())
384
  key = os.path.basename(p).split(" ")[0]
385
  Image.fromarray(render_overlay(img, res)).save(os.path.join(a.out_dir, f"{key}_unlabeled_pred.png"))
386
  rows[f"{key} (no annotation)"] = {"counts": res.counts}
@@ -401,11 +439,13 @@ def main(argv=None):
401
  c.add_argument("--device", default="auto")
402
  c.add_argument("--retrain", action="store_true")
403
  c.add_argument("--reuse-eval", action="store_true", help="reuse <key>_eval.json files newer than their checkpoint")
 
404
  f = sub.add_parser("final")
405
  f.add_argument("--data-dir", required=True)
406
  f.add_argument("--weights", required=True)
407
  f.add_argument("--out-dir", default="reports/final")
408
  f.add_argument("--device", default="auto")
 
409
  a = ap.parse_args(argv)
410
  {"cv": cmd_cv, "final": cmd_final}[a.cmd](a)
411
 
 
26
  import subprocess
27
  import sys
28
  import time
29
+ from dataclasses import asdict, replace
30
 
31
  import numpy as np
32
  from PIL import Image
 
41
  DIHEDRAL = [(k, f) for f in (False, True) for k in range(4)]
42
 
43
 
44
+ def load_post_config(spec: str) -> dict:
45
+ """``--post-config`` argument -> dict. Either a JSON file / literal holding
46
+ PostConfig field overrides for every image ({"min_peak_dist": 0.63}) or a
47
+ mapping image key -> overrides ({"C100": {"min_peak_dist": 0.61}, ...}),
48
+ e.g. thresholds chosen per fold on that fold's training images."""
49
+ if not spec:
50
+ return {}
51
+ if os.path.exists(spec):
52
+ with open(spec) as f:
53
+ d = json.load(f)
54
+ else:
55
+ d = json.loads(spec)
56
+ if not isinstance(d, dict):
57
+ raise SystemExit("--post-config must be a JSON object")
58
+ return d
59
+
60
+
61
+ def post_config_for(overrides: dict, key: str) -> PostConfig:
62
+ """PostConfig for one image from the --post-config dict (see load_post_config)."""
63
+ if overrides and all(isinstance(v, dict) for v in overrides.values()):
64
+ o = overrides.get(key, {})
65
+ else:
66
+ o = overrides
67
+ unknown = set(o) - set(PostConfig.__dataclass_fields__)
68
+ if unknown:
69
+ raise SystemExit(f"--post-config: unknown PostConfig fields {sorted(unknown)}")
70
+ return PostConfig(**o)
71
+
72
+
73
  def fwd_np(a: np.ndarray, k: int, flip: bool) -> np.ndarray:
74
  if flip:
75
  a = a[..., ::-1]
 
176
  return h.hexdigest()[:16]
177
 
178
 
179
+ def test_image(weights: str, img: np.ndarray, R, gt_counts: dict, device: str, out_prefix: str | None = None,
180
+ cfg: PostConfig | None = None) -> dict:
181
+ cfg = cfg or PostConfig()
182
+ out: dict = {"runs": {}, "post_config": asdict(cfg)}
183
  p1 = Predictor(weights, device=device, tta=1)
184
  # base + determinism
185
  hashes = []
 
212
  # post-processing rule variants (same probabilities as the base run)
213
  for nr in ("head", "geometric"):
214
  for cr in ("head", "contains", "either"):
215
+ res = analyze(base_probs, replace(cfg, nostril_rule=nr, cellnos_rule=cr))
216
  out["runs"][f"rule_nos={nr}_cell={cr}"] = score(R, res, gt_counts)
217
  return out
218
 
 
349
  samples, _ = discover(a.data_dir)
350
  keys = a.keys.split(",") if a.keys else [s.key for s in samples]
351
  os.makedirs(a.runs_dir, exist_ok=True)
352
+ overrides = load_post_config(a.post_config)
353
  per_key = {}
354
  for s in samples:
355
  if s.key not in keys:
 
376
  img = load_image(s.image_path)
377
  R = rasterize(s.annotation, img.shape[0], img.shape[1])
378
  t = time.time()
379
+ cfg = post_config_for(overrides, s.key)
380
+ if overrides:
381
+ changed = {k: v for k, v in asdict(cfg).items() if v != getattr(PostConfig(), k)}
382
+ print(f"[{s.key}] post-processing overrides: {changed}", flush=True)
383
+ res = test_image(ck, img, R, s.annotation.counts(), a.device, out_prefix=os.path.join(a.runs_dir, s.key), cfg=cfg)
384
  res["train_keys"] = list(meta.get("train_keys", []))
385
  res["checkpoint"] = os.path.basename(ck)
386
  per_key[s.key] = res
 
391
  agg = aggregate(per_key)
392
  totals = {lab: sum(s.annotation.count(lab) for s in samples) for lab in ALL_LABELS}
393
  agg["dataset_totals"] = totals
394
+ agg["post_config"] = {k: per_key[k].get("post_config", asdict(PostConfig())) for k in per_key}
395
  with open(os.path.join(a.runs_dir, "cv_results.json"), "w") as f:
396
  json.dump({"aggregate": agg, "per_image": per_key}, f, indent=1)
397
  n_nuc = sum(totals[c] for c in CELL_LABELS)
 
405
  def cmd_final(a):
406
  samples, unann = discover(a.data_dir)
407
  os.makedirs(a.out_dir, exist_ok=True)
408
+ overrides = load_post_config(a.post_config)
409
  pred = Predictor(a.weights, device=a.device, tta=1)
410
+ rows = {"post_config": asdict(post_config_for(overrides, ""))}
411
  for s in samples:
412
  img = load_image(s.image_path)
413
  R = rasterize(s.annotation, img.shape[0], img.shape[1])
414
+ res, _ = pred.predict(img, post_config_for(overrides, s.key))
415
  rows[s.key] = score(R, res, s.annotation.counts())
416
  Image.fromarray(render_overlay(img, res)).save(os.path.join(a.out_dir, f"{s.key}_pred.png"))
417
  b = rows[s.key]["summary"]
418
  print(f"[{s.key}] TRAINING-SET fit: nuclei F1 {b['cell_det_f1']:.3f} attr {b['cell_attr_both']:.3f} puncta F1 {b['nos_det_f1']:.3f}")
419
  for p in unann:
420
  img = load_image(p)
421
+ res, _ = pred.predict(img, post_config_for(overrides, ""))
422
  key = os.path.basename(p).split(" ")[0]
423
  Image.fromarray(render_overlay(img, res)).save(os.path.join(a.out_dir, f"{key}_unlabeled_pred.png"))
424
  rows[f"{key} (no annotation)"] = {"counts": res.counts}
 
439
  c.add_argument("--device", default="auto")
440
  c.add_argument("--retrain", action="store_true")
441
  c.add_argument("--reuse-eval", action="store_true", help="reuse <key>_eval.json files newer than their checkpoint")
442
+ c.add_argument("--post-config", default="", help="JSON (file or literal) of PostConfig overrides, global or per image key")
443
  f = sub.add_parser("final")
444
  f.add_argument("--data-dir", required=True)
445
  f.add_argument("--weights", required=True)
446
  f.add_argument("--out-dir", default="reports/final")
447
  f.add_argument("--device", default="auto")
448
+ f.add_argument("--post-config", default="", help="JSON (file or literal) of PostConfig overrides")
449
  a = ap.parse_args(argv)
450
  {"cv": cmd_cv, "final": cmd_final}[a.cmd](a)
451
 
reports/SUMMARY.md CHANGED
@@ -1,27 +1,27 @@
1
  # CellQuant: validation summary
2
 
3
  Data: 9 annotated 1024×1024 images, 1569 nucleus outlines and 1057 punctum outlines.
4
- Protocol: leave-one-image-out cross-validation (one model per image, 3000 iterations each, held-out image never seen in training), then a final model trained on all images with the same recipe. Each held-out image was tested 23 times: 3 identical runs (determinism), no-TTA / 4× / 8× test-time augmentation, 8 rotations and flips, 6 photometric perturbations, and 6 post-processing rule variants. The only choice made on held-out data is the rule that gives a nucleus the Nostril attribute: of three candidate rules, 'contains an in-nucleus punctum' (the annotation protocol) scored best and is the default; see the rule-variant table in cv_report.md.
5
 
6
- Recipe (v2, shipped): ResNet34-UNet with a full-resolution stem; targets are a 3-px inner border ring (class weight 3), a per-nucleus normalised distance-to-outline map that the watershed uses as its landscape (markers = distance > 0.5), an antibody head whose loss up-weights antibody-negative pixels by sqrt(positive / negative pixels) (4.5 on the full set), and 25 % of training crops centred on an antibody-negative nucleus. v1 (reports/cv/) used a 2-px ring, the interior map as watershed landscape and an unweighted antibody loss; the change table below compares the two under the same protocol.
7
 
8
  ## Headline numbers (micro-averaged over all held-out images, no TTA)
9
 
10
  | Measure | Precision | Recall | F1 |
11
  |---|---:|---:|---:|
12
- | Nucleus detection, IoU ≥ 0.5 | 75.8% | 85.5% | 80.4% |
13
- | Nucleus detection, IoU ≥ 0.3 | 82.7% | 93.3% | 87.7% |
14
- | Nucleus detection, centroid inside GT nucleus | 83.7% | 94.5% | 88.8% |
15
  | Punctum detection (centroid ≤ 6 px) | 76.4% | 84.8% | 80.4% |
16
- | Nucleus detection, IoU ≥ 0.5, 8× TTA | 75.9% | 85.7% | 80.5% |
17
 
18
  Per-class F1 (a true positive needs a matched object **and** the right label):
19
 
20
  | Label | GT | Pred | F1 |
21
  |---|---:|---:|---:|
22
- | Cell, Antibody + | 1061 | 1292 | 74.4% |
23
- | Cell, Antibody - | 105 | 91 | 37.8% |
24
- | Cell, Nostril, Antibody + | 398 | 388 | 72.8% |
25
  | Cell, Nostril, Antibody - | 5 | 0 | 0.0% |
26
  | Nostril in Nucleus | 525 | 532 | 78.7% |
27
  | Nostril External of Nucleus | 532 | 641 | 71.8% |
@@ -30,68 +30,68 @@ Per-class F1 (a true positive needs a matched object **and** the right label):
30
 
31
  | Image | Nuclei F1 | Antibody acc | Nostril-attr acc | Puncta F1 | Nuclei F1 over 14 perturbed runs (mean ± sd) | Deterministic |
32
  |---|---:|---:|---:|---:|---|---|
33
- | C100 | 84.4% | 96.9% | 98.1% | 71.4% | 85.5 ± 1.5 | yes |
34
- | C102 | 86.4% | 100.0% | 89.5% | 78.9% | 86.6 ± 1.2 | yes |
35
- | C110 | 77.4% | 98.7% | 94.2% | 83.8% | 78.4 ± 1.5 | yes |
36
- | C112 | 71.6% | 95.0% | 97.5% | 76.9% | 71.0 ± 1.4 | yes |
37
- | F100 | 82.9% | 98.1% | 94.4% | 85.7% | 78.8 ± 2.8 | yes |
38
- | F110 | 77.0% | 97.2% | 90.8% | 82.9% | 71.7 ± 2.5 | yes |
39
- | F113 | 69.9% | 100.0% | 97.4% | 83.3% | 70.6 ± 1.5 | yes |
40
- | F114 | 82.4% | 90.1% | 94.1% | 63.5% | 81.8 ± 0.8 | yes |
41
- | F99 | 87.5% | 79.3% | 99.6% | 74.3% | 87.8 ± 0.7 | yes |
42
 
43
  ## Final model (trained on all images) – fit on its own training images, NOT a generalisation estimate
44
 
45
  | Image | Nuclei F1 | Attribute acc | Puncta F1 |
46
  |---|---:|---:|---:|
47
- | C100 | 93.0% | 98.3% | 80.0% |
48
- | C102 | 91.5% | 94.5% | 78.8% |
49
- | C110 | 87.0% | 96.4% | 83.9% |
50
- | C112 | 80.0% | 99.2% | 82.0% |
51
- | F100 | 88.7% | 94.8% | 90.3% |
52
- | F110 | 89.7% | 97.5% | 88.6% |
53
- | F113 | 84.8% | 97.9% | 92.1% |
54
- | F114 | 90.8% | 95.1% | 82.5% |
55
- | F99 | 91.5% | 97.1% | 90.9% |
56
-
57
- ## Change from v1 (same protocol, same nine held-out images; `reports/cv_v2/comparison.md` has the full table)
58
-
59
- | Measure | v1 | v2 | Δ (points) |
60
  |---|---:|---:|---:|
61
- | Nucleus F1, IoU ≥ 0.5 | 77.9% | 80.4% | +2.4 |
62
- | Nucleus precision, IoU ≥ 0.5 | 77.5% | 75.8% | -1.7 |
63
- | Nucleus recall, IoU ≥ 0.5 | 78.4% | 85.5% | +7.1 |
64
- | Nucleus F1, centroid rule | 87.5% | 88.8% | +1.3 |
65
- | Nucleus F1, IoU ≥ 0.5, 8× TTA | 78.5% | 80.5% | +2.0 |
66
- | Puncta F1 | 79.9% | 80.4% | +0.4 |
67
- | F1 [Cell, Antibody +] | 72.5% | 74.4% | +1.9 |
68
- | F1 [Cell, Antibody -] | 31.9% | 37.8% | +5.8 |
69
- | F1 [Cell, Nostril, Antibody +] | 71.6% | 72.8% | +1.2 |
70
  | F1 [Cell, Nostril, Antibody -] | 0.0% | 0.0% | +0.0 |
71
- | F1 [Nostril in Nucleus] | 78.9% | 78.7% | -0.2 |
72
- | F1 [Nostril External of Nucleus] | 70.3% | 71.8% | +1.5 |
73
- | Attribute accuracy, both (each model's own matched nuclei) | 90.2% | 89.3% | -1.0 |
74
- | Punctum type accuracy (matched puncta) | 93.1% | 93.4% | +0.3 |
75
- | Nuclei F1, C100 held out | 81.2% | 84.4% | +3.2 |
76
- | Nuclei F1, C102 held out | 90.6% | 86.4% | -4.2 |
77
- | Nuclei F1, C110 held out | 78.5% | 77.4% | -1.1 |
78
- | Nuclei F1, C112 held out | 75.0% | 71.6% | -3.4 |
79
- | Nuclei F1, F100 held out | 75.1% | 82.9% | +7.8 |
80
- | Nuclei F1, F110 held out | 72.0% | 77.0% | +5.0 |
81
- | Nuclei F1, F113 held out | 65.4% | 69.9% | +4.5 |
82
- | Nuclei F1, F114 held out | 78.8% | 82.4% | +3.7 |
83
- | Nuclei F1, F99 held out | 84.4% | 87.5% | +3.2 |
84
-
85
- v2 misses far fewer nuclei (339 → 227 unmatched annotated nuclei, mostly touching nuclei that v1 merged) at the cost of more unmatched predictions (358 → 429): it predicts 1771 nuclei for 1569 annotated ones (v1: 1588). The extra predictions are dim objects the annotator skipped and outline/split disagreements in roughly the proportion shown in the FP table of cv_report.md. On the nuclei matched by both models the attribute accuracy differs by less than one point (see comparison.md), which is within the sampling error of ~1200 nuclei; the antibody-negative class gains recall (26 % → 38 % on matched nuclei) and loses precision (74 % → 59 %).
86
-
87
- ## Antibody − threshold (held-out matched nuclei: 1342, of which 98 negative)
88
-
89
- Default threshold 0.5. Best Antibody − F1 in the sweep: 50.8% at threshold 0.90 (precision 51.6%, recall 50.0%); the full curve is in cv_report.md. The default was kept because the class is dominated by a single image (F99 holds 62 of the 110 negatives).
90
 
91
  ## What limits the scores
92
 
93
  * **Touching nuclei** are still the main source of IoU-0.5 misses (worst on F113, the densest image). The distance-map watershed of v2 recovers many clusters that v1 merged (v1 marker/threshold sweeps in `reports/cv/postprocess_sweep.json` had shown post-processing alone could not), but some clusters are still merged and a few nuclei are now split in two.
94
- * **Annotation completeness**: most unmatched predictions overlap an annotated nucleus (outline disagreement) and the rest are dimmer, smaller objects the annotator skipped; many faint red dots were likewise not annotated, which caps external-punctum precision. v2 detects more of these dim objects than v1, so it over-counts nuclei by about 13 % on the annotated images.
95
  * **Antibody − is rare** (110 of 1569 nuclei, 62 in one image), and **Cell, Nostril, Antibody −** has five examples; their scores are not reliable. The class-balanced loss of v2 trades antibody-negative precision for recall.
96
  * Scores are from nine images of one experiment (35-day spinal cord, Iba1/TMEM119). New staining batches or magnifications need a check against a few annotated images.
97
 
@@ -99,7 +99,9 @@ Default threshold 0.5. Best Antibody − F1 in the sweep: 50.8% at threshold 0.9
99
 
100
  ```bash
101
  python evaluate.py cv --data-dir <folder with TIFs and CVAT zips> --runs-dir runs/cv_v2 --iters 3000
 
 
102
  python evaluate.py final --data-dir <folder> --weights weights/cellquant.pt
103
- python scripts/compare_cv.py reports/cv/cv_results.json runs/cv_v2/cv_results.json --out reports/cv_v2/comparison.md
104
- python scripts/build_summary.py --cv-dir runs/cv_v2
105
  ```
 
1
  # CellQuant: validation summary
2
 
3
  Data: 9 annotated 1024×1024 images, 1569 nucleus outlines and 1057 punctum outlines.
4
+ Protocol: leave-one-image-out cross-validation (one model per image, 3000 iterations each, held-out image never seen in training), then a final model trained on all images with the same recipe. Each held-out image was tested 23 times: 3 identical runs (determinism), no-TTA / 4× / 8× test-time augmentation, 8 rotations and flips, 6 photometric perturbations, and 6 post-processing rule variants. The only choice made on held-out data is the rule that gives a nucleus the Nostril attribute: of three candidate rules, 'contains an in-nucleus punctum' (the annotation protocol) scored best and is the default; see the rule-variant table in cv_report.md. The peak-distance threshold of v3 was chosen, for every fold, on that fold's eight training images only (scripts/rule_study.py), and for the shipped model on its own nine training images; the held-out images never entered that choice.
5
 
6
+ Recipe (v3, shipped): the v2 model (ResNet34-UNet with a full-resolution stem; targets are a 3-px inner border ring (class weight 3), a per-nucleus normalised distance-to-outline map that the watershed uses as its landscape (markers = distance > 0.5), an antibody head whose loss up-weights antibody-negative pixels by sqrt(positive / negative pixels) (4.5 on the full set), and 25 % of training crops centred on an antibody-negative nucleus) plus one post-processing rule: a nucleus whose peak predicted distance is below 0.64 is dropped (PostConfig.min_peak_dist). The distance head is trained to reach 1 inside every annotated nucleus and to stay at 0 everywhere else, so a low peak marks a blob the annotator would have skipped. The weights are unchanged from v2 (reports/cv_v2/); v1 (reports/cv/) used a 2-px ring, the interior map as watershed landscape and an unweighted antibody loss. The change table below compares the shipped recipe with the previous one under the same protocol.
7
 
8
  ## Headline numbers (micro-averaged over all held-out images, no TTA)
9
 
10
  | Measure | Precision | Recall | F1 |
11
  |---|---:|---:|---:|
12
+ | Nucleus detection, IoU ≥ 0.5 | 81.1% | 84.2% | 82.6% |
13
+ | Nucleus detection, IoU ≥ 0.3 | 88.0% | 91.3% | 89.6% |
14
+ | Nucleus detection, centroid inside GT nucleus | 88.8% | 92.2% | 90.5% |
15
  | Punctum detection (centroid ≤ 6 px) | 76.4% | 84.8% | 80.4% |
16
+ | Nucleus detection, IoU ≥ 0.5, 8× TTA | 82.5% | 83.4% | 82.9% |
17
 
18
  Per-class F1 (a true positive needs a matched object **and** the right label):
19
 
20
  | Label | GT | Pred | F1 |
21
  |---|---:|---:|---:|
22
+ | Cell, Antibody + | 1061 | 1191 | 76.8% |
23
+ | Cell, Antibody - | 105 | 75 | 40.0% |
24
+ | Cell, Nostril, Antibody + | 398 | 363 | 74.1% |
25
  | Cell, Nostril, Antibody - | 5 | 0 | 0.0% |
26
  | Nostril in Nucleus | 525 | 532 | 78.7% |
27
  | Nostril External of Nucleus | 532 | 641 | 71.8% |
 
30
 
31
  | Image | Nuclei F1 | Antibody acc | Nostril-attr acc | Puncta F1 | Nuclei F1 over 14 perturbed runs (mean ± sd) | Deterministic |
32
  |---|---:|---:|---:|---:|---|---|
33
+ | C100 | 87.4% | 96.9% | 98.1% | 71.4% | 87.6 ± 1.4 | yes |
34
+ | C102 | 88.7% | 100.0% | 89.5% | 78.9% | 89.0 ± 1.3 | yes |
35
+ | C110 | 82.4% | 98.7% | 94.2% | 83.8% | 82.6 ± 1.6 | yes |
36
+ | C112 | 74.6% | 95.8% | 97.5% | 76.9% | 74.2 ± 1.5 | yes |
37
+ | F100 | 84.4% | 98.1% | 94.4% | 85.7% | 80.5 ± 2.7 | yes |
38
+ | F110 | 80.2% | 97.1% | 91.4% | 82.9% | 76.7 ± 2.3 | yes |
39
+ | F113 | 71.8% | 100.0% | 97.3% | 83.3% | 72.1 ± 1.5 | yes |
40
+ | F114 | 83.1% | 91.2% | 94.3% | 63.5% | 82.0 ± 1.1 | yes |
41
+ | F99 | 88.0% | 79.6% | 99.6% | 74.3% | 88.3 ± 0.8 | yes |
42
 
43
  ## Final model (trained on all images) – fit on its own training images, NOT a generalisation estimate
44
 
45
  | Image | Nuclei F1 | Attribute acc | Puncta F1 |
46
  |---|---:|---:|---:|
47
+ | C100 | 94.2% | 98.8% | 80.0% |
48
+ | C102 | 94.8% | 94.5% | 78.8% |
49
+ | C110 | 90.5% | 96.4% | 83.9% |
50
+ | C112 | 84.8% | 99.2% | 82.0% |
51
+ | F100 | 90.1% | 94.8% | 90.3% |
52
+ | F110 | 91.9% | 97.5% | 88.6% |
53
+ | F113 | 85.2% | 97.8% | 92.1% |
54
+ | F114 | 91.7% | 95.0% | 82.5% |
55
+ | F99 | 91.9% | 97.0% | 90.9% |
56
+
57
+ ## Change from v2 (same protocol, same nine held-out images; `reports/cv_v3/comparison.md` has the full table)
58
+
59
+ | Measure | v2 | v3 | Δ (points) |
60
  |---|---:|---:|---:|
61
+ | Nucleus F1, IoU ≥ 0.5 | 80.4% | 82.6% | +2.3 |
62
+ | Nucleus precision, IoU ≥ 0.5 | 75.8% | 81.1% | +5.3 |
63
+ | Nucleus recall, IoU ≥ 0.5 | 85.5% | 84.2% | -1.3 |
64
+ | Nucleus F1, centroid rule | 88.8% | 90.5% | +1.7 |
65
+ | Nucleus F1, IoU ≥ 0.5, 8× TTA | 80.5% | 82.9% | +2.4 |
66
+ | Puncta F1 | 80.4% | 80.4% | +0.0 |
67
+ | F1 [Cell, Antibody +] | 74.4% | 76.8% | +2.4 |
68
+ | F1 [Cell, Antibody -] | 37.8% | 40.0% | +2.2 |
69
+ | F1 [Cell, Nostril, Antibody +] | 72.8% | 74.1% | +1.3 |
70
  | F1 [Cell, Nostril, Antibody -] | 0.0% | 0.0% | +0.0 |
71
+ | F1 [Nostril in Nucleus] | 78.7% | 78.7% | +0.0 |
72
+ | F1 [Nostril External of Nucleus] | 71.8% | 71.8% | +0.0 |
73
+ | Attribute accuracy, both (each model's own matched nuclei) | 89.3% | 89.6% | +0.3 |
74
+ | Punctum type accuracy (matched puncta) | 93.4% | 93.4% | +0.0 |
75
+ | Nuclei F1, C100 held out | 84.4% | 87.4% | +3.0 |
76
+ | Nuclei F1, C102 held out | 86.4% | 88.7% | +2.2 |
77
+ | Nuclei F1, C110 held out | 77.4% | 82.4% | +5.0 |
78
+ | Nuclei F1, C112 held out | 71.6% | 74.6% | +3.0 |
79
+ | Nuclei F1, F100 held out | 82.9% | 84.4% | +1.5 |
80
+ | Nuclei F1, F110 held out | 77.0% | 80.2% | +3.1 |
81
+ | Nuclei F1, F113 held out | 69.9% | 71.8% | +1.9 |
82
+ | Nuclei F1, F114 held out | 82.4% | 83.1% | +0.6 |
83
+ | Nuclei F1, F99 held out | 87.5% | 88.0% | +0.4 |
84
+
85
+ v3 keeps the v2 weights and drops 142 of the 1771 nuclei v2 predicted on the nine held-out images (1569 annotated): unmatched predictions fall from 429 to 308 (those overlapping no annotated nucleus from 143 to 72) while matched ones fall from 1342 to 1321, so precision rises by 5.3 points and recall drops by 1.3. Puncta are untouched. The over-count of nuclei on the annotated images shrinks from 13 % to 4 %. The threshold was never tuned on a held-out image: each fold used the value chosen on its own training images (0.60-0.67, see rule_study.md), the shipped model the value chosen on all nine (0.64).
86
+
87
+ ## Antibody − threshold (held-out matched nuclei: 1321, of which 94 negative)
88
+
89
+ Default threshold 0.5. Best Antibody − F1 in the sweep: 51.6% at threshold 0.90 (precision 52.2%, recall 51.1%); the full curve is in cv_report.md. The default was kept because the class is dominated by a single image (F99 holds 62 of the 110 negatives).
90
 
91
  ## What limits the scores
92
 
93
  * **Touching nuclei** are still the main source of IoU-0.5 misses (worst on F113, the densest image). The distance-map watershed of v2 recovers many clusters that v1 merged (v1 marker/threshold sweeps in `reports/cv/postprocess_sweep.json` had shown post-processing alone could not), but some clusters are still merged and a few nuclei are now split in two.
94
+ * **Annotation completeness**: most unmatched predictions overlap an annotated nucleus (outline disagreement); the rest are dimmer, smaller objects the annotator skipped, which the peak-distance rule of v3 halves (143 → 72 on the held-out images) at the price of 21 matched nuclei. Many faint red dots were likewise not annotated, which caps external-punctum precision.
95
  * **Antibody − is rare** (110 of 1569 nuclei, 62 in one image), and **Cell, Nostril, Antibody −** has five examples; their scores are not reliable. The class-balanced loss of v2 trades antibody-negative precision for recall.
96
  * Scores are from nine images of one experiment (35-day spinal cord, Iba1/TMEM119). New staining batches or magnifications need a check against a few annotated images.
97
 
 
99
 
100
  ```bash
101
  python evaluate.py cv --data-dir <folder with TIFs and CVAT zips> --runs-dir runs/cv_v2 --iters 3000
102
+ python scripts/rule_study.py --data-dir <folder> --runs-dir runs/cv_v2 --out runs/cv_v3/rule_study --weights weights/cellquant.pt
103
+ python evaluate.py cv --data-dir <folder> --runs-dir runs/cv_v3 --post-config runs/cv_v3/rule_study_post_config.json # fold_*.pt linked from runs/cv_v2
104
  python evaluate.py final --data-dir <folder> --weights weights/cellquant.pt
105
+ python scripts/compare_cv.py reports/cv_v2/cv_results.json runs/cv_v3/cv_results.json --names v2,v3 --out reports/cv_v3/comparison.md
106
+ python scripts/build_summary.py --cv-dir reports/cv_v3 --compare reports/cv_v2/cv_results.json --names v2,v3
107
  ```
reports/final/final_fit.json CHANGED
@@ -1,57 +1,70 @@
1
  {
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  "C100": {
3
  "cells": {
4
  "detection": {
5
- "tp": 173,
6
- "fp": 21,
7
- "fn": 5,
8
- "precision": 0.8917525773195877,
9
- "recall": 0.9719101123595506,
10
- "f1": 0.9301075268817205,
11
- "mean_iou_matched": 0.795722646954622,
12
  "iou30": {
13
- "tp": 177,
14
- "fp": 17,
15
- "fn": 1,
16
- "precision": 0.9123711340206185,
17
- "recall": 0.9943820224719101,
18
- "f1": 0.9516129032258065
19
  },
20
  "centroid": {
21
- "tp": 178,
22
- "fp": 16,
23
- "fn": 0,
24
- "precision": 0.9175257731958762,
25
- "recall": 1.0,
26
- "f1": 0.956989247311828
27
  }
28
  },
29
  "attribute_accuracy": {
30
- "antibody": 0.9884393063583815,
31
- "nostril": 0.9942196531791907,
32
- "both": 0.9826589595375722,
33
- "n_matched": 173
34
  },
35
  "per_class": {
36
  "Cell, Antibody +": {
37
- "tp": 158,
38
- "fp": 18,
39
- "fn": 7,
40
- "precision": 0.8977272727272727,
41
- "recall": 0.9575757575757575,
42
- "f1": 0.9266862170087976,
43
  "gt_count": 165,
44
- "pred_count": 176
45
  },
46
  "Cell, Antibody -": {
47
  "tp": 0,
48
- "fp": 6,
49
  "fn": 0,
50
  "precision": 0.0,
51
  "recall": 0.0,
52
  "f1": 0.0,
53
  "gt_count": 0,
54
- "pred_count": 6
55
  },
56
  "Cell, Nostril, Antibody +": {
57
  "tp": 12,
@@ -76,8 +89,8 @@
76
  },
77
  "confusion": {
78
  "Cell, Antibody +": {
79
- "Cell, Antibody +": 158,
80
- "Cell, Antibody -": 2,
81
  "Cell, Nostril, Antibody +": 0,
82
  "Cell, Nostril, Antibody -": 0
83
  },
@@ -101,7 +114,7 @@
101
  }
102
  },
103
  "n_gt": 178,
104
- "n_pred": 194
105
  },
106
  "nostrils": {
107
  "detection": {
@@ -142,14 +155,14 @@
142
  "counts": {
143
  "Cell, Antibody +": {
144
  "gt": 165,
145
- "pred": 176,
146
- "abs_err": 11,
147
- "rel_err": 0.06666666666666667
148
  },
149
  "Cell, Antibody -": {
150
  "gt": 0,
151
- "pred": 6,
152
- "abs_err": 6,
153
  "rel_err": Infinity
154
  },
155
  "Cell, Nostril, Antibody +": {
@@ -180,20 +193,20 @@
180
  "fp_analysis": null,
181
  "pairs": null,
182
  "summary": {
183
- "cell_det_f1": 0.9301075268817205,
184
- "cell_det_precision": 0.8917525773195877,
185
- "cell_det_recall": 0.9719101123595506,
186
- "cell_mean_iou": 0.795722646954622,
187
- "cell_det_f1_iou30": 0.9516129032258065,
188
- "cell_det_f1_centroid": 0.956989247311828,
189
- "cell_attr_antibody": 0.9884393063583815,
190
- "cell_attr_nostril": 0.9942196531791907,
191
- "cell_attr_both": 0.9826589595375722,
192
  "nos_det_f1": 0.8000000000000002,
193
  "nos_det_precision": 0.8,
194
  "nos_det_recall": 0.8,
195
  "nos_type_acc": 1.0,
196
- "f1[Cell, Antibody +]": 0.9266862170087976,
197
  "f1[Cell, Antibody -]": 0.0,
198
  "f1[Cell, Nostril, Antibody +]": 0.9600000000000001,
199
  "f1[Cell, Nostril, Antibody -]": 0.0,
@@ -205,27 +218,27 @@
205
  "cells": {
206
  "detection": {
207
  "tp": 91,
208
- "fp": 17,
209
  "fn": 0,
210
- "precision": 0.8425925925925926,
211
  "recall": 1.0,
212
- "f1": 0.914572864321608,
213
  "mean_iou_matched": 0.8036863197012898,
214
  "iou30": {
215
  "tp": 91,
216
- "fp": 17,
217
  "fn": 0,
218
- "precision": 0.8425925925925926,
219
  "recall": 1.0,
220
- "f1": 0.914572864321608
221
  },
222
  "centroid": {
223
  "tp": 91,
224
- "fp": 17,
225
  "fn": 0,
226
- "precision": 0.8425925925925926,
227
  "recall": 1.0,
228
- "f1": 0.914572864321608
229
  }
230
  },
231
  "attribute_accuracy": {
@@ -237,33 +250,33 @@
237
  "per_class": {
238
  "Cell, Antibody +": {
239
  "tp": 41,
240
- "fp": 17,
241
  "fn": 0,
242
- "precision": 0.7068965517241379,
243
  "recall": 1.0,
244
- "f1": 0.8282828282828283,
245
  "gt_count": 41,
246
- "pred_count": 58
247
  },
248
  "Cell, Antibody -": {
249
  "tp": 0,
250
- "fp": 1,
251
  "fn": 0,
252
  "precision": 0.0,
253
  "recall": 0.0,
254
  "f1": 0.0,
255
  "gt_count": 0,
256
- "pred_count": 1
257
  },
258
  "Cell, Nostril, Antibody +": {
259
  "tp": 45,
260
- "fp": 4,
261
  "fn": 5,
262
- "precision": 0.9183673469387755,
263
  "recall": 0.9,
264
- "f1": 0.9090909090909091,
265
  "gt_count": 50,
266
- "pred_count": 49
267
  },
268
  "Cell, Nostril, Antibody -": {
269
  "tp": 0,
@@ -303,7 +316,7 @@
303
  }
304
  },
305
  "n_gt": 91,
306
- "n_pred": 108
307
  },
308
  "nostrils": {
309
  "detection": {
@@ -344,21 +357,21 @@
344
  "counts": {
345
  "Cell, Antibody +": {
346
  "gt": 41,
347
- "pred": 58,
348
- "abs_err": 17,
349
- "rel_err": 0.4146341463414634
350
  },
351
  "Cell, Antibody -": {
352
  "gt": 0,
353
- "pred": 1,
354
- "abs_err": 1,
355
- "rel_err": Infinity
356
  },
357
  "Cell, Nostril, Antibody +": {
358
  "gt": 50,
359
- "pred": 49,
360
- "abs_err": 1,
361
- "rel_err": 0.02
362
  },
363
  "Cell, Nostril, Antibody -": {
364
  "gt": 0,
@@ -382,12 +395,12 @@
382
  "fp_analysis": null,
383
  "pairs": null,
384
  "summary": {
385
- "cell_det_f1": 0.914572864321608,
386
- "cell_det_precision": 0.8425925925925926,
387
  "cell_det_recall": 1.0,
388
  "cell_mean_iou": 0.8036863197012898,
389
- "cell_det_f1_iou30": 0.914572864321608,
390
- "cell_det_f1_centroid": 0.914572864321608,
391
  "cell_attr_antibody": 1.0,
392
  "cell_attr_nostril": 0.945054945054945,
393
  "cell_attr_both": 0.945054945054945,
@@ -395,9 +408,9 @@
395
  "nos_det_precision": 0.6910112359550562,
396
  "nos_det_recall": 0.917910447761194,
397
  "nos_type_acc": 0.991869918699187,
398
- "f1[Cell, Antibody +]": 0.8282828282828283,
399
  "f1[Cell, Antibody -]": 0.0,
400
- "f1[Cell, Nostril, Antibody +]": 0.9090909090909091,
401
  "f1[Cell, Nostril, Antibody -]": 0.0,
402
  "f1[Nostril in Nucleus]": 0.8503937007874016,
403
  "f1[Nostril External of Nucleus]": 0.7351351351351352
@@ -406,46 +419,46 @@
406
  "C110": {
407
  "cells": {
408
  "detection": {
409
- "tp": 168,
410
- "fp": 34,
411
- "fn": 16,
412
- "precision": 0.8316831683168316,
413
- "recall": 0.9130434782608695,
414
- "f1": 0.8704663212435233,
415
- "mean_iou_matched": 0.7832369871445182,
416
  "iou30": {
417
- "tp": 178,
418
- "fp": 24,
419
- "fn": 6,
420
- "precision": 0.8811881188118812,
421
- "recall": 0.967391304347826,
422
- "f1": 0.922279792746114
423
  },
424
  "centroid": {
425
- "tp": 180,
426
- "fp": 22,
427
- "fn": 4,
428
- "precision": 0.8910891089108911,
429
- "recall": 0.9782608695652174,
430
- "f1": 0.9326424870466321
431
  }
432
  },
433
  "attribute_accuracy": {
434
- "antibody": 0.9940476190476191,
435
- "nostril": 0.9702380952380952,
436
- "both": 0.9642857142857143,
437
- "n_matched": 168
438
  },
439
  "per_class": {
440
  "Cell, Antibody +": {
441
  "tp": 52,
442
- "fp": 23,
443
  "fn": 9,
444
- "precision": 0.6933333333333334,
445
  "recall": 0.8524590163934426,
446
- "f1": 0.7647058823529411,
447
  "gt_count": 61,
448
- "pred_count": 75
449
  },
450
  "Cell, Antibody -": {
451
  "tp": 1,
@@ -458,14 +471,14 @@
458
  "pred_count": 2
459
  },
460
  "Cell, Nostril, Antibody +": {
461
- "tp": 109,
462
- "fp": 16,
463
- "fn": 13,
464
- "precision": 0.872,
465
- "recall": 0.8934426229508197,
466
- "f1": 0.8825910931174089,
467
  "gt_count": 122,
468
- "pred_count": 125
469
  },
470
  "Cell, Nostril, Antibody -": {
471
  "tp": 0,
@@ -494,7 +507,7 @@
494
  "Cell, Nostril, Antibody +": {
495
  "Cell, Antibody +": 3,
496
  "Cell, Antibody -": 0,
497
- "Cell, Nostril, Antibody +": 109,
498
  "Cell, Nostril, Antibody -": 0
499
  },
500
  "Cell, Nostril, Antibody -": {
@@ -505,7 +518,7 @@
505
  }
506
  },
507
  "n_gt": 184,
508
- "n_pred": 202
509
  },
510
  "nostrils": {
511
  "detection": {
@@ -546,9 +559,9 @@
546
  "counts": {
547
  "Cell, Antibody +": {
548
  "gt": 61,
549
- "pred": 75,
550
- "abs_err": 14,
551
- "rel_err": 0.22950819672131148
552
  },
553
  "Cell, Antibody -": {
554
  "gt": 1,
@@ -558,7 +571,7 @@
558
  },
559
  "Cell, Nostril, Antibody +": {
560
  "gt": 122,
561
- "pred": 125,
562
  "abs_err": 3,
563
  "rel_err": 0.02459016393442623
564
  },
@@ -584,22 +597,22 @@
584
  "fp_analysis": null,
585
  "pairs": null,
586
  "summary": {
587
- "cell_det_f1": 0.8704663212435233,
588
- "cell_det_precision": 0.8316831683168316,
589
- "cell_det_recall": 0.9130434782608695,
590
- "cell_mean_iou": 0.7832369871445182,
591
- "cell_det_f1_iou30": 0.922279792746114,
592
- "cell_det_f1_centroid": 0.9326424870466321,
593
- "cell_attr_antibody": 0.9940476190476191,
594
- "cell_attr_nostril": 0.9702380952380952,
595
- "cell_attr_both": 0.9642857142857143,
596
  "nos_det_f1": 0.8391777509068925,
597
  "nos_det_precision": 0.7694013303769401,
598
  "nos_det_recall": 0.9228723404255319,
599
  "nos_type_acc": 0.9740634005763689,
600
- "f1[Cell, Antibody +]": 0.7647058823529411,
601
  "f1[Cell, Antibody -]": 0.6666666666666666,
602
- "f1[Cell, Nostril, Antibody +]": 0.8825910931174089,
603
  "f1[Cell, Nostril, Antibody -]": 0.0,
604
  "f1[Nostril in Nucleus]": 0.8324022346368716,
605
  "f1[Nostril External of Nucleus]": 0.8059701492537313
@@ -608,66 +621,66 @@
608
  "C112": {
609
  "cells": {
610
  "detection": {
611
- "tp": 130,
612
- "fp": 49,
613
- "fn": 16,
614
- "precision": 0.7262569832402235,
615
- "recall": 0.8904109589041096,
616
- "f1": 0.8,
617
- "mean_iou_matched": 0.7378347860378052,
618
  "iou30": {
619
- "tp": 141,
620
- "fp": 38,
621
- "fn": 5,
622
- "precision": 0.7877094972067039,
623
- "recall": 0.9657534246575342,
624
- "f1": 0.8676923076923078
625
  },
626
  "centroid": {
627
- "tp": 143,
628
- "fp": 36,
629
- "fn": 3,
630
- "precision": 0.7988826815642458,
631
- "recall": 0.9794520547945206,
632
- "f1": 0.88
633
  }
634
  },
635
  "attribute_accuracy": {
636
  "antibody": 1.0,
637
- "nostril": 0.9923076923076923,
638
- "both": 0.9923076923076923,
639
- "n_matched": 130
640
  },
641
  "per_class": {
642
  "Cell, Antibody +": {
643
- "tp": 109,
644
- "fp": 43,
645
- "fn": 15,
646
- "precision": 0.7171052631578947,
647
- "recall": 0.8790322580645161,
648
- "f1": 0.7898550724637682,
649
  "gt_count": 124,
650
- "pred_count": 152
651
  },
652
  "Cell, Antibody -": {
653
  "tp": 5,
654
- "fp": 1,
655
  "fn": 0,
656
- "precision": 0.8333333333333334,
657
  "recall": 1.0,
658
- "f1": 0.9090909090909091,
659
  "gt_count": 5,
660
- "pred_count": 6
661
  },
662
  "Cell, Nostril, Antibody +": {
663
  "tp": 15,
664
- "fp": 6,
665
  "fn": 2,
666
- "precision": 0.7142857142857143,
667
  "recall": 0.8823529411764706,
668
- "f1": 0.7894736842105262,
669
  "gt_count": 17,
670
- "pred_count": 21
671
  },
672
  "Cell, Nostril, Antibody -": {
673
  "tp": 0,
@@ -682,7 +695,7 @@
682
  },
683
  "confusion": {
684
  "Cell, Antibody +": {
685
- "Cell, Antibody +": 109,
686
  "Cell, Antibody -": 0,
687
  "Cell, Nostril, Antibody +": 1,
688
  "Cell, Nostril, Antibody -": 0
@@ -707,7 +720,7 @@
707
  }
708
  },
709
  "n_gt": 146,
710
- "n_pred": 179
711
  },
712
  "nostrils": {
713
  "detection": {
@@ -748,21 +761,21 @@
748
  "counts": {
749
  "Cell, Antibody +": {
750
  "gt": 124,
751
- "pred": 152,
752
- "abs_err": 28,
753
- "rel_err": 0.22580645161290322
754
  },
755
  "Cell, Antibody -": {
756
  "gt": 5,
757
- "pred": 6,
758
- "abs_err": 1,
759
- "rel_err": 0.2
760
  },
761
  "Cell, Nostril, Antibody +": {
762
  "gt": 17,
763
- "pred": 21,
764
- "abs_err": 4,
765
- "rel_err": 0.23529411764705882
766
  },
767
  "Cell, Nostril, Antibody -": {
768
  "gt": 0,
@@ -786,22 +799,22 @@
786
  "fp_analysis": null,
787
  "pairs": null,
788
  "summary": {
789
- "cell_det_f1": 0.8,
790
- "cell_det_precision": 0.7262569832402235,
791
- "cell_det_recall": 0.8904109589041096,
792
- "cell_mean_iou": 0.7378347860378052,
793
- "cell_det_f1_iou30": 0.8676923076923078,
794
- "cell_det_f1_centroid": 0.88,
795
  "cell_attr_antibody": 1.0,
796
- "cell_attr_nostril": 0.9923076923076923,
797
- "cell_attr_both": 0.9923076923076923,
798
  "nos_det_f1": 0.819672131147541,
799
  "nos_det_precision": 0.6944444444444444,
800
  "nos_det_recall": 1.0,
801
  "nos_type_acc": 1.0,
802
- "f1[Cell, Antibody +]": 0.7898550724637682,
803
- "f1[Cell, Antibody -]": 0.9090909090909091,
804
- "f1[Cell, Nostril, Antibody +]": 0.7894736842105262,
805
  "f1[Cell, Nostril, Antibody -]": 0.0,
806
  "f1[Nostril in Nucleus]": 0.92,
807
  "f1[Nostril External of Nucleus]": 0.3636363636363636
@@ -811,27 +824,27 @@
811
  "cells": {
812
  "detection": {
813
  "tp": 172,
814
- "fp": 30,
815
  "fn": 14,
816
- "precision": 0.8514851485148515,
817
  "recall": 0.9247311827956989,
818
- "f1": 0.8865979381443299,
819
  "mean_iou_matched": 0.7905866698793166,
820
  "iou30": {
821
- "tp": 182,
822
- "fp": 20,
823
- "fn": 4,
824
- "precision": 0.900990099009901,
825
- "recall": 0.978494623655914,
826
- "f1": 0.9381443298969072
827
  },
828
  "centroid": {
829
- "tp": 180,
830
- "fp": 22,
831
- "fn": 6,
832
- "precision": 0.8910891089108911,
833
- "recall": 0.967741935483871,
834
- "f1": 0.9278350515463919
835
  }
836
  },
837
  "attribute_accuracy": {
@@ -843,23 +856,23 @@
843
  "per_class": {
844
  "Cell, Antibody +": {
845
  "tp": 80,
846
- "fp": 26,
847
  "fn": 7,
848
- "precision": 0.7547169811320755,
849
  "recall": 0.9195402298850575,
850
- "f1": 0.8290155440414508,
851
  "gt_count": 87,
852
- "pred_count": 106
853
  },
854
  "Cell, Antibody -": {
855
  "tp": 5,
856
- "fp": 6,
857
  "fn": 1,
858
- "precision": 0.45454545454545453,
859
  "recall": 0.8333333333333334,
860
- "f1": 0.5882352941176471,
861
  "gt_count": 6,
862
- "pred_count": 11
863
  },
864
  "Cell, Nostril, Antibody +": {
865
  "tp": 78,
@@ -909,7 +922,7 @@
909
  }
910
  },
911
  "n_gt": 186,
912
- "n_pred": 202
913
  },
914
  "nostrils": {
915
  "detection": {
@@ -950,15 +963,15 @@
950
  "counts": {
951
  "Cell, Antibody +": {
952
  "gt": 87,
953
- "pred": 106,
954
- "abs_err": 19,
955
- "rel_err": 0.21839080459770116
956
  },
957
  "Cell, Antibody -": {
958
  "gt": 6,
959
- "pred": 11,
960
- "abs_err": 5,
961
- "rel_err": 0.8333333333333334
962
  },
963
  "Cell, Nostril, Antibody +": {
964
  "gt": 93,
@@ -988,12 +1001,12 @@
988
  "fp_analysis": null,
989
  "pairs": null,
990
  "summary": {
991
- "cell_det_f1": 0.8865979381443299,
992
- "cell_det_precision": 0.8514851485148515,
993
  "cell_det_recall": 0.9247311827956989,
994
  "cell_mean_iou": 0.7905866698793166,
995
- "cell_det_f1_iou30": 0.9381443298969072,
996
- "cell_det_f1_centroid": 0.9278350515463919,
997
  "cell_attr_antibody": 0.9941860465116279,
998
  "cell_attr_nostril": 0.9534883720930233,
999
  "cell_attr_both": 0.9476744186046512,
@@ -1001,8 +1014,8 @@
1001
  "nos_det_precision": 0.9171974522292994,
1002
  "nos_det_recall": 0.8888888888888888,
1003
  "nos_type_acc": 0.9930555555555556,
1004
- "f1[Cell, Antibody +]": 0.8290155440414508,
1005
- "f1[Cell, Antibody -]": 0.5882352941176471,
1006
  "f1[Cell, Nostril, Antibody +]": 0.8764044943820225,
1007
  "f1[Cell, Nostril, Antibody -]": 0.0,
1008
  "f1[Nostril in Nucleus]": 0.8803418803418803,
@@ -1012,66 +1025,66 @@
1012
  "F110": {
1013
  "cells": {
1014
  "detection": {
1015
- "tp": 122,
1016
- "fp": 22,
1017
- "fn": 6,
1018
- "precision": 0.8472222222222222,
1019
- "recall": 0.953125,
1020
- "f1": 0.8970588235294118,
1021
- "mean_iou_matched": 0.7899681957823826,
1022
  "iou30": {
1023
- "tp": 125,
1024
- "fp": 19,
1025
- "fn": 3,
1026
- "precision": 0.8680555555555556,
1027
- "recall": 0.9765625,
1028
- "f1": 0.9191176470588235
1029
  },
1030
  "centroid": {
1031
- "tp": 126,
1032
- "fp": 18,
1033
- "fn": 2,
1034
- "precision": 0.875,
1035
- "recall": 0.984375,
1036
- "f1": 0.9264705882352942
1037
  }
1038
  },
1039
  "attribute_accuracy": {
1040
  "antibody": 1.0,
1041
- "nostril": 0.9754098360655737,
1042
- "both": 0.9754098360655737,
1043
- "n_matched": 122
1044
  },
1045
  "per_class": {
1046
  "Cell, Antibody +": {
1047
- "tp": 85,
1048
- "fp": 15,
1049
- "fn": 2,
1050
- "precision": 0.85,
1051
- "recall": 0.9770114942528736,
1052
- "f1": 0.9090909090909092,
1053
  "gt_count": 87,
1054
- "pred_count": 100
1055
  },
1056
  "Cell, Antibody -": {
1057
  "tp": 6,
1058
- "fp": 5,
1059
  "fn": 1,
1060
- "precision": 0.5454545454545454,
1061
  "recall": 0.8571428571428571,
1062
- "f1": 0.6666666666666665,
1063
  "gt_count": 7,
1064
- "pred_count": 11
1065
  },
1066
  "Cell, Nostril, Antibody +": {
1067
- "tp": 28,
1068
- "fp": 5,
1069
- "fn": 5,
1070
- "precision": 0.8484848484848485,
1071
- "recall": 0.8484848484848485,
1072
- "f1": 0.8484848484848486,
1073
  "gt_count": 33,
1074
- "pred_count": 33
1075
  },
1076
  "Cell, Nostril, Antibody -": {
1077
  "tp": 0,
@@ -1086,7 +1099,7 @@
1086
  },
1087
  "confusion": {
1088
  "Cell, Antibody +": {
1089
- "Cell, Antibody +": 85,
1090
  "Cell, Antibody -": 0,
1091
  "Cell, Nostril, Antibody +": 0,
1092
  "Cell, Nostril, Antibody -": 0
@@ -1100,7 +1113,7 @@
1100
  "Cell, Nostril, Antibody +": {
1101
  "Cell, Antibody +": 3,
1102
  "Cell, Antibody -": 0,
1103
- "Cell, Nostril, Antibody +": 28,
1104
  "Cell, Nostril, Antibody -": 0
1105
  },
1106
  "Cell, Nostril, Antibody -": {
@@ -1111,7 +1124,7 @@
1111
  }
1112
  },
1113
  "n_gt": 128,
1114
- "n_pred": 144
1115
  },
1116
  "nostrils": {
1117
  "detection": {
@@ -1152,21 +1165,21 @@
1152
  "counts": {
1153
  "Cell, Antibody +": {
1154
  "gt": 87,
1155
- "pred": 100,
1156
- "abs_err": 13,
1157
- "rel_err": 0.14942528735632185
1158
  },
1159
  "Cell, Antibody -": {
1160
  "gt": 7,
1161
- "pred": 11,
1162
- "abs_err": 4,
1163
- "rel_err": 0.5714285714285714
1164
  },
1165
  "Cell, Nostril, Antibody +": {
1166
  "gt": 33,
1167
- "pred": 33,
1168
- "abs_err": 0,
1169
- "rel_err": 0.0
1170
  },
1171
  "Cell, Nostril, Antibody -": {
1172
  "gt": 1,
@@ -1190,22 +1203,22 @@
1190
  "fp_analysis": null,
1191
  "pairs": null,
1192
  "summary": {
1193
- "cell_det_f1": 0.8970588235294118,
1194
- "cell_det_precision": 0.8472222222222222,
1195
- "cell_det_recall": 0.953125,
1196
- "cell_mean_iou": 0.7899681957823826,
1197
- "cell_det_f1_iou30": 0.9191176470588235,
1198
- "cell_det_f1_centroid": 0.9264705882352942,
1199
  "cell_attr_antibody": 1.0,
1200
- "cell_attr_nostril": 0.9754098360655737,
1201
- "cell_attr_both": 0.9754098360655737,
1202
  "nos_det_f1": 0.8864864864864865,
1203
  "nos_det_precision": 0.9318181818181818,
1204
  "nos_det_recall": 0.845360824742268,
1205
  "nos_type_acc": 0.9878048780487805,
1206
- "f1[Cell, Antibody +]": 0.9090909090909092,
1207
- "f1[Cell, Antibody -]": 0.6666666666666665,
1208
- "f1[Cell, Nostril, Antibody +]": 0.8484848484848486,
1209
  "f1[Cell, Nostril, Antibody -]": 0.0,
1210
  "f1[Nostril in Nucleus]": 0.8518518518518519,
1211
  "f1[Nostril External of Nucleus]": 0.8854961832061069
@@ -1214,46 +1227,46 @@
1214
  "F113": {
1215
  "cells": {
1216
  "detection": {
1217
- "tp": 140,
1218
- "fp": 27,
1219
- "fn": 23,
1220
- "precision": 0.8383233532934131,
1221
- "recall": 0.8588957055214724,
1222
- "f1": 0.8484848484848485,
1223
- "mean_iou_matched": 0.7555449040647428,
1224
  "iou30": {
1225
- "tp": 155,
1226
- "fp": 12,
1227
- "fn": 8,
1228
- "precision": 0.9281437125748503,
1229
- "recall": 0.950920245398773,
1230
- "f1": 0.9393939393939393
1231
  },
1232
  "centroid": {
1233
- "tp": 153,
1234
- "fp": 14,
1235
- "fn": 10,
1236
- "precision": 0.9161676646706587,
1237
- "recall": 0.9386503067484663,
1238
- "f1": 0.9272727272727274
1239
  }
1240
  },
1241
  "attribute_accuracy": {
1242
  "antibody": 1.0,
1243
- "nostril": 0.9785714285714285,
1244
- "both": 0.9785714285714285,
1245
- "n_matched": 140
1246
  },
1247
  "per_class": {
1248
  "Cell, Antibody +": {
1249
- "tp": 117,
1250
- "fp": 26,
1251
- "fn": 21,
1252
- "precision": 0.8181818181818182,
1253
- "recall": 0.8478260869565217,
1254
- "f1": 0.8327402135231317,
1255
  "gt_count": 138,
1256
- "pred_count": 143
1257
  },
1258
  "Cell, Antibody -": {
1259
  "tp": 2,
@@ -1266,14 +1279,14 @@
1266
  "pred_count": 3
1267
  },
1268
  "Cell, Nostril, Antibody +": {
1269
- "tp": 18,
1270
- "fp": 3,
1271
- "fn": 4,
1272
- "precision": 0.8571428571428571,
1273
- "recall": 0.8181818181818182,
1274
- "f1": 0.8372093023255814,
1275
  "gt_count": 22,
1276
- "pred_count": 21
1277
  },
1278
  "Cell, Nostril, Antibody -": {
1279
  "tp": 0,
@@ -1288,7 +1301,7 @@
1288
  },
1289
  "confusion": {
1290
  "Cell, Antibody +": {
1291
- "Cell, Antibody +": 117,
1292
  "Cell, Antibody -": 0,
1293
  "Cell, Nostril, Antibody +": 0,
1294
  "Cell, Nostril, Antibody -": 0
@@ -1302,7 +1315,7 @@
1302
  "Cell, Nostril, Antibody +": {
1303
  "Cell, Antibody +": 3,
1304
  "Cell, Antibody -": 0,
1305
- "Cell, Nostril, Antibody +": 18,
1306
  "Cell, Nostril, Antibody -": 0
1307
  },
1308
  "Cell, Nostril, Antibody -": {
@@ -1313,7 +1326,7 @@
1313
  }
1314
  },
1315
  "n_gt": 163,
1316
- "n_pred": 167
1317
  },
1318
  "nostrils": {
1319
  "detection": {
@@ -1354,9 +1367,9 @@
1354
  "counts": {
1355
  "Cell, Antibody +": {
1356
  "gt": 138,
1357
- "pred": 143,
1358
- "abs_err": 5,
1359
- "rel_err": 0.036231884057971016
1360
  },
1361
  "Cell, Antibody -": {
1362
  "gt": 3,
@@ -1366,9 +1379,9 @@
1366
  },
1367
  "Cell, Nostril, Antibody +": {
1368
  "gt": 22,
1369
- "pred": 21,
1370
- "abs_err": 1,
1371
- "rel_err": 0.045454545454545456
1372
  },
1373
  "Cell, Nostril, Antibody -": {
1374
  "gt": 0,
@@ -1392,22 +1405,22 @@
1392
  "fp_analysis": null,
1393
  "pairs": null,
1394
  "summary": {
1395
- "cell_det_f1": 0.8484848484848485,
1396
- "cell_det_precision": 0.8383233532934131,
1397
- "cell_det_recall": 0.8588957055214724,
1398
- "cell_mean_iou": 0.7555449040647428,
1399
- "cell_det_f1_iou30": 0.9393939393939393,
1400
- "cell_det_f1_centroid": 0.9272727272727274,
1401
  "cell_attr_antibody": 1.0,
1402
- "cell_attr_nostril": 0.9785714285714285,
1403
- "cell_attr_both": 0.9785714285714285,
1404
  "nos_det_f1": 0.9206349206349206,
1405
  "nos_det_precision": 1.0,
1406
  "nos_det_recall": 0.8529411764705882,
1407
  "nos_type_acc": 0.9310344827586207,
1408
- "f1[Cell, Antibody +]": 0.8327402135231317,
1409
  "f1[Cell, Antibody -]": 0.6666666666666666,
1410
- "f1[Cell, Nostril, Antibody +]": 0.8372093023255814,
1411
  "f1[Cell, Nostril, Antibody -]": 0.0,
1412
  "f1[Nostril in Nucleus]": 0.8979591836734695,
1413
  "f1[Nostril External of Nucleus]": 0.7142857142857143
@@ -1416,66 +1429,66 @@
1416
  "F114": {
1417
  "cells": {
1418
  "detection": {
1419
- "tp": 226,
1420
- "fp": 30,
1421
- "fn": 16,
1422
- "precision": 0.8828125,
1423
- "recall": 0.9338842975206612,
1424
- "f1": 0.9076305220883534,
1425
- "mean_iou_matched": 0.8136301605298976,
1426
  "iou30": {
1427
- "tp": 236,
1428
- "fp": 20,
1429
- "fn": 6,
1430
- "precision": 0.921875,
1431
- "recall": 0.9752066115702479,
1432
- "f1": 0.9477911646586346
1433
  },
1434
  "centroid": {
1435
- "tp": 236,
1436
- "fp": 20,
1437
- "fn": 6,
1438
- "precision": 0.921875,
1439
- "recall": 0.9752066115702479,
1440
- "f1": 0.9477911646586346
1441
  }
1442
  },
1443
  "attribute_accuracy": {
1444
- "antibody": 0.9911504424778761,
1445
- "nostril": 0.9601769911504425,
1446
- "both": 0.9513274336283186,
1447
- "n_matched": 226
1448
  },
1449
  "per_class": {
1450
  "Cell, Antibody +": {
1451
- "tp": 157,
1452
- "fp": 20,
1453
- "fn": 18,
1454
- "precision": 0.8870056497175142,
1455
- "recall": 0.8971428571428571,
1456
- "f1": 0.8920454545454545,
1457
  "gt_count": 175,
1458
- "pred_count": 177
1459
  },
1460
  "Cell, Antibody -": {
1461
- "tp": 21,
1462
- "fp": 9,
1463
- "fn": 0,
1464
- "precision": 0.7,
1465
- "recall": 1.0,
1466
- "f1": 0.8235294117647058,
1467
  "gt_count": 21,
1468
- "pred_count": 30
1469
  },
1470
  "Cell, Nostril, Antibody +": {
1471
  "tp": 36,
1472
- "fp": 12,
1473
  "fn": 6,
1474
- "precision": 0.75,
1475
  "recall": 0.8571428571428571,
1476
- "f1": 0.7999999999999999,
1477
  "gt_count": 42,
1478
- "pred_count": 48
1479
  },
1480
  "Cell, Nostril, Antibody -": {
1481
  "tp": 1,
@@ -1490,14 +1503,14 @@
1490
  },
1491
  "confusion": {
1492
  "Cell, Antibody +": {
1493
- "Cell, Antibody +": 157,
1494
  "Cell, Antibody -": 2,
1495
  "Cell, Nostril, Antibody +": 5,
1496
  "Cell, Nostril, Antibody -": 0
1497
  },
1498
  "Cell, Antibody -": {
1499
  "Cell, Antibody +": 0,
1500
- "Cell, Antibody -": 21,
1501
  "Cell, Nostril, Antibody +": 0,
1502
  "Cell, Nostril, Antibody -": 0
1503
  },
@@ -1515,7 +1528,7 @@
1515
  }
1516
  },
1517
  "n_gt": 242,
1518
- "n_pred": 256
1519
  },
1520
  "nostrils": {
1521
  "detection": {
@@ -1556,21 +1569,21 @@
1556
  "counts": {
1557
  "Cell, Antibody +": {
1558
  "gt": 175,
1559
- "pred": 177,
1560
- "abs_err": 2,
1561
- "rel_err": 0.011428571428571429
1562
  },
1563
  "Cell, Antibody -": {
1564
  "gt": 21,
1565
- "pred": 30,
1566
- "abs_err": 9,
1567
- "rel_err": 0.42857142857142855
1568
  },
1569
  "Cell, Nostril, Antibody +": {
1570
  "gt": 42,
1571
- "pred": 48,
1572
- "abs_err": 6,
1573
- "rel_err": 0.14285714285714285
1574
  },
1575
  "Cell, Nostril, Antibody -": {
1576
  "gt": 4,
@@ -1594,22 +1607,22 @@
1594
  "fp_analysis": null,
1595
  "pairs": null,
1596
  "summary": {
1597
- "cell_det_f1": 0.9076305220883534,
1598
- "cell_det_precision": 0.8828125,
1599
- "cell_det_recall": 0.9338842975206612,
1600
- "cell_mean_iou": 0.8136301605298976,
1601
- "cell_det_f1_iou30": 0.9477911646586346,
1602
- "cell_det_f1_centroid": 0.9477911646586346,
1603
- "cell_attr_antibody": 0.9911504424778761,
1604
- "cell_attr_nostril": 0.9601769911504425,
1605
- "cell_attr_both": 0.9513274336283186,
1606
  "nos_det_f1": 0.8252427184466019,
1607
  "nos_det_precision": 0.7327586206896551,
1608
  "nos_det_recall": 0.9444444444444444,
1609
  "nos_type_acc": 1.0,
1610
- "f1[Cell, Antibody +]": 0.8920454545454545,
1611
- "f1[Cell, Antibody -]": 0.8235294117647058,
1612
- "f1[Cell, Nostril, Antibody +]": 0.7999999999999999,
1613
  "f1[Cell, Nostril, Antibody -]": 0.4,
1614
  "f1[Nostril in Nucleus]": 0.8648648648648649,
1615
  "f1[Nostril External of Nucleus]": 0.7789473684210527
@@ -1618,56 +1631,56 @@
1618
  "F99": {
1619
  "cells": {
1620
  "detection": {
1621
- "tp": 241,
1622
- "fp": 35,
1623
- "fn": 10,
1624
- "precision": 0.8731884057971014,
1625
- "recall": 0.9601593625498008,
1626
- "f1": 0.9146110056925996,
1627
- "mean_iou_matched": 0.8180266624759959,
1628
  "iou30": {
1629
- "tp": 247,
1630
- "fp": 29,
1631
- "fn": 4,
1632
- "precision": 0.894927536231884,
1633
- "recall": 0.9840637450199203,
1634
- "f1": 0.937381404174573
1635
  },
1636
  "centroid": {
1637
- "tp": 246,
1638
- "fp": 30,
1639
- "fn": 5,
1640
- "precision": 0.8913043478260869,
1641
- "recall": 0.9800796812749004,
1642
- "f1": 0.9335863377609108
1643
  }
1644
  },
1645
  "attribute_accuracy": {
1646
- "antibody": 0.979253112033195,
1647
- "nostril": 0.991701244813278,
1648
- "both": 0.970954356846473,
1649
- "n_matched": 241
1650
  },
1651
  "per_class": {
1652
  "Cell, Antibody +": {
1653
- "tp": 173,
1654
- "fp": 26,
1655
- "fn": 10,
1656
- "precision": 0.8693467336683417,
1657
- "recall": 0.9453551912568307,
1658
- "f1": 0.9057591623036649,
1659
  "gt_count": 183,
1660
- "pred_count": 199
1661
  },
1662
  "Cell, Antibody -": {
1663
- "tp": 57,
1664
- "fp": 16,
1665
- "fn": 5,
1666
- "precision": 0.7808219178082192,
1667
- "recall": 0.9193548387096774,
1668
- "f1": 0.8444444444444444,
1669
  "gt_count": 62,
1670
- "pred_count": 73
1671
  },
1672
  "Cell, Nostril, Antibody +": {
1673
  "tp": 4,
@@ -1692,14 +1705,14 @@
1692
  },
1693
  "confusion": {
1694
  "Cell, Antibody +": {
1695
- "Cell, Antibody +": 173,
1696
  "Cell, Antibody -": 4,
1697
  "Cell, Nostril, Antibody +": 0,
1698
  "Cell, Nostril, Antibody -": 0
1699
  },
1700
  "Cell, Antibody -": {
1701
  "Cell, Antibody +": 1,
1702
- "Cell, Antibody -": 57,
1703
  "Cell, Nostril, Antibody +": 0,
1704
  "Cell, Nostril, Antibody -": 0
1705
  },
@@ -1717,7 +1730,7 @@
1717
  }
1718
  },
1719
  "n_gt": 251,
1720
- "n_pred": 276
1721
  },
1722
  "nostrils": {
1723
  "detection": {
@@ -1758,15 +1771,15 @@
1758
  "counts": {
1759
  "Cell, Antibody +": {
1760
  "gt": 183,
1761
- "pred": 199,
1762
- "abs_err": 16,
1763
- "rel_err": 0.08743169398907104
1764
  },
1765
  "Cell, Antibody -": {
1766
  "gt": 62,
1767
- "pred": 73,
1768
- "abs_err": 11,
1769
- "rel_err": 0.1774193548387097
1770
  },
1771
  "Cell, Nostril, Antibody +": {
1772
  "gt": 6,
@@ -1796,21 +1809,21 @@
1796
  "fp_analysis": null,
1797
  "pairs": null,
1798
  "summary": {
1799
- "cell_det_f1": 0.9146110056925996,
1800
- "cell_det_precision": 0.8731884057971014,
1801
- "cell_det_recall": 0.9601593625498008,
1802
- "cell_mean_iou": 0.8180266624759959,
1803
- "cell_det_f1_iou30": 0.937381404174573,
1804
- "cell_det_f1_centroid": 0.9335863377609108,
1805
- "cell_attr_antibody": 0.979253112033195,
1806
- "cell_attr_nostril": 0.991701244813278,
1807
- "cell_attr_both": 0.970954356846473,
1808
  "nos_det_f1": 0.9090909090909091,
1809
  "nos_det_precision": 0.9090909090909091,
1810
  "nos_det_recall": 0.9090909090909091,
1811
  "nos_type_acc": 1.0,
1812
- "f1[Cell, Antibody +]": 0.9057591623036649,
1813
- "f1[Cell, Antibody -]": 0.8444444444444444,
1814
  "f1[Cell, Nostril, Antibody +]": 0.8,
1815
  "f1[Cell, Nostril, Antibody -]": 0.0,
1816
  "f1[Nostril in Nucleus]": 0.8,
 
1
  {
2
+ "post_config": {
3
+ "cell_fg_thr": 0.5,
4
+ "marker_thr": 0.5,
5
+ "min_marker_area": 12,
6
+ "min_cell_area": 50,
7
+ "min_peak_dist": 0.64,
8
+ "nos_fg_thr": 0.5,
9
+ "min_nos_area": 3,
10
+ "antibody_thr": 0.5,
11
+ "cellnos_thr": 0.5,
12
+ "nostril_rule": "head",
13
+ "cellnos_rule": "contains"
14
+ },
15
  "C100": {
16
  "cells": {
17
  "detection": {
18
+ "tp": 171,
19
+ "fp": 14,
20
+ "fn": 7,
21
+ "precision": 0.9243243243243243,
22
+ "recall": 0.9606741573033708,
23
+ "f1": 0.9421487603305785,
24
+ "mean_iou_matched": 0.7972938750523493,
25
  "iou30": {
26
+ "tp": 175,
27
+ "fp": 10,
28
+ "fn": 3,
29
+ "precision": 0.9459459459459459,
30
+ "recall": 0.9831460674157303,
31
+ "f1": 0.9641873278236914
32
  },
33
  "centroid": {
34
+ "tp": 175,
35
+ "fp": 10,
36
+ "fn": 3,
37
+ "precision": 0.9459459459459459,
38
+ "recall": 0.9831460674157303,
39
+ "f1": 0.9641873278236914
40
  }
41
  },
42
  "attribute_accuracy": {
43
+ "antibody": 0.9941520467836257,
44
+ "nostril": 0.9941520467836257,
45
+ "both": 0.9883040935672515,
46
+ "n_matched": 171
47
  },
48
  "per_class": {
49
  "Cell, Antibody +": {
50
+ "tp": 157,
51
+ "fp": 13,
52
+ "fn": 8,
53
+ "precision": 0.9235294117647059,
54
+ "recall": 0.9515151515151515,
55
+ "f1": 0.9373134328358209,
56
  "gt_count": 165,
57
+ "pred_count": 170
58
  },
59
  "Cell, Antibody -": {
60
  "tp": 0,
61
+ "fp": 3,
62
  "fn": 0,
63
  "precision": 0.0,
64
  "recall": 0.0,
65
  "f1": 0.0,
66
  "gt_count": 0,
67
+ "pred_count": 3
68
  },
69
  "Cell, Nostril, Antibody +": {
70
  "tp": 12,
 
89
  },
90
  "confusion": {
91
  "Cell, Antibody +": {
92
+ "Cell, Antibody +": 157,
93
+ "Cell, Antibody -": 1,
94
  "Cell, Nostril, Antibody +": 0,
95
  "Cell, Nostril, Antibody -": 0
96
  },
 
114
  }
115
  },
116
  "n_gt": 178,
117
+ "n_pred": 185
118
  },
119
  "nostrils": {
120
  "detection": {
 
155
  "counts": {
156
  "Cell, Antibody +": {
157
  "gt": 165,
158
+ "pred": 170,
159
+ "abs_err": 5,
160
+ "rel_err": 0.030303030303030304
161
  },
162
  "Cell, Antibody -": {
163
  "gt": 0,
164
+ "pred": 3,
165
+ "abs_err": 3,
166
  "rel_err": Infinity
167
  },
168
  "Cell, Nostril, Antibody +": {
 
193
  "fp_analysis": null,
194
  "pairs": null,
195
  "summary": {
196
+ "cell_det_f1": 0.9421487603305785,
197
+ "cell_det_precision": 0.9243243243243243,
198
+ "cell_det_recall": 0.9606741573033708,
199
+ "cell_mean_iou": 0.7972938750523493,
200
+ "cell_det_f1_iou30": 0.9641873278236914,
201
+ "cell_det_f1_centroid": 0.9641873278236914,
202
+ "cell_attr_antibody": 0.9941520467836257,
203
+ "cell_attr_nostril": 0.9941520467836257,
204
+ "cell_attr_both": 0.9883040935672515,
205
  "nos_det_f1": 0.8000000000000002,
206
  "nos_det_precision": 0.8,
207
  "nos_det_recall": 0.8,
208
  "nos_type_acc": 1.0,
209
+ "f1[Cell, Antibody +]": 0.9373134328358209,
210
  "f1[Cell, Antibody -]": 0.0,
211
  "f1[Cell, Nostril, Antibody +]": 0.9600000000000001,
212
  "f1[Cell, Nostril, Antibody -]": 0.0,
 
218
  "cells": {
219
  "detection": {
220
  "tp": 91,
221
+ "fp": 10,
222
  "fn": 0,
223
+ "precision": 0.900990099009901,
224
  "recall": 1.0,
225
+ "f1": 0.9479166666666667,
226
  "mean_iou_matched": 0.8036863197012898,
227
  "iou30": {
228
  "tp": 91,
229
+ "fp": 10,
230
  "fn": 0,
231
+ "precision": 0.900990099009901,
232
  "recall": 1.0,
233
+ "f1": 0.9479166666666667
234
  },
235
  "centroid": {
236
  "tp": 91,
237
+ "fp": 10,
238
  "fn": 0,
239
+ "precision": 0.900990099009901,
240
  "recall": 1.0,
241
+ "f1": 0.9479166666666667
242
  }
243
  },
244
  "attribute_accuracy": {
 
250
  "per_class": {
251
  "Cell, Antibody +": {
252
  "tp": 41,
253
+ "fp": 13,
254
  "fn": 0,
255
+ "precision": 0.7592592592592593,
256
  "recall": 1.0,
257
+ "f1": 0.8631578947368421,
258
  "gt_count": 41,
259
+ "pred_count": 54
260
  },
261
  "Cell, Antibody -": {
262
  "tp": 0,
263
+ "fp": 0,
264
  "fn": 0,
265
  "precision": 0.0,
266
  "recall": 0.0,
267
  "f1": 0.0,
268
  "gt_count": 0,
269
+ "pred_count": 0
270
  },
271
  "Cell, Nostril, Antibody +": {
272
  "tp": 45,
273
+ "fp": 2,
274
  "fn": 5,
275
+ "precision": 0.9574468085106383,
276
  "recall": 0.9,
277
+ "f1": 0.9278350515463918,
278
  "gt_count": 50,
279
+ "pred_count": 47
280
  },
281
  "Cell, Nostril, Antibody -": {
282
  "tp": 0,
 
316
  }
317
  },
318
  "n_gt": 91,
319
+ "n_pred": 101
320
  },
321
  "nostrils": {
322
  "detection": {
 
357
  "counts": {
358
  "Cell, Antibody +": {
359
  "gt": 41,
360
+ "pred": 54,
361
+ "abs_err": 13,
362
+ "rel_err": 0.3170731707317073
363
  },
364
  "Cell, Antibody -": {
365
  "gt": 0,
366
+ "pred": 0,
367
+ "abs_err": 0,
368
+ "rel_err": 0.0
369
  },
370
  "Cell, Nostril, Antibody +": {
371
  "gt": 50,
372
+ "pred": 47,
373
+ "abs_err": 3,
374
+ "rel_err": 0.06
375
  },
376
  "Cell, Nostril, Antibody -": {
377
  "gt": 0,
 
395
  "fp_analysis": null,
396
  "pairs": null,
397
  "summary": {
398
+ "cell_det_f1": 0.9479166666666667,
399
+ "cell_det_precision": 0.900990099009901,
400
  "cell_det_recall": 1.0,
401
  "cell_mean_iou": 0.8036863197012898,
402
+ "cell_det_f1_iou30": 0.9479166666666667,
403
+ "cell_det_f1_centroid": 0.9479166666666667,
404
  "cell_attr_antibody": 1.0,
405
  "cell_attr_nostril": 0.945054945054945,
406
  "cell_attr_both": 0.945054945054945,
 
408
  "nos_det_precision": 0.6910112359550562,
409
  "nos_det_recall": 0.917910447761194,
410
  "nos_type_acc": 0.991869918699187,
411
+ "f1[Cell, Antibody +]": 0.8631578947368421,
412
  "f1[Cell, Antibody -]": 0.0,
413
+ "f1[Cell, Nostril, Antibody +]": 0.9278350515463918,
414
  "f1[Cell, Nostril, Antibody -]": 0.0,
415
  "f1[Nostril in Nucleus]": 0.8503937007874016,
416
  "f1[Nostril External of Nucleus]": 0.7351351351351352
 
419
  "C110": {
420
  "cells": {
421
  "detection": {
422
+ "tp": 167,
423
+ "fp": 18,
424
+ "fn": 17,
425
+ "precision": 0.9027027027027027,
426
+ "recall": 0.907608695652174,
427
+ "f1": 0.9051490514905148,
428
+ "mean_iou_matched": 0.7834847507494777,
429
  "iou30": {
430
+ "tp": 174,
431
+ "fp": 11,
432
+ "fn": 10,
433
+ "precision": 0.9405405405405406,
434
+ "recall": 0.9456521739130435,
435
+ "f1": 0.9430894308943089
436
  },
437
  "centroid": {
438
+ "tp": 175,
439
+ "fp": 10,
440
+ "fn": 9,
441
+ "precision": 0.9459459459459459,
442
+ "recall": 0.9510869565217391,
443
+ "f1": 0.948509485094851
444
  }
445
  },
446
  "attribute_accuracy": {
447
+ "antibody": 0.9940119760479041,
448
+ "nostril": 0.9700598802395209,
449
+ "both": 0.9640718562874252,
450
+ "n_matched": 167
451
  },
452
  "per_class": {
453
  "Cell, Antibody +": {
454
  "tp": 52,
455
+ "fp": 12,
456
  "fn": 9,
457
+ "precision": 0.8125,
458
  "recall": 0.8524590163934426,
459
+ "f1": 0.8319999999999999,
460
  "gt_count": 61,
461
+ "pred_count": 64
462
  },
463
  "Cell, Antibody -": {
464
  "tp": 1,
 
471
  "pred_count": 2
472
  },
473
  "Cell, Nostril, Antibody +": {
474
+ "tp": 108,
475
+ "fp": 11,
476
+ "fn": 14,
477
+ "precision": 0.907563025210084,
478
+ "recall": 0.8852459016393442,
479
+ "f1": 0.8962655601659751,
480
  "gt_count": 122,
481
+ "pred_count": 119
482
  },
483
  "Cell, Nostril, Antibody -": {
484
  "tp": 0,
 
507
  "Cell, Nostril, Antibody +": {
508
  "Cell, Antibody +": 3,
509
  "Cell, Antibody -": 0,
510
+ "Cell, Nostril, Antibody +": 108,
511
  "Cell, Nostril, Antibody -": 0
512
  },
513
  "Cell, Nostril, Antibody -": {
 
518
  }
519
  },
520
  "n_gt": 184,
521
+ "n_pred": 185
522
  },
523
  "nostrils": {
524
  "detection": {
 
559
  "counts": {
560
  "Cell, Antibody +": {
561
  "gt": 61,
562
+ "pred": 64,
563
+ "abs_err": 3,
564
+ "rel_err": 0.04918032786885246
565
  },
566
  "Cell, Antibody -": {
567
  "gt": 1,
 
571
  },
572
  "Cell, Nostril, Antibody +": {
573
  "gt": 122,
574
+ "pred": 119,
575
  "abs_err": 3,
576
  "rel_err": 0.02459016393442623
577
  },
 
597
  "fp_analysis": null,
598
  "pairs": null,
599
  "summary": {
600
+ "cell_det_f1": 0.9051490514905148,
601
+ "cell_det_precision": 0.9027027027027027,
602
+ "cell_det_recall": 0.907608695652174,
603
+ "cell_mean_iou": 0.7834847507494777,
604
+ "cell_det_f1_iou30": 0.9430894308943089,
605
+ "cell_det_f1_centroid": 0.948509485094851,
606
+ "cell_attr_antibody": 0.9940119760479041,
607
+ "cell_attr_nostril": 0.9700598802395209,
608
+ "cell_attr_both": 0.9640718562874252,
609
  "nos_det_f1": 0.8391777509068925,
610
  "nos_det_precision": 0.7694013303769401,
611
  "nos_det_recall": 0.9228723404255319,
612
  "nos_type_acc": 0.9740634005763689,
613
+ "f1[Cell, Antibody +]": 0.8319999999999999,
614
  "f1[Cell, Antibody -]": 0.6666666666666666,
615
+ "f1[Cell, Nostril, Antibody +]": 0.8962655601659751,
616
  "f1[Cell, Nostril, Antibody -]": 0.0,
617
  "f1[Nostril in Nucleus]": 0.8324022346368716,
618
  "f1[Nostril External of Nucleus]": 0.8059701492537313
 
621
  "C112": {
622
  "cells": {
623
  "detection": {
624
+ "tp": 128,
625
+ "fp": 28,
626
+ "fn": 18,
627
+ "precision": 0.8205128205128205,
628
+ "recall": 0.8767123287671232,
629
+ "f1": 0.847682119205298,
630
+ "mean_iou_matched": 0.7399381730547632,
631
  "iou30": {
632
+ "tp": 137,
633
+ "fp": 19,
634
+ "fn": 9,
635
+ "precision": 0.8782051282051282,
636
+ "recall": 0.9383561643835616,
637
+ "f1": 0.9072847682119205
638
  },
639
  "centroid": {
640
+ "tp": 138,
641
+ "fp": 18,
642
+ "fn": 8,
643
+ "precision": 0.8846153846153846,
644
+ "recall": 0.9452054794520548,
645
+ "f1": 0.9139072847682119
646
  }
647
  },
648
  "attribute_accuracy": {
649
  "antibody": 1.0,
650
+ "nostril": 0.9921875,
651
+ "both": 0.9921875,
652
+ "n_matched": 128
653
  },
654
  "per_class": {
655
  "Cell, Antibody +": {
656
+ "tp": 107,
657
+ "fp": 24,
658
+ "fn": 17,
659
+ "precision": 0.816793893129771,
660
+ "recall": 0.8629032258064516,
661
+ "f1": 0.8392156862745098,
662
  "gt_count": 124,
663
+ "pred_count": 131
664
  },
665
  "Cell, Antibody -": {
666
  "tp": 5,
667
+ "fp": 0,
668
  "fn": 0,
669
+ "precision": 1.0,
670
  "recall": 1.0,
671
+ "f1": 1.0,
672
  "gt_count": 5,
673
+ "pred_count": 5
674
  },
675
  "Cell, Nostril, Antibody +": {
676
  "tp": 15,
677
+ "fp": 5,
678
  "fn": 2,
679
+ "precision": 0.75,
680
  "recall": 0.8823529411764706,
681
+ "f1": 0.8108108108108107,
682
  "gt_count": 17,
683
+ "pred_count": 20
684
  },
685
  "Cell, Nostril, Antibody -": {
686
  "tp": 0,
 
695
  },
696
  "confusion": {
697
  "Cell, Antibody +": {
698
+ "Cell, Antibody +": 107,
699
  "Cell, Antibody -": 0,
700
  "Cell, Nostril, Antibody +": 1,
701
  "Cell, Nostril, Antibody -": 0
 
720
  }
721
  },
722
  "n_gt": 146,
723
+ "n_pred": 156
724
  },
725
  "nostrils": {
726
  "detection": {
 
761
  "counts": {
762
  "Cell, Antibody +": {
763
  "gt": 124,
764
+ "pred": 131,
765
+ "abs_err": 7,
766
+ "rel_err": 0.056451612903225805
767
  },
768
  "Cell, Antibody -": {
769
  "gt": 5,
770
+ "pred": 5,
771
+ "abs_err": 0,
772
+ "rel_err": 0.0
773
  },
774
  "Cell, Nostril, Antibody +": {
775
  "gt": 17,
776
+ "pred": 20,
777
+ "abs_err": 3,
778
+ "rel_err": 0.17647058823529413
779
  },
780
  "Cell, Nostril, Antibody -": {
781
  "gt": 0,
 
799
  "fp_analysis": null,
800
  "pairs": null,
801
  "summary": {
802
+ "cell_det_f1": 0.847682119205298,
803
+ "cell_det_precision": 0.8205128205128205,
804
+ "cell_det_recall": 0.8767123287671232,
805
+ "cell_mean_iou": 0.7399381730547632,
806
+ "cell_det_f1_iou30": 0.9072847682119205,
807
+ "cell_det_f1_centroid": 0.9139072847682119,
808
  "cell_attr_antibody": 1.0,
809
+ "cell_attr_nostril": 0.9921875,
810
+ "cell_attr_both": 0.9921875,
811
  "nos_det_f1": 0.819672131147541,
812
  "nos_det_precision": 0.6944444444444444,
813
  "nos_det_recall": 1.0,
814
  "nos_type_acc": 1.0,
815
+ "f1[Cell, Antibody +]": 0.8392156862745098,
816
+ "f1[Cell, Antibody -]": 1.0,
817
+ "f1[Cell, Nostril, Antibody +]": 0.8108108108108107,
818
  "f1[Cell, Nostril, Antibody -]": 0.0,
819
  "f1[Nostril in Nucleus]": 0.92,
820
  "f1[Nostril External of Nucleus]": 0.3636363636363636
 
824
  "cells": {
825
  "detection": {
826
  "tp": 172,
827
+ "fp": 24,
828
  "fn": 14,
829
+ "precision": 0.8775510204081632,
830
  "recall": 0.9247311827956989,
831
+ "f1": 0.900523560209424,
832
  "mean_iou_matched": 0.7905866698793166,
833
  "iou30": {
834
+ "tp": 181,
835
+ "fp": 15,
836
+ "fn": 5,
837
+ "precision": 0.923469387755102,
838
+ "recall": 0.9731182795698925,
839
+ "f1": 0.9476439790575916
840
  },
841
  "centroid": {
842
+ "tp": 179,
843
+ "fp": 17,
844
+ "fn": 7,
845
+ "precision": 0.9132653061224489,
846
+ "recall": 0.9623655913978495,
847
+ "f1": 0.9371727748691099
848
  }
849
  },
850
  "attribute_accuracy": {
 
856
  "per_class": {
857
  "Cell, Antibody +": {
858
  "tp": 80,
859
+ "fp": 21,
860
  "fn": 7,
861
+ "precision": 0.7920792079207921,
862
  "recall": 0.9195402298850575,
863
+ "f1": 0.851063829787234,
864
  "gt_count": 87,
865
+ "pred_count": 101
866
  },
867
  "Cell, Antibody -": {
868
  "tp": 5,
869
+ "fp": 5,
870
  "fn": 1,
871
+ "precision": 0.5,
872
  "recall": 0.8333333333333334,
873
+ "f1": 0.625,
874
  "gt_count": 6,
875
+ "pred_count": 10
876
  },
877
  "Cell, Nostril, Antibody +": {
878
  "tp": 78,
 
922
  }
923
  },
924
  "n_gt": 186,
925
+ "n_pred": 196
926
  },
927
  "nostrils": {
928
  "detection": {
 
963
  "counts": {
964
  "Cell, Antibody +": {
965
  "gt": 87,
966
+ "pred": 101,
967
+ "abs_err": 14,
968
+ "rel_err": 0.16091954022988506
969
  },
970
  "Cell, Antibody -": {
971
  "gt": 6,
972
+ "pred": 10,
973
+ "abs_err": 4,
974
+ "rel_err": 0.6666666666666666
975
  },
976
  "Cell, Nostril, Antibody +": {
977
  "gt": 93,
 
1001
  "fp_analysis": null,
1002
  "pairs": null,
1003
  "summary": {
1004
+ "cell_det_f1": 0.900523560209424,
1005
+ "cell_det_precision": 0.8775510204081632,
1006
  "cell_det_recall": 0.9247311827956989,
1007
  "cell_mean_iou": 0.7905866698793166,
1008
+ "cell_det_f1_iou30": 0.9476439790575916,
1009
+ "cell_det_f1_centroid": 0.9371727748691099,
1010
  "cell_attr_antibody": 0.9941860465116279,
1011
  "cell_attr_nostril": 0.9534883720930233,
1012
  "cell_attr_both": 0.9476744186046512,
 
1014
  "nos_det_precision": 0.9171974522292994,
1015
  "nos_det_recall": 0.8888888888888888,
1016
  "nos_type_acc": 0.9930555555555556,
1017
+ "f1[Cell, Antibody +]": 0.851063829787234,
1018
+ "f1[Cell, Antibody -]": 0.625,
1019
  "f1[Cell, Nostril, Antibody +]": 0.8764044943820225,
1020
  "f1[Cell, Nostril, Antibody -]": 0.0,
1021
  "f1[Nostril in Nucleus]": 0.8803418803418803,
 
1025
  "F110": {
1026
  "cells": {
1027
  "detection": {
1028
+ "tp": 119,
1029
+ "fp": 12,
1030
+ "fn": 9,
1031
+ "precision": 0.9083969465648855,
1032
+ "recall": 0.9296875,
1033
+ "f1": 0.918918918918919,
1034
+ "mean_iou_matched": 0.79252398616283,
1035
  "iou30": {
1036
+ "tp": 122,
1037
+ "fp": 9,
1038
+ "fn": 6,
1039
+ "precision": 0.9312977099236641,
1040
+ "recall": 0.953125,
1041
+ "f1": 0.9420849420849421
1042
  },
1043
  "centroid": {
1044
+ "tp": 123,
1045
+ "fp": 8,
1046
+ "fn": 5,
1047
+ "precision": 0.9389312977099237,
1048
+ "recall": 0.9609375,
1049
+ "f1": 0.9498069498069499
1050
  }
1051
  },
1052
  "attribute_accuracy": {
1053
  "antibody": 1.0,
1054
+ "nostril": 0.9747899159663865,
1055
+ "both": 0.9747899159663865,
1056
+ "n_matched": 119
1057
  },
1058
  "per_class": {
1059
  "Cell, Antibody +": {
1060
+ "tp": 83,
1061
+ "fp": 9,
1062
+ "fn": 4,
1063
+ "precision": 0.9021739130434783,
1064
+ "recall": 0.9540229885057471,
1065
+ "f1": 0.9273743016759777,
1066
  "gt_count": 87,
1067
+ "pred_count": 92
1068
  },
1069
  "Cell, Antibody -": {
1070
  "tp": 6,
1071
+ "fp": 2,
1072
  "fn": 1,
1073
+ "precision": 0.75,
1074
  "recall": 0.8571428571428571,
1075
+ "f1": 0.7999999999999999,
1076
  "gt_count": 7,
1077
+ "pred_count": 8
1078
  },
1079
  "Cell, Nostril, Antibody +": {
1080
+ "tp": 27,
1081
+ "fp": 4,
1082
+ "fn": 6,
1083
+ "precision": 0.8709677419354839,
1084
+ "recall": 0.8181818181818182,
1085
+ "f1": 0.84375,
1086
  "gt_count": 33,
1087
+ "pred_count": 31
1088
  },
1089
  "Cell, Nostril, Antibody -": {
1090
  "tp": 0,
 
1099
  },
1100
  "confusion": {
1101
  "Cell, Antibody +": {
1102
+ "Cell, Antibody +": 83,
1103
  "Cell, Antibody -": 0,
1104
  "Cell, Nostril, Antibody +": 0,
1105
  "Cell, Nostril, Antibody -": 0
 
1113
  "Cell, Nostril, Antibody +": {
1114
  "Cell, Antibody +": 3,
1115
  "Cell, Antibody -": 0,
1116
+ "Cell, Nostril, Antibody +": 27,
1117
  "Cell, Nostril, Antibody -": 0
1118
  },
1119
  "Cell, Nostril, Antibody -": {
 
1124
  }
1125
  },
1126
  "n_gt": 128,
1127
+ "n_pred": 131
1128
  },
1129
  "nostrils": {
1130
  "detection": {
 
1165
  "counts": {
1166
  "Cell, Antibody +": {
1167
  "gt": 87,
1168
+ "pred": 92,
1169
+ "abs_err": 5,
1170
+ "rel_err": 0.05747126436781609
1171
  },
1172
  "Cell, Antibody -": {
1173
  "gt": 7,
1174
+ "pred": 8,
1175
+ "abs_err": 1,
1176
+ "rel_err": 0.14285714285714285
1177
  },
1178
  "Cell, Nostril, Antibody +": {
1179
  "gt": 33,
1180
+ "pred": 31,
1181
+ "abs_err": 2,
1182
+ "rel_err": 0.06060606060606061
1183
  },
1184
  "Cell, Nostril, Antibody -": {
1185
  "gt": 1,
 
1203
  "fp_analysis": null,
1204
  "pairs": null,
1205
  "summary": {
1206
+ "cell_det_f1": 0.918918918918919,
1207
+ "cell_det_precision": 0.9083969465648855,
1208
+ "cell_det_recall": 0.9296875,
1209
+ "cell_mean_iou": 0.79252398616283,
1210
+ "cell_det_f1_iou30": 0.9420849420849421,
1211
+ "cell_det_f1_centroid": 0.9498069498069499,
1212
  "cell_attr_antibody": 1.0,
1213
+ "cell_attr_nostril": 0.9747899159663865,
1214
+ "cell_attr_both": 0.9747899159663865,
1215
  "nos_det_f1": 0.8864864864864865,
1216
  "nos_det_precision": 0.9318181818181818,
1217
  "nos_det_recall": 0.845360824742268,
1218
  "nos_type_acc": 0.9878048780487805,
1219
+ "f1[Cell, Antibody +]": 0.9273743016759777,
1220
+ "f1[Cell, Antibody -]": 0.7999999999999999,
1221
+ "f1[Cell, Nostril, Antibody +]": 0.84375,
1222
  "f1[Cell, Nostril, Antibody -]": 0.0,
1223
  "f1[Nostril in Nucleus]": 0.8518518518518519,
1224
  "f1[Nostril External of Nucleus]": 0.8854961832061069
 
1227
  "F113": {
1228
  "cells": {
1229
  "detection": {
1230
+ "tp": 135,
1231
+ "fp": 19,
1232
+ "fn": 28,
1233
+ "precision": 0.8766233766233766,
1234
+ "recall": 0.8282208588957055,
1235
+ "f1": 0.8517350157728707,
1236
+ "mean_iou_matched": 0.7577631927559012,
1237
  "iou30": {
1238
+ "tp": 148,
1239
+ "fp": 6,
1240
+ "fn": 15,
1241
+ "precision": 0.961038961038961,
1242
+ "recall": 0.9079754601226994,
1243
+ "f1": 0.9337539432176657
1244
  },
1245
  "centroid": {
1246
+ "tp": 146,
1247
+ "fp": 8,
1248
+ "fn": 17,
1249
+ "precision": 0.948051948051948,
1250
+ "recall": 0.8957055214723927,
1251
+ "f1": 0.9211356466876971
1252
  }
1253
  },
1254
  "attribute_accuracy": {
1255
  "antibody": 1.0,
1256
+ "nostril": 0.9777777777777777,
1257
+ "both": 0.9777777777777777,
1258
+ "n_matched": 135
1259
  },
1260
  "per_class": {
1261
  "Cell, Antibody +": {
1262
+ "tp": 113,
1263
+ "fp": 19,
1264
+ "fn": 25,
1265
+ "precision": 0.8560606060606061,
1266
+ "recall": 0.8188405797101449,
1267
+ "f1": 0.837037037037037,
1268
  "gt_count": 138,
1269
+ "pred_count": 132
1270
  },
1271
  "Cell, Antibody -": {
1272
  "tp": 2,
 
1279
  "pred_count": 3
1280
  },
1281
  "Cell, Nostril, Antibody +": {
1282
+ "tp": 17,
1283
+ "fp": 2,
1284
+ "fn": 5,
1285
+ "precision": 0.8947368421052632,
1286
+ "recall": 0.7727272727272727,
1287
+ "f1": 0.8292682926829269,
1288
  "gt_count": 22,
1289
+ "pred_count": 19
1290
  },
1291
  "Cell, Nostril, Antibody -": {
1292
  "tp": 0,
 
1301
  },
1302
  "confusion": {
1303
  "Cell, Antibody +": {
1304
+ "Cell, Antibody +": 113,
1305
  "Cell, Antibody -": 0,
1306
  "Cell, Nostril, Antibody +": 0,
1307
  "Cell, Nostril, Antibody -": 0
 
1315
  "Cell, Nostril, Antibody +": {
1316
  "Cell, Antibody +": 3,
1317
  "Cell, Antibody -": 0,
1318
+ "Cell, Nostril, Antibody +": 17,
1319
  "Cell, Nostril, Antibody -": 0
1320
  },
1321
  "Cell, Nostril, Antibody -": {
 
1326
  }
1327
  },
1328
  "n_gt": 163,
1329
+ "n_pred": 154
1330
  },
1331
  "nostrils": {
1332
  "detection": {
 
1367
  "counts": {
1368
  "Cell, Antibody +": {
1369
  "gt": 138,
1370
+ "pred": 132,
1371
+ "abs_err": 6,
1372
+ "rel_err": 0.043478260869565216
1373
  },
1374
  "Cell, Antibody -": {
1375
  "gt": 3,
 
1379
  },
1380
  "Cell, Nostril, Antibody +": {
1381
  "gt": 22,
1382
+ "pred": 19,
1383
+ "abs_err": 3,
1384
+ "rel_err": 0.13636363636363635
1385
  },
1386
  "Cell, Nostril, Antibody -": {
1387
  "gt": 0,
 
1405
  "fp_analysis": null,
1406
  "pairs": null,
1407
  "summary": {
1408
+ "cell_det_f1": 0.8517350157728707,
1409
+ "cell_det_precision": 0.8766233766233766,
1410
+ "cell_det_recall": 0.8282208588957055,
1411
+ "cell_mean_iou": 0.7577631927559012,
1412
+ "cell_det_f1_iou30": 0.9337539432176657,
1413
+ "cell_det_f1_centroid": 0.9211356466876971,
1414
  "cell_attr_antibody": 1.0,
1415
+ "cell_attr_nostril": 0.9777777777777777,
1416
+ "cell_attr_both": 0.9777777777777777,
1417
  "nos_det_f1": 0.9206349206349206,
1418
  "nos_det_precision": 1.0,
1419
  "nos_det_recall": 0.8529411764705882,
1420
  "nos_type_acc": 0.9310344827586207,
1421
+ "f1[Cell, Antibody +]": 0.837037037037037,
1422
  "f1[Cell, Antibody -]": 0.6666666666666666,
1423
+ "f1[Cell, Nostril, Antibody +]": 0.8292682926829269,
1424
  "f1[Cell, Nostril, Antibody -]": 0.0,
1425
  "f1[Nostril in Nucleus]": 0.8979591836734695,
1426
  "f1[Nostril External of Nucleus]": 0.7142857142857143
 
1429
  "F114": {
1430
  "cells": {
1431
  "detection": {
1432
+ "tp": 222,
1433
+ "fp": 20,
1434
+ "fn": 20,
1435
+ "precision": 0.9173553719008265,
1436
+ "recall": 0.9173553719008265,
1437
+ "f1": 0.9173553719008265,
1438
+ "mean_iou_matched": 0.8151069160963895,
1439
  "iou30": {
1440
+ "tp": 231,
1441
+ "fp": 11,
1442
+ "fn": 11,
1443
+ "precision": 0.9545454545454546,
1444
+ "recall": 0.9545454545454546,
1445
+ "f1": 0.9545454545454546
1446
  },
1447
  "centroid": {
1448
+ "tp": 231,
1449
+ "fp": 11,
1450
+ "fn": 11,
1451
+ "precision": 0.9545454545454546,
1452
+ "recall": 0.9545454545454546,
1453
+ "f1": 0.9545454545454546
1454
  }
1455
  },
1456
  "attribute_accuracy": {
1457
+ "antibody": 0.990990990990991,
1458
+ "nostril": 0.9594594594594594,
1459
+ "both": 0.9504504504504504,
1460
+ "n_matched": 222
1461
  },
1462
  "per_class": {
1463
  "Cell, Antibody +": {
1464
+ "tp": 154,
1465
+ "fp": 15,
1466
+ "fn": 21,
1467
+ "precision": 0.9112426035502958,
1468
+ "recall": 0.88,
1469
+ "f1": 0.8953488372093023,
1470
  "gt_count": 175,
1471
+ "pred_count": 169
1472
  },
1473
  "Cell, Antibody -": {
1474
+ "tp": 20,
1475
+ "fp": 5,
1476
+ "fn": 1,
1477
+ "precision": 0.8,
1478
+ "recall": 0.9523809523809523,
1479
+ "f1": 0.8695652173913043,
1480
  "gt_count": 21,
1481
+ "pred_count": 25
1482
  },
1483
  "Cell, Nostril, Antibody +": {
1484
  "tp": 36,
1485
+ "fp": 11,
1486
  "fn": 6,
1487
+ "precision": 0.7659574468085106,
1488
  "recall": 0.8571428571428571,
1489
+ "f1": 0.8089887640449439,
1490
  "gt_count": 42,
1491
+ "pred_count": 47
1492
  },
1493
  "Cell, Nostril, Antibody -": {
1494
  "tp": 1,
 
1503
  },
1504
  "confusion": {
1505
  "Cell, Antibody +": {
1506
+ "Cell, Antibody +": 154,
1507
  "Cell, Antibody -": 2,
1508
  "Cell, Nostril, Antibody +": 5,
1509
  "Cell, Nostril, Antibody -": 0
1510
  },
1511
  "Cell, Antibody -": {
1512
  "Cell, Antibody +": 0,
1513
+ "Cell, Antibody -": 20,
1514
  "Cell, Nostril, Antibody +": 0,
1515
  "Cell, Nostril, Antibody -": 0
1516
  },
 
1528
  }
1529
  },
1530
  "n_gt": 242,
1531
+ "n_pred": 242
1532
  },
1533
  "nostrils": {
1534
  "detection": {
 
1569
  "counts": {
1570
  "Cell, Antibody +": {
1571
  "gt": 175,
1572
+ "pred": 169,
1573
+ "abs_err": 6,
1574
+ "rel_err": 0.03428571428571429
1575
  },
1576
  "Cell, Antibody -": {
1577
  "gt": 21,
1578
+ "pred": 25,
1579
+ "abs_err": 4,
1580
+ "rel_err": 0.19047619047619047
1581
  },
1582
  "Cell, Nostril, Antibody +": {
1583
  "gt": 42,
1584
+ "pred": 47,
1585
+ "abs_err": 5,
1586
+ "rel_err": 0.11904761904761904
1587
  },
1588
  "Cell, Nostril, Antibody -": {
1589
  "gt": 4,
 
1607
  "fp_analysis": null,
1608
  "pairs": null,
1609
  "summary": {
1610
+ "cell_det_f1": 0.9173553719008265,
1611
+ "cell_det_precision": 0.9173553719008265,
1612
+ "cell_det_recall": 0.9173553719008265,
1613
+ "cell_mean_iou": 0.8151069160963895,
1614
+ "cell_det_f1_iou30": 0.9545454545454546,
1615
+ "cell_det_f1_centroid": 0.9545454545454546,
1616
+ "cell_attr_antibody": 0.990990990990991,
1617
+ "cell_attr_nostril": 0.9594594594594594,
1618
+ "cell_attr_both": 0.9504504504504504,
1619
  "nos_det_f1": 0.8252427184466019,
1620
  "nos_det_precision": 0.7327586206896551,
1621
  "nos_det_recall": 0.9444444444444444,
1622
  "nos_type_acc": 1.0,
1623
+ "f1[Cell, Antibody +]": 0.8953488372093023,
1624
+ "f1[Cell, Antibody -]": 0.8695652173913043,
1625
+ "f1[Cell, Nostril, Antibody +]": 0.8089887640449439,
1626
  "f1[Cell, Nostril, Antibody -]": 0.4,
1627
  "f1[Nostril in Nucleus]": 0.8648648648648649,
1628
  "f1[Nostril External of Nucleus]": 0.7789473684210527
 
1631
  "F99": {
1632
  "cells": {
1633
  "detection": {
1634
+ "tp": 237,
1635
+ "fp": 28,
1636
+ "fn": 14,
1637
+ "precision": 0.8943396226415095,
1638
+ "recall": 0.9442231075697212,
1639
+ "f1": 0.9186046511627908,
1640
+ "mean_iou_matched": 0.8196380690151054,
1641
  "iou30": {
1642
+ "tp": 243,
1643
+ "fp": 22,
1644
+ "fn": 8,
1645
+ "precision": 0.9169811320754717,
1646
+ "recall": 0.9681274900398407,
1647
+ "f1": 0.9418604651162791
1648
  },
1649
  "centroid": {
1650
+ "tp": 242,
1651
+ "fp": 23,
1652
+ "fn": 9,
1653
+ "precision": 0.9132075471698113,
1654
+ "recall": 0.9641434262948207,
1655
+ "f1": 0.937984496124031
1656
  }
1657
  },
1658
  "attribute_accuracy": {
1659
+ "antibody": 0.9789029535864979,
1660
+ "nostril": 0.9915611814345991,
1661
+ "both": 0.9704641350210971,
1662
+ "n_matched": 237
1663
  },
1664
  "per_class": {
1665
  "Cell, Antibody +": {
1666
+ "tp": 170,
1667
+ "fp": 24,
1668
+ "fn": 13,
1669
+ "precision": 0.8762886597938144,
1670
+ "recall": 0.9289617486338798,
1671
+ "f1": 0.9018567639257294,
1672
  "gt_count": 183,
1673
+ "pred_count": 194
1674
  },
1675
  "Cell, Antibody -": {
1676
+ "tp": 56,
1677
+ "fp": 11,
1678
+ "fn": 6,
1679
+ "precision": 0.835820895522388,
1680
+ "recall": 0.9032258064516129,
1681
+ "f1": 0.8682170542635659,
1682
  "gt_count": 62,
1683
+ "pred_count": 67
1684
  },
1685
  "Cell, Nostril, Antibody +": {
1686
  "tp": 4,
 
1705
  },
1706
  "confusion": {
1707
  "Cell, Antibody +": {
1708
+ "Cell, Antibody +": 170,
1709
  "Cell, Antibody -": 4,
1710
  "Cell, Nostril, Antibody +": 0,
1711
  "Cell, Nostril, Antibody -": 0
1712
  },
1713
  "Cell, Antibody -": {
1714
  "Cell, Antibody +": 1,
1715
+ "Cell, Antibody -": 56,
1716
  "Cell, Nostril, Antibody +": 0,
1717
  "Cell, Nostril, Antibody -": 0
1718
  },
 
1730
  }
1731
  },
1732
  "n_gt": 251,
1733
+ "n_pred": 265
1734
  },
1735
  "nostrils": {
1736
  "detection": {
 
1771
  "counts": {
1772
  "Cell, Antibody +": {
1773
  "gt": 183,
1774
+ "pred": 194,
1775
+ "abs_err": 11,
1776
+ "rel_err": 0.060109289617486336
1777
  },
1778
  "Cell, Antibody -": {
1779
  "gt": 62,
1780
+ "pred": 67,
1781
+ "abs_err": 5,
1782
+ "rel_err": 0.08064516129032258
1783
  },
1784
  "Cell, Nostril, Antibody +": {
1785
  "gt": 6,
 
1809
  "fp_analysis": null,
1810
  "pairs": null,
1811
  "summary": {
1812
+ "cell_det_f1": 0.9186046511627908,
1813
+ "cell_det_precision": 0.8943396226415095,
1814
+ "cell_det_recall": 0.9442231075697212,
1815
+ "cell_mean_iou": 0.8196380690151054,
1816
+ "cell_det_f1_iou30": 0.9418604651162791,
1817
+ "cell_det_f1_centroid": 0.937984496124031,
1818
+ "cell_attr_antibody": 0.9789029535864979,
1819
+ "cell_attr_nostril": 0.9915611814345991,
1820
+ "cell_attr_both": 0.9704641350210971,
1821
  "nos_det_f1": 0.9090909090909091,
1822
  "nos_det_precision": 0.9090909090909091,
1823
  "nos_det_recall": 0.9090909090909091,
1824
  "nos_type_acc": 1.0,
1825
+ "f1[Cell, Antibody +]": 0.9018567639257294,
1826
+ "f1[Cell, Antibody -]": 0.8682170542635659,
1827
  "f1[Cell, Nostril, Antibody +]": 0.8,
1828
  "f1[Cell, Nostril, Antibody -]": 0.0,
1829
  "f1[Nostril in Nucleus]": 0.8,
scripts/build_summary.py CHANGED
@@ -1,7 +1,7 @@
1
  #!/usr/bin/env python3
2
  """Write reports/SUMMARY.md from a cross-validation run and reports/final/final_fit.json.
3
 
4
- python scripts/build_summary.py [--cv-dir runs/cv_v2] [--recipe v2]
5
  """
6
  from __future__ import annotations
7
 
@@ -19,16 +19,29 @@ def pct(x: float) -> str:
19
  return f"{100 * x:.1f}%"
20
 
21
 
 
 
 
 
 
 
22
  def main(argv=None):
23
  ap = argparse.ArgumentParser()
24
  ap.add_argument("--cv-dir", default="runs/cv", help="folder holding cv_results.json of the shipped recipe")
25
  ap.add_argument("--compare", default="reports/cv/cv_results.json",
26
  help="cv_results.json of the previous recipe for the change table ('' = none)")
 
27
  a = ap.parse_args(argv)
 
28
  cv = json.load(open(os.path.join(ROOT, a.cv_dir, "cv_results.json")))
29
  prev = json.load(open(os.path.join(ROOT, a.compare)))["aggregate"] if a.compare and os.path.exists(os.path.join(ROOT, a.compare)) else None
30
  agg = cv["aggregate"]
31
  per = agg["per_image"]
 
 
 
 
 
32
  fit = json.load(open(os.path.join(ROOT, "reports", "final", "final_fit.json")))
33
  tot = agg["dataset_totals"]
34
  n_nuc = sum(tot[c] for c in CELL_LABELS)
@@ -41,12 +54,17 @@ def main(argv=None):
41
  "then a final model trained on all images with the same recipe. Each held-out image was tested 23 times: 3 identical runs "
42
  "(determinism), no-TTA / 4× / 8× test-time augmentation, 8 rotations and flips, 6 photometric perturbations, and 6 post-processing rule variants. "
43
  "The only choice made on held-out data is the rule that gives a nucleus the Nostril attribute: of three candidate rules, "
44
- "'contains an in-nucleus punctum' (the annotation protocol) scored best and is the default; see the rule-variant table in cv_report.md.", "",
45
- "Recipe (v2, shipped): ResNet34-UNet with a full-resolution stem; targets are a 3-px inner border ring (class weight 3), "
 
 
46
  "a per-nucleus normalised distance-to-outline map that the watershed uses as its landscape (markers = distance > 0.5), "
47
  "an antibody head whose loss up-weights antibody-negative pixels by sqrt(positive / negative pixels) (4.5 on the full set), "
48
- "and 25 % of training crops centred on an antibody-negative nucleus. v1 (reports/cv/) used a 2-px ring, the interior map as "
49
- "watershed landscape and an unweighted antibody loss; the change table below compares the two under the same protocol.", "",
 
 
 
50
  "## Headline numbers (micro-averaged over all held-out images, no TTA)", "",
51
  "| Measure | Precision | Recall | F1 |", "|---|---:|---:|---:|",
52
  f"| Nucleus detection, IoU ≥ 0.5 | {pct(det['cells']['precision'])} | {pct(det['cells']['recall'])} | {pct(det['cells']['f1'])} |",
@@ -74,8 +92,8 @@ def main(argv=None):
74
  L.append(f"| {k} | {pct(s['cell_det_f1'])} | {pct(s['cell_attr_both'])} | {pct(s['nos_det_f1'])} |")
75
  if prev:
76
  pd, pd8 = prev["detection_base"], prev["detection_tta8"]
77
- L += ["", "## Change from v1 (same protocol, same nine held-out images; `reports/cv_v2/comparison.md` has the full table)", "",
78
- "| Measure | v1 | v2 | Δ (points) |", "|---|---:|---:|---:|"]
79
  def row(name, x, y):
80
  L.append(f"| {name} | {pct(x)} | {pct(y)} | {100 * (y - x):+.1f} |")
81
  row("Nucleus F1, IoU ≥ 0.5", pd["cells"]["f1"], det["cells"]["f1"])
@@ -92,15 +110,19 @@ def main(argv=None):
92
  for k in sorted(per):
93
  if k in prev["per_image"]:
94
  row(f"Nuclei F1, {k} held out", prev["per_image"][k]["base"]["cell_det_f1"], per[k]["base"]["cell_det_f1"])
95
- n_pred_v1 = pd["cells"]["tp"] + pd["cells"]["fp"]
96
- n_pred_v2 = det["cells"]["tp"] + det["cells"]["fp"]
97
  n_gt = det["cells"]["tp"] + det["cells"]["fn"]
98
- L += ["", f"v2 misses far fewer nuclei ({pd['cells']['fn']} → {det['cells']['fn']} unmatched annotated nuclei, mostly touching nuclei that v1 "
99
- f"merged) at the cost of more unmatched predictions ({pd['cells']['fp']} → {det['cells']['fp']}): it predicts {n_pred_v2} nuclei "
100
- f"for {n_gt} annotated ones (v1: {n_pred_v1}). The extra predictions are dim objects the annotator skipped and outline/split "
101
- "disagreements in roughly the proportion shown in the FP table of cv_report.md. On the nuclei matched by both models the "
102
- "attribute accuracy differs by less than one point (see comparison.md), which is within the sampling error of ~1200 nuclei; "
103
- "the antibody-negative class gains recall (26 % → 38 % on matched nuclei) and loses precision (74 % → 59 %)."]
 
 
 
 
104
  sw = agg.get("antibody_threshold_sweep")
105
  if sw:
106
  best = max(sw["sweep"], key=lambda r: r["neg_f1"])
@@ -110,14 +132,16 @@ def main(argv=None):
110
  f"The default was kept because the class is dominated by a single image (F99 holds 62 of the {tot['Cell, Antibody -'] + tot['Cell, Nostril, Antibody -']} negatives)."]
111
  L += ["", "## What limits the scores", "",
112
  "* **Touching nuclei** are still the main source of IoU-0.5 misses (worst on F113, the densest image). The distance-map watershed of v2 recovers many clusters that v1 merged (v1 marker/threshold sweeps in `reports/cv/postprocess_sweep.json` had shown post-processing alone could not), but some clusters are still merged and a few nuclei are now split in two.",
113
- "* **Annotation completeness**: most unmatched predictions overlap an annotated nucleus (outline disagreement) and the rest are dimmer, smaller objects the annotator skipped; many faint red dots were likewise not annotated, which caps external-punctum precision. v2 detects more of these dim objects than v1, so it over-counts nuclei by about 13 % on the annotated images.",
114
  f"* **Antibody − is rare** ({tot['Cell, Antibody -'] + tot['Cell, Nostril, Antibody -']} of {n_nuc} nuclei, 62 in one image), and **Cell, Nostril, Antibody −** has five examples; their scores are not reliable. The class-balanced loss of v2 trades antibody-negative precision for recall.",
115
  "* Scores are from nine images of one experiment (35-day spinal cord, Iba1/TMEM119). New staining batches or magnifications need a check against a few annotated images.",
116
  "", "## Reproduce", "",
117
  "```bash", "python evaluate.py cv --data-dir <folder with TIFs and CVAT zips> --runs-dir runs/cv_v2 --iters 3000",
 
 
118
  "python evaluate.py final --data-dir <folder> --weights weights/cellquant.pt",
119
- "python scripts/compare_cv.py reports/cv/cv_results.json runs/cv_v2/cv_results.json --out reports/cv_v2/comparison.md",
120
- "python scripts/build_summary.py --cv-dir runs/cv_v2", "```", ""]
121
  out = os.path.join(ROOT, "reports", "SUMMARY.md")
122
  with open(out, "w") as f:
123
  f.write("\n".join(L))
 
1
  #!/usr/bin/env python3
2
  """Write reports/SUMMARY.md from a cross-validation run and reports/final/final_fit.json.
3
 
4
+ python scripts/build_summary.py --cv-dir reports/cv_v3 --compare reports/cv_v2/cv_results.json --names v2,v3
5
  """
6
  from __future__ import annotations
7
 
 
19
  return f"{100 * x:.1f}%"
20
 
21
 
22
+ def peak_thr(agg: dict) -> float:
23
+ """Shipped min_peak_dist: the final model's threshold from rule_study.json next to cv_results.json, else PostConfig's default."""
24
+ from cellquant.postprocess import PostConfig
25
+ return float(agg.get("final_min_peak_dist", PostConfig().min_peak_dist))
26
+
27
+
28
  def main(argv=None):
29
  ap = argparse.ArgumentParser()
30
  ap.add_argument("--cv-dir", default="runs/cv", help="folder holding cv_results.json of the shipped recipe")
31
  ap.add_argument("--compare", default="reports/cv/cv_results.json",
32
  help="cv_results.json of the previous recipe for the change table ('' = none)")
33
+ ap.add_argument("--names", default="v1,v2", help="names of the previous and the shipped recipe")
34
  a = ap.parse_args(argv)
35
+ prev_name, cur_name = a.names.split(",")
36
  cv = json.load(open(os.path.join(ROOT, a.cv_dir, "cv_results.json")))
37
  prev = json.load(open(os.path.join(ROOT, a.compare)))["aggregate"] if a.compare and os.path.exists(os.path.join(ROOT, a.compare)) else None
38
  agg = cv["aggregate"]
39
  per = agg["per_image"]
40
+ rs = os.path.join(ROOT, a.cv_dir, "rule_study.json")
41
+ if os.path.exists(rs):
42
+ fin = json.load(open(rs)).get("final")
43
+ if fin:
44
+ agg["final_min_peak_dist"] = fin["chosen"]["thr"]
45
  fit = json.load(open(os.path.join(ROOT, "reports", "final", "final_fit.json")))
46
  tot = agg["dataset_totals"]
47
  n_nuc = sum(tot[c] for c in CELL_LABELS)
 
54
  "then a final model trained on all images with the same recipe. Each held-out image was tested 23 times: 3 identical runs "
55
  "(determinism), no-TTA / 4× / 8× test-time augmentation, 8 rotations and flips, 6 photometric perturbations, and 6 post-processing rule variants. "
56
  "The only choice made on held-out data is the rule that gives a nucleus the Nostril attribute: of three candidate rules, "
57
+ "'contains an in-nucleus punctum' (the annotation protocol) scored best and is the default; see the rule-variant table in cv_report.md. "
58
+ "The peak-distance threshold of v3 was chosen, for every fold, on that fold's eight training images only (scripts/rule_study.py), "
59
+ "and for the shipped model on its own nine training images; the held-out images never entered that choice.", "",
60
+ "Recipe (v3, shipped): the v2 model (ResNet34-UNet with a full-resolution stem; targets are a 3-px inner border ring (class weight 3), "
61
  "a per-nucleus normalised distance-to-outline map that the watershed uses as its landscape (markers = distance > 0.5), "
62
  "an antibody head whose loss up-weights antibody-negative pixels by sqrt(positive / negative pixels) (4.5 on the full set), "
63
+ "and 25 % of training crops centred on an antibody-negative nucleus) plus one post-processing rule: a nucleus whose peak "
64
+ f"predicted distance is below {peak_thr(agg):.2f} is dropped (PostConfig.min_peak_dist). The distance head is trained to reach 1 inside "
65
+ "every annotated nucleus and to stay at 0 everywhere else, so a low peak marks a blob the annotator would have skipped. "
66
+ "The weights are unchanged from v2 (reports/cv_v2/); v1 (reports/cv/) used a 2-px ring, the interior map as watershed landscape "
67
+ "and an unweighted antibody loss. The change table below compares the shipped recipe with the previous one under the same protocol.", "",
68
  "## Headline numbers (micro-averaged over all held-out images, no TTA)", "",
69
  "| Measure | Precision | Recall | F1 |", "|---|---:|---:|---:|",
70
  f"| Nucleus detection, IoU ≥ 0.5 | {pct(det['cells']['precision'])} | {pct(det['cells']['recall'])} | {pct(det['cells']['f1'])} |",
 
92
  L.append(f"| {k} | {pct(s['cell_det_f1'])} | {pct(s['cell_attr_both'])} | {pct(s['nos_det_f1'])} |")
93
  if prev:
94
  pd, pd8 = prev["detection_base"], prev["detection_tta8"]
95
+ L += ["", f"## Change from {prev_name} (same protocol, same nine held-out images; `{a.cv_dir}/comparison.md` has the full table)", "",
96
+ f"| Measure | {prev_name} | {cur_name} | Δ (points) |", "|---|---:|---:|---:|"]
97
  def row(name, x, y):
98
  L.append(f"| {name} | {pct(x)} | {pct(y)} | {100 * (y - x):+.1f} |")
99
  row("Nucleus F1, IoU ≥ 0.5", pd["cells"]["f1"], det["cells"]["f1"])
 
110
  for k in sorted(per):
111
  if k in prev["per_image"]:
112
  row(f"Nuclei F1, {k} held out", prev["per_image"][k]["base"]["cell_det_f1"], per[k]["base"]["cell_det_f1"])
113
+ n_pred_prev = pd["cells"]["tp"] + pd["cells"]["fp"]
114
+ n_pred_cur = det["cells"]["tp"] + det["cells"]["fp"]
115
  n_gt = det["cells"]["tp"] + det["cells"]["fn"]
116
+ nov_prev = sum(s["fp_analysis"]["n_fp_no_gt_overlap"] for s in prev["per_image"].values())
117
+ nov_cur = sum(s["fp_analysis"]["n_fp_no_gt_overlap"] for s in per.values())
118
+ L += ["", f"{cur_name} keeps the {prev_name} weights and drops {n_pred_prev - n_pred_cur} of the {n_pred_prev} nuclei {prev_name} predicted on the "
119
+ f"nine held-out images ({n_gt} annotated): unmatched predictions fall from {pd['cells']['fp']} to {det['cells']['fp']} "
120
+ f"(those overlapping no annotated nucleus from {nov_prev} to {nov_cur}) while matched ones fall from {pd['cells']['tp']} to {det['cells']['tp']}, "
121
+ f"so precision rises by {100 * (det['cells']['precision'] - pd['cells']['precision']):.1f} points and recall drops by "
122
+ f"{100 * (pd['cells']['recall'] - det['cells']['recall']):.1f}. Puncta are untouched. The over-count of nuclei on the annotated images "
123
+ f"shrinks from {100 * (n_pred_prev / n_gt - 1):.0f} % to {100 * (n_pred_cur / n_gt - 1):.0f} %. The threshold was never tuned on a held-out image: "
124
+ "each fold used the value chosen on its own training images (0.60-0.67, see rule_study.md), the shipped model the value chosen on all nine "
125
+ f"({peak_thr(agg):.2f})."]
126
  sw = agg.get("antibody_threshold_sweep")
127
  if sw:
128
  best = max(sw["sweep"], key=lambda r: r["neg_f1"])
 
132
  f"The default was kept because the class is dominated by a single image (F99 holds 62 of the {tot['Cell, Antibody -'] + tot['Cell, Nostril, Antibody -']} negatives)."]
133
  L += ["", "## What limits the scores", "",
134
  "* **Touching nuclei** are still the main source of IoU-0.5 misses (worst on F113, the densest image). The distance-map watershed of v2 recovers many clusters that v1 merged (v1 marker/threshold sweeps in `reports/cv/postprocess_sweep.json` had shown post-processing alone could not), but some clusters are still merged and a few nuclei are now split in two.",
135
+ "* **Annotation completeness**: most unmatched predictions overlap an annotated nucleus (outline disagreement); the rest are dimmer, smaller objects the annotator skipped, which the peak-distance rule of v3 halves (143 → 72 on the held-out images) at the price of 21 matched nuclei. Many faint red dots were likewise not annotated, which caps external-punctum precision.",
136
  f"* **Antibody − is rare** ({tot['Cell, Antibody -'] + tot['Cell, Nostril, Antibody -']} of {n_nuc} nuclei, 62 in one image), and **Cell, Nostril, Antibody −** has five examples; their scores are not reliable. The class-balanced loss of v2 trades antibody-negative precision for recall.",
137
  "* Scores are from nine images of one experiment (35-day spinal cord, Iba1/TMEM119). New staining batches or magnifications need a check against a few annotated images.",
138
  "", "## Reproduce", "",
139
  "```bash", "python evaluate.py cv --data-dir <folder with TIFs and CVAT zips> --runs-dir runs/cv_v2 --iters 3000",
140
+ "python scripts/rule_study.py --data-dir <folder> --runs-dir runs/cv_v2 --out runs/cv_v3/rule_study --weights weights/cellquant.pt",
141
+ "python evaluate.py cv --data-dir <folder> --runs-dir runs/cv_v3 --post-config runs/cv_v3/rule_study_post_config.json # fold_*.pt linked from runs/cv_v2",
142
  "python evaluate.py final --data-dir <folder> --weights weights/cellquant.pt",
143
+ "python scripts/compare_cv.py reports/cv_v2/cv_results.json runs/cv_v3/cv_results.json --names v2,v3 --out reports/cv_v3/comparison.md",
144
+ "python scripts/build_summary.py --cv-dir reports/cv_v3 --compare reports/cv_v2/cv_results.json --names v2,v3", "```", ""]
145
  out = os.path.join(ROOT, "reports", "SUMMARY.md")
146
  with open(out, "w") as f:
147
  f.write("\n".join(L))
validation/C100 35D SC Iba_Annotated.zip CHANGED
Binary files a/validation/C100 35D SC Iba_Annotated.zip and b/validation/C100 35D SC Iba_Annotated.zip differ
 
validation/C122 F 35D SC Tmem W003_Annotated.zip CHANGED
Binary files a/validation/C122 F 35D SC Tmem W003_Annotated.zip and b/validation/C122 F 35D SC Tmem W003_Annotated.zip differ
 
validation/F110 F 35D SC Iba W002_Annotated.zip CHANGED
Binary files a/validation/F110 F 35D SC Iba W002_Annotated.zip and b/validation/F110 F 35D SC Iba W002_Annotated.zip differ
 
validation/F113 F 35D SC Tmem W001_Annotated.zip CHANGED
Binary files a/validation/F113 F 35D SC Tmem W001_Annotated.zip and b/validation/F113 F 35D SC Tmem W001_Annotated.zip differ