docs: def calc_detection_voc_prec_rec(gt_boxlists, pred_boxlists, iou_thresh=0.5):
      """Calculate precision and recall based on evaluation code of PASCAL VOC.
      This function calculates precision and recall of
      predicted bounding boxes obtained from a dataset which has :math:`N`
      images.
      The code is based on the evaluation code used in PASCAL VOC Challenge.
     """
      n_pos = defaultdict(int)
      score = defaultdict(list)
      match = defaultdict(list)
      for gt_boxlist, pred_boxlist in zip(gt_boxlists, pred_boxlists):
          pred_bbox = pred_boxlist.bbox.numpy()
          pred_label = pred_boxlist.get_field("labels").numpy()
          pred_score = pred_boxlist.get_field("scores").numpy()
          gt_bbox = gt_boxlist.bbox.numpy()
          gt_label = gt_boxlist.get_field("labels").numpy()
          gt_difficult = gt_boxlist.get_field("difficult").numpy()
  
          for l in np.unique(np.concatenate((pred_label, gt_label)).astype(int)):
              pred_mask_l = pred_label == l
              pred_bbox_l = pred_bbox[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_bbox[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
  
              # VOC evaluation follows integer typed bounding boxes.
              pred_bbox_l = pred_bbox_l.copy()
              pred_bbox_l[:, 2:] += 1
              gt_bbox_l = gt_bbox_l.copy()
              gt_bbox_l[:, 2:] += 1
              iou = boxlist_iou(
                  BoxList(pred_bbox_l, gt_boxlist.size),
                  BoxList(gt_bbox_l, gt_boxlist.size),
              ).numpy()
              gt_index = iou.argmax(axis=1)
              # set -1 if there is no matching ground truth
              gt_index[iou.max(axis=1) < iou_thresh] = -1
              del iou
  
              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