text: import os
  from collections import defaultdict
  
  import numpy as np
  import torch
  
  from ssd.utils.iou import iou
  
  
  def do_voc_evaluation(dataset, predictions, output_dir, logger, **kwargs):
      pred_boxes = []
      pred_labels = []
      pred_scores = []
  
      gt_boxes = []
      gt_labels = []
      gt_difficults = []
  
      size = kwargs['size']
  
      for image_id, prediction in enumerate(predictions):
          image_info = dataset.get_img_info(image_id)
          image_width = image_info['width']
          image_height = image_info['height']
  
          prediction[0] = resize(prediction[0], (image_width, image_height), size)
  
          pred_boxes.append(prediction[0])
          pred_labels.append(prediction[1])
          pred_scores.append(prediction[2])
  
          gt = dataset.get_groundtruth(image_id)
          gt_boxes.append(gt[0])
          gt_labels.append(gt[1])
          gt_difficults.append(gt[2])
  
      result = eval_detection_voc(
          pred_boxes=pred_boxes,
          pred_labels=pred_labels,
          pred_scores=pred_scores,
          gt_boxes=gt_boxes,
          gt_labels=gt_labels,
          gt_difficults=gt_difficults
      )
      result_str = f"mAP: {result['map']:.4f}\n"
      for i, ap in enumerate(result['ap']):
          if i == 0:
              continue
          result_str += f'{dataset.map_class_id_to_class_name(i):<16}: {ap:.4f}\n'
  
      logger.info(result_str)
      if output_dir:
          with open(os.path.join(output_dir, "result.txt"), "w") as fid:
              fid.write(result_str)
      return result
  
  
  def eval_detection_voc(
      pred_boxes,
      pred_labels,
      pred_scores,
      gt_boxes,
      gt_labels,
      gt_difficults,
      iou_thresh=0.5,
      use_07_metric=False
  ):
      assert len(pred_boxes) == len(gt_boxes)
  
      prec, rec = calc_detection_voc_prec_rec(
          pred_boxes=pred_boxes,
          pred_labels=pred_labels,
          pred_scores=pred_scores,
          gt_boxes=gt_boxes,
          gt_labels=gt_labels,
          gt_difficults=gt_difficults,
          iou_thresh=iou_thresh
      )
      ap = calc_detection_voc_ap(prec, rec, use_07_metric=use_07_metric)
      return {"ap": ap, "map": np.nanmean(ap)}
  
  
  def calc_detection_voc_prec_rec(
      pred_boxes,
      pred_labels,
      pred_scores,
      gt_boxes,
      gt_labels,
      gt_difficults,
      iou_thresh
  ):
      n_pos = defaultdict(int)
      score = defaultdict(list)
      match = defaultdict(list)
  
      for gt_box, gt_label, gt_difficult, pred_box, pred_label, pred_score in zip(
          gt_boxes, gt_labels, gt_difficults, pred_boxes, pred_labels, pred_scores
      ):
          pred_box = pred_box.numpy()
          pred_label = pred_label.numpy()
          pred_score = pred_score.numpy()
          gt_box = gt_box
          gt_label = gt_label
          gt_difficult = gt_difficult
  
          for l in np.unique(np.concatenate((pred_label, gt_label)).astype(int)):
              pred_mask_l = pred_label == l
              pred_bbox_l = pred_box[pred_mask_l]
              pred_score_l = pred_score[pred_mask_l]
              # sort by score
              order = pred_score_l.argsort()[::-1]
              pred_bbox_l = pred_bbox_l[order]
              pred_score_l = pred_score_l[order]
  
              gt_mask_l = gt_label == l
              gt_bbox_l = gt_box[gt_mask_l]
              gt_difficult_l = gt_difficult[gt_mask_l]
  
              n_pos[l] += np.logical_not(gt_difficult_l).sum()
              score[l].extend(pred_score_l)
  
              if len(pred_bbox_l) == 0:
                  continue
              if len(gt_bbox_l) == 0:
                  match[l].extend((0,) * pred_bbox_l.shape[0])
                  continue
  
              pred_bbox_l = pred_bbox_l.copy()
              pred_bbox_l[:, 2:] += 1
              gt_bbox_l = gt_bbox_l.copy()
              gt_bbox_l[:, 2:] += 1
  
              overlaps = iou(
                  torch.from_numpy(pred_bbox_l),
                  torch.from_numpy(gt_bbox_l)
              ).numpy()
              gt_index = overlaps.argmax(axis=1)
              del overlaps
  
              selec = np.zeros(gt_bbox_l.shape[0], dtype=bool)
              for gt_idx in gt_index:
                  if gt_idx >= 0:
                      if gt_difficult_l[gt_idx]:
                          match[l].append(-1)
                      else:
                          if not selec[gt_idx]:
                              match[l].append(1)
                          else:
                              match[l].append(0)
                      selec[gt_idx] = True
                  else:
                      match[l].append(0)
      n_fg_class = max(n_pos.keys()) + 1
      prec = [None] * n_fg_class
      rec = [None] * n_fg_class
  
      for l in n_pos.keys():
          score_l = np.array(score[l])
          match_l = np.array(match[l], dtype=np.int8)
  
          order = score_l.argsort()[::-1]
          match_l = match_l[order]
  
          tp = np.cumsum(match_l == 1)
          fp = np.cumsum(match_l == 0)
  
          # If an element of fp + tp is 0,
          # the corresponding element of prec[l] is nan.
          prec[l] = tp / (fp + tp)
          # If n_pos[l] is 0, rec[l] is None.
          if n_pos[l] > 0:
              rec[l] = tp / n_pos[l]
  
      return prec, rec
  
  
  def calc_detection_voc_ap(prec, rec, use_07_metric=False):
      n_fg_class = len(prec)
      ap = np.empty(n_fg_class)
      for l in range(n_fg_class):
          if prec[l] is None or rec[l] is None:
              ap[l] = np.nan
              continue
  
          if use_07_metric:
              # 11 point metric
              ap[l] = 0
              for t in np.arange(0.0, 1.1, 0.1):
                  if np.sum(rec[l] >= t) == 0:
                      p = 0
                  else:
                      p = np.max(np.nan_to_num(prec[l])[rec[l] >= t])
                  ap[l] += p / 11
          else:
              # correct AP calculation
              # first append sentinel values at the end
              mpre = np.concatenate(([0], np.nan_to_num(prec[l]), [0]))
              mrec = np.concatenate(([0], rec[l], [1]))
  
              mpre = np.maximum.accumulate(mpre[::-1])[::-1]
  
              # to calculate area under PR curve, look for points
              # where X axis (recall) changes value
              i = np.where(mrec[1:] != mrec[:-1])[0]
  
              # and sum (\Delta recall) * prec
              ap[l] = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
  
      return ap
  
  
  def resize(boxes, size, origin_size):
      boxes[:, [0, 2]] = size[0] / origin_size[0] * boxes[:, [0, 2]]
      boxes[:, [1, 3]] = size[1] / origin_size[1] * boxes[:, [1, 3]]
      return boxes
  