-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathocelot_eval_metrics.py
More file actions
122 lines (94 loc) · 3.95 KB
/
Copy pathocelot_eval_metrics.py
File metadata and controls
122 lines (94 loc) · 3.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# -*- coding: utf-8 -*-
# Adopted from https://github.com/lunit-io/ocelot23algo/tree/main/evaluation
#
# @ Fabian Hörst, fabian.hoerst@uk-essen.de
# Institute for Artifical Intelligence in Medicine,
# University Medicine Essen
import numpy as np
def _preprocess_distance_and_confidence(
pred_all, gt_all, cls_idx_to_name={1: "BC", 2: "TC"}
):
"""Preprocess distance and confidence used for F1 calculation.
Parameters
----------
pred_all: List[List[Tuple(int, int, int, float)]]
List of predictions, each element corresponds a patch.
Each patch contains list of tuples, each element corresponds a single cell.
Each predicted cell consist of x, y, cls, prob.
gt_all: List[List[Tuple(int, int, int)]]
List of GTs, each element corresponds a patch.
Each patch contains list of tuples, each element corresponds a single cell.
Each GT cell consist of x, y, cls.
Returns
-------
all_sample_result: List[List[Tuple(int, np.array, np.array)]]
Distance (between pred and GT) and Confidence per class and sample.
"""
all_sample_result = []
for pred, gt in zip(pred_all, gt_all):
one_sample_result = {}
for cls_idx in sorted(list(cls_idx_to_name.keys())):
pred_cls = np.array([p for p in pred if p[2] == cls_idx], np.float32)
gt_cls = np.array([g for g in gt if g[2] == cls_idx], np.float32)
if len(gt_cls) == 0:
gt_cls = np.zeros(shape=(0, 4))
if len(pred_cls) == 0:
distance = np.zeros([0, len(gt_cls)])
confidence = np.zeros([0, len(gt_cls)])
else:
pred_loc = pred_cls[:, :2].reshape([-1, 1, 2])
gt_loc = gt_cls[:, :2].reshape([1, -1, 2])
distance = np.linalg.norm(pred_loc - gt_loc, axis=2)
confidence = pred_cls[:, 3]
one_sample_result[cls_idx] = (distance, confidence)
all_sample_result.append(one_sample_result)
return all_sample_result
def _calc_scores(all_sample_result, cls_idx, cutoff):
"""Calculate Precision, Recall, and F1 scores for given class
Parameters
----------
all_sample_result: List[List[Tuple(int, np.array, np.array)]]
Distance (between pred and GT) and Confidence per class and sample.
cls_idx: int
1 or 2, where 1 and 2 corresponds Tumor (TC) and Background (BC) cells, respectively.
cutoff: int
Distance cutoff that used as a threshold for collecting candidates of
matching ground-truths per each predicted cell.
Returns
-------
precision: float
Precision of given class
recall: float
Recall of given class
f1: float
F1 of given class
"""
global_num_gt = 0
global_num_tp = 0
global_num_fp = 0
for one_sample_result in all_sample_result:
distance, confidence = one_sample_result[cls_idx]
num_pred, num_gt = distance.shape
assert len(confidence) == num_pred
sorted_pred_indices = np.argsort(-confidence)
bool_mask = distance <= cutoff
num_tp = 0
num_fp = 0
for pred_idx in sorted_pred_indices:
gt_neighbors = bool_mask[pred_idx].nonzero()[0]
if len(gt_neighbors) == 0: # No matching GT --> False Positive
num_fp += 1
else: # Assign neares GT --> True Positive
gt_idx = min(
gt_neighbors, key=lambda gt_idx: distance[pred_idx, gt_idx]
)
num_tp += 1
bool_mask[:, gt_idx] = False
assert num_tp + num_fp == num_pred
global_num_gt += num_gt
global_num_tp += num_tp
global_num_fp += num_fp
precision = global_num_tp / (global_num_tp + global_num_fp + 1e-7)
recall = global_num_tp / (global_num_gt + 1e-7)
f1 = 2 * precision * recall / (precision + recall + 1e-7)
return round(precision, 4), round(recall, 4), round(f1, 4)