import numpy as np from ..a4.label_grid import iou def _assign_detections(det_boxes_scores, gt_boxes, min_iou): """Returns a list of 3-tuples (det_box, det_score, closest_gt_box)""" assert min_iou > 0, "min_iou should be greater than zero" det2gt = [] for det_box, det_score in det_boxes_scores: # get all GTs that overlap at least by 0.5 and sort descending by overlap best_iou = -1.0 closest_gt_box = None for gt in gt_boxes: curr_iou = iou(gt, det_box) if curr_iou > best_iou: best_iou = curr_iou closest_gt_box = gt if best_iou >= min_iou: det2gt.append((det_box, det_score, closest_gt_box)) else: det2gt.append((det_box, det_score, None)) return det2gt def calculate_ap_pr(det_boxes_scores: dict, gt_boxes: dict, min_iou=0.5): """ Calculates average precision for the given detections and ground truths. This function also calculates precision and recall values that can be used to plot a PR curve @param det_boxes_scores: A dictionary that maps image numbers to a list of tuples, containing detected AnnotationRect objects and a score. @param gt_boxes: A dictionary that maps image numbers to the list of ground truth AnnotationRect objects. """ for dets in det_boxes_scores.values(): assert all( isinstance(d, tuple) and len(d) == 2 for d in dets ), "Your detection boxes must have scores" assert ( len(set(gt_boxes.keys()).intersection(det_boxes_scores.keys())) > 0 ), "The two dictionaries have no common keys. Maybe you have selected the wrong dataset?" gts_total = 0 dets2gts_flat = [] for img in gt_boxes: gts_total += len(gt_boxes[img]) det2gts = _assign_detections( det_boxes_scores.get(img, []), gt_boxes[img], min_iou ) dets2gts_flat.extend(det2gts) # sort by detection confidence # p[1] is the score dets2gts_flat.sort(key=lambda p: p[1], reverse=True) tp = np.zeros(len(dets2gts_flat), dtype=np.float32) fp = np.zeros(len(dets2gts_flat), dtype=np.float32) fn_cum = np.zeros(len(dets2gts_flat), dtype=np.float32) gts_seen = {None} for idx, (_det, _det_score, gt) in enumerate(dets2gts_flat): gt_prev_seen = gt in gts_seen # If detection was assigned to GT that was not previously assigned to anything-> TP tp[idx] = gt is not None and not gt_prev_seen # If detection was not assigned to GT or detection was assigned to a previously assigned GT -> FP fp[idx] = (gt is None) or gt_prev_seen # all gts that have not been assigned yet -> FN gts_seen.add(gt) fn_cum[idx] = gts_total - (len(gts_seen) - 1) # dets2gts_flat i sorted by score, therefore we can calculate tp and fp with cumsum tp_cum = np.cumsum(tp) fp_cum = np.cumsum(fp) precision = tp_cum / (tp_cum + fp_cum) recall = tp_cum / (tp_cum + fn_cum) average_prevision = np.trapz(precision, recall) return average_prevision, precision, recall # def example(): # dboxes = { # "31": [ # (AnnotationRect(1, 2, 3, 4), 0.234), # (AnnotationRect(9, 8, 7, 6), 2.431093), # ], # "32": [ # (AnnotationRect(1, 2, 3, 4), 0.234), # (AnnotationRect(9, 8, 7, 6), 2.431093), # ], # # ... # } # gboxes = { # "31": [AnnotationRect(0, 3, 2, 3)], # "32": [AnnotationRect(9, 9, 12, 23), AnnotationRect(1, 2, 40, 40)], # } # ap, _, _ = calculate_ap_pr(dboxes, gboxes) # print("ap is", ap)