cledouxluma commited on
Commit
11246bf
·
verified ·
1 Parent(s): c0f802b

Upload evaluation/metrics.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. evaluation/metrics.py +166 -0
evaluation/metrics.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Core evaluation metrics for face detection.
3
+
4
+ Implements:
5
+ - IoU computation (pairwise and matrix)
6
+ - Average Precision (AP) with VOC-style 11-point interpolation
7
+ - Recall at various IoU thresholds
8
+ - WiderFace evaluation protocol helpers
9
+ """
10
+
11
+ import numpy as np
12
+ from typing import List, Tuple, Optional
13
+
14
+
15
+ def compute_iou_matrix(boxes1: np.ndarray, boxes2: np.ndarray) -> np.ndarray:
16
+ """
17
+ Compute pairwise IoU between two sets of boxes.
18
+
19
+ Args:
20
+ boxes1: [N, 4] (x1, y1, x2, y2)
21
+ boxes2: [M, 4] (x1, y1, x2, y2)
22
+
23
+ Returns:
24
+ [N, M] IoU matrix
25
+ """
26
+ x1 = np.maximum(boxes1[:, 0:1], boxes2[:, 0:1].T)
27
+ y1 = np.maximum(boxes1[:, 1:2], boxes2[:, 1:2].T)
28
+ x2 = np.minimum(boxes1[:, 2:3], boxes2[:, 2:3].T)
29
+ y2 = np.minimum(boxes1[:, 3:4], boxes2[:, 3:4].T)
30
+
31
+ inter = np.maximum(0, x2 - x1) * np.maximum(0, y2 - y1)
32
+
33
+ area1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1])
34
+ area2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1])
35
+
36
+ union = area1[:, None] + area2[None, :] - inter
37
+ return inter / (union + 1e-6)
38
+
39
+
40
+ def compute_ap(recall: np.ndarray, precision: np.ndarray,
41
+ use_11_point: bool = True) -> float:
42
+ """
43
+ Compute Average Precision from recall-precision curve.
44
+
45
+ WiderFace uses 11-point interpolation (VOC2007 style).
46
+
47
+ Args:
48
+ recall: [N] sorted recall values
49
+ precision: [N] corresponding precision values
50
+ use_11_point: Use 11-point interpolation (default: True)
51
+
52
+ Returns:
53
+ AP value
54
+ """
55
+ if use_11_point:
56
+ # 11-point interpolation
57
+ ap = 0.0
58
+ for t in np.arange(0, 1.1, 0.1):
59
+ if np.sum(recall >= t) == 0:
60
+ p = 0
61
+ else:
62
+ p = np.max(precision[recall >= t])
63
+ ap += p / 11
64
+ return ap
65
+ else:
66
+ # All-point interpolation (VOC2010+ style)
67
+ mrec = np.concatenate(([0.0], recall, [1.0]))
68
+ mpre = np.concatenate(([0.0], precision, [0.0]))
69
+
70
+ # Make precision monotonically decreasing
71
+ for i in range(len(mpre) - 1, 0, -1):
72
+ mpre[i - 1] = max(mpre[i - 1], mpre[i])
73
+
74
+ # Compute area under curve
75
+ idx = np.where(mrec[1:] != mrec[:-1])[0]
76
+ ap = np.sum((mrec[idx + 1] - mrec[idx]) * mpre[idx + 1])
77
+ return ap
78
+
79
+
80
+ def compute_recall_at_iou(pred_boxes: np.ndarray, pred_scores: np.ndarray,
81
+ gt_boxes: np.ndarray, iou_threshold: float = 0.5
82
+ ) -> Tuple[float, np.ndarray, np.ndarray]:
83
+ """
84
+ Compute recall and precision at a given IoU threshold.
85
+
86
+ Args:
87
+ pred_boxes: [N, 4] predicted boxes sorted by score (descending)
88
+ pred_scores: [N] prediction scores
89
+ gt_boxes: [M, 4] ground truth boxes
90
+ iou_threshold: IoU threshold for matching
91
+
92
+ Returns:
93
+ (ap, recall_curve, precision_curve)
94
+ """
95
+ num_gt = gt_boxes.shape[0]
96
+ if num_gt == 0:
97
+ return 0.0, np.array([]), np.array([])
98
+
99
+ # Sort by score
100
+ order = np.argsort(-pred_scores)
101
+ pred_boxes = pred_boxes[order]
102
+
103
+ iou_matrix = compute_iou_matrix(pred_boxes, gt_boxes)
104
+
105
+ # Greedy matching
106
+ gt_matched = np.zeros(num_gt, dtype=bool)
107
+ tp = np.zeros(len(pred_boxes))
108
+ fp = np.zeros(len(pred_boxes))
109
+
110
+ for i in range(len(pred_boxes)):
111
+ if iou_matrix.shape[1] > 0:
112
+ best_gt = iou_matrix[i].argmax()
113
+ if iou_matrix[i, best_gt] >= iou_threshold and not gt_matched[best_gt]:
114
+ tp[i] = 1
115
+ gt_matched[best_gt] = True
116
+ else:
117
+ fp[i] = 1
118
+ else:
119
+ fp[i] = 1
120
+
121
+ tp_cumsum = np.cumsum(tp)
122
+ fp_cumsum = np.cumsum(fp)
123
+
124
+ recall = tp_cumsum / num_gt
125
+ precision = tp_cumsum / (tp_cumsum + fp_cumsum)
126
+
127
+ ap = compute_ap(recall, precision)
128
+ return ap, recall, precision
129
+
130
+
131
+ def match_detections_to_gt(pred_boxes: np.ndarray, gt_boxes: np.ndarray,
132
+ iou_threshold: float = 0.5
133
+ ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
134
+ """
135
+ Match predictions to ground truth for detailed analysis.
136
+
137
+ Returns:
138
+ (tp_mask, fp_mask, fn_indices)
139
+ tp_mask: [N] boolean, True for true positives
140
+ fp_mask: [N] boolean, True for false positives
141
+ fn_indices: indices of unmatched GT boxes (false negatives)
142
+ """
143
+ if len(pred_boxes) == 0:
144
+ return (np.array([], dtype=bool),
145
+ np.array([], dtype=bool),
146
+ np.arange(len(gt_boxes)))
147
+
148
+ if len(gt_boxes) == 0:
149
+ return (np.zeros(len(pred_boxes), dtype=bool),
150
+ np.ones(len(pred_boxes), dtype=bool),
151
+ np.array([], dtype=int))
152
+
153
+ iou_matrix = compute_iou_matrix(pred_boxes, gt_boxes)
154
+ gt_matched = np.zeros(len(gt_boxes), dtype=bool)
155
+ tp_mask = np.zeros(len(pred_boxes), dtype=bool)
156
+
157
+ for i in range(len(pred_boxes)):
158
+ best_gt = iou_matrix[i].argmax()
159
+ if iou_matrix[i, best_gt] >= iou_threshold and not gt_matched[best_gt]:
160
+ tp_mask[i] = True
161
+ gt_matched[best_gt] = True
162
+
163
+ fp_mask = ~tp_mask
164
+ fn_indices = np.where(~gt_matched)[0]
165
+
166
+ return tp_mask, fp_mask, fn_indices