text: from __future__ import absolute_import
  from __future__ import division
  from __future__ import print_function
  
  import os
  import torch
  import numpy as np
  
  
  def select_device(device='', apex=False, batch_size=None):
      # device = 'cpu' or '0' or '0,1,2,3'
      cpu_request = device.lower() == 'cpu'
      if device and not cpu_request:  # if device requested other than 'cpu'
          os.environ['CUDA_VISIBLE_DEVICES'] = device  # set environment variable
          assert torch.cuda.is_available(), 'CUDA unavailable, invalid device %s requested' % device  # check availablity
  
      cuda = False if cpu_request else torch.cuda.is_available()
      if cuda:
          c = 1024 ** 2  # bytes to MB
          ng = torch.cuda.device_count()
          if ng > 1 and batch_size:  # check that batch_size is compatible with device_count
              assert batch_size % ng == 0, 'batch-size %g not multiple of GPU count %g' % (batch_size, ng)
          x = [torch.cuda.get_device_properties(i) for i in range(ng)]
          s = 'Using CUDA ' + ('Apex ' if apex else '')  # apex for mixed precision https://github.com/NVIDIA/apex
          for i in range(0, ng):
              if i == 1:
                  s = ' ' * len(s)
              print("%sdevice%g _CudaDeviceProperties(name='%s', total_memory=%dMB)" %
                    (s, i, x[i].name, x[i].total_memory / c))
      else:
          print('Using CPU')
  
      print('')  # skip a line
      return torch.device('cuda:0' if cuda else 'cpu')
  
  
  class AverageMeter(object):
      """Computes and stores the average and current value"""
  
      def __init__(self):
          self.reset()
  
      def reset(self):
          self.val = 0
          self.avg = 0
          self.sum = 0
          self.count = 0
  
      def update(self, val, n=1):
          self.val = val
          self.sum += val * n
          self.count += n
          if self.count > 0:
              self.avg = self.sum / self.count
  
  
  def xyxy2xywh(x):
      #                                   0   1   2   3
      # Convert bounding box format from [x1, y1, x2, y2] to [x, y, w, h]
      y = torch.zeros(x.shape) if x.dtype is torch.float32 else np.zeros(x.shape)
      y[:, 0] = (x[:, 0] + x[:, 2]) / 2  # center x
      y[:, 1] = (x[:, 1] + x[:, 3]) / 2  # center y
      y[:, 2] = x[:, 2] - x[:, 0]  # width
      y[:, 3] = x[:, 3] - x[:, 1]  # height
      return y
  
  
  def xywh2xyxy(x):
      # Convert bounding box format from [x, y, w, h] to [x1, y1, x2, y2]
      y = torch.zeros(x.shape) if x.dtype is torch.float32 else np.zeros(x.shape)
      y[:, 0] = (x[:, 0] - x[:, 2] / 2)
      y[:, 1] = (x[:, 1] - x[:, 3] / 2)
      y[:, 2] = (x[:, 0] + x[:, 2] / 2)
      y[:, 3] = (x[:, 1] + x[:, 3] / 2)
      return y
  
  def ltwh2xywh(x):
      #                                   0   1   2   3
      # Convert bounding box format from [x1, y1, w, h] to [x, y, w, h]
      y = torch.zeros(x.shape) if x.dtype is torch.float32 else np.zeros(x.shape)
      y[:, 0] = x[:, 0] + x[:, 2] / 2     # center x
      y[:, 1] = x[:, 1] + x[:, 3] / 2     # center y
      y[:, 2] = x[:, 2]                   # width
      y[:, 3] = x[:, 3]                   # height
      return y
  
  
  def ap_per_class(tp, conf, pred_cls, target_cls):
      """ Compute the average precision, given the recall and precision curves.
      Method originally from https://github.com/rafaelpadilla/Object-Detection-Metrics.
      # Arguments
          tp:    True positives (list).
          conf:  Objectness value from 0-1 (list).
          pred_cls: Predicted object classes (list).
          target_cls: True object classes (list).
      # Returns
          The average precision as computed in py-faster-rcnn.
      """
  
      # lists/pytorch to numpy
      tp, conf, pred_cls, target_cls = np.array(tp), np.array(conf), np.array(pred_cls), np.array(target_cls)
  
      # Sort by objectness
      i = np.argsort(-conf)
      tp, conf, pred_cls = tp[i], conf[i], pred_cls[i]
  
      # Find unique classes
      unique_classes = np.unique(np.concatenate((pred_cls, target_cls), 0))
  
      # Create Precision-Recall curve and compute AP for each class
      ap, p, r = [], [], []
      for c in unique_classes:
          i = pred_cls == c
          n_gt = sum(target_cls == c)  # Number of ground truth objects
          n_p = sum(i)  # Number of predicted objects
  
          if (n_p == 0) and (n_gt == 0):
              continue
          elif (n_p == 0) or (n_gt == 0):
              ap.append(0)
              r.append(0)
              p.append(0)
          else:
              # Accumulate FPs and TPs
              fpc = np.cumsum(1 - tp[i])
              tpc = np.cumsum(tp[i])
  
              # Recall
              recall_curve = tpc / (n_gt + 1e-16)
              r.append(tpc[-1] / (n_gt + 1e-16))
  
              # Precision
              precision_curve = tpc / (tpc + fpc)
              p.append(tpc[-1] / (tpc[-1] + fpc[-1]))
  
              # AP from recall-precision curve
              ap.append(compute_ap(recall_curve, precision_curve))
  
      return np.array(ap), unique_classes.astype('int32'), np.array(r), np.array(p)
  
  
  def compute_ap(recall, precision):
      """ Compute the average precision, given the recall and precision curves.
      Code originally from https://github.com/rbgirshick/py-faster-rcnn.
      # Arguments
          recall:    The recall curve (list).
          precision: The precision curve (list).
      # Returns
          The average precision as computed in py-faster-rcnn.
      """
      # correct AP calculation
      # first append sentinel values at the end
  
      mrec = np.concatenate(([0.], recall, [1.]))
      mpre = np.concatenate(([0.], precision, [0.]))
  
      # compute the precision envelope
      for i in range(mpre.size - 1, 0, -1):
          mpre[i - 1] = np.maximum(mpre[i - 1], mpre[i])
  
      # 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 = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1])
      return ap
  
  
  def bbox_iou(box1, box2, x1y1x2y2=False):
      """
      Returns the IoU of two bounding boxes
      """
      N, M = len(box1), len(box2)
      if x1y1x2y2:
          # Get the coordinates of bounding boxes
          b1_x1, b1_y1, b1_x2, b1_y2 = box1[:, 0], box1[:, 1], box1[:, 2], box1[:, 3]
          b2_x1, b2_y1, b2_x2, b2_y2 = box2[:, 0], box2[:, 1], box2[:, 2], box2[:, 3]
      else:
          # Transform from center and width to exact coordinates
          b1_x1, b1_x2 = box1[:, 0] - box1[:, 2] / 2, box1[:, 0] + box1[:, 2] / 2
          b1_y1, b1_y2 = box1[:, 1] - box1[:, 3] / 2, box1[:, 1] + box1[:, 3] / 2
          b2_x1, b2_x2 = box2[:, 0] - box2[:, 2] / 2, box2[:, 0] + box2[:, 2] / 2
          b2_y1, b2_y2 = box2[:, 1] - box2[:, 3] / 2, box2[:, 1] + box2[:, 3] / 2
  
      # get the coordinates of the intersection rectangle
      inter_rect_x1 = torch.max(b1_x1.unsqueeze(1), b2_x1)
      inter_rect_y1 = torch.max(b1_y1.unsqueeze(1), b2_y1)
      inter_rect_x2 = torch.min(b1_x2.unsqueeze(1), b2_x2)
      inter_rect_y2 = torch.min(b1_y2.unsqueeze(1), b2_y2)
      # Intersection area
      inter_area = torch.clamp(inter_rect_x2 - inter_rect_x1, 0) * torch.clamp(inter_rect_y2 - inter_rect_y1, 0)
      # Union Area
      b1_area = ((b1_x2 - b1_x1) * (b1_y2 - b1_y1))
      b1_area = ((b1_x2 - b1_x1) * (b1_y2 - b1_y1)).view(-1, 1).expand(N, M)
      b2_area = ((b2_x2 - b2_x1) * (b2_y2 - b2_y1)).view(1, -1).expand(N, M)
  
      return inter_area / (b1_area + b2_area - inter_area + 1e-16)
  
  
  def generate_anchors(nGh, nGw, anchor_wh):
      nA = len(anchor_wh)
      yy, xx = np.meshgrid(np.arange(nGh), np.arange(nGw), indexing='ij')
  
      mesh = np.stack([xx, yy], axis=0)  # Shape 2, nGh, nGw
      mesh = np.tile(np.expand_dims(mesh, axis=0), (nA, 1, 1, 1))  # Shape nA x 2 x nGh x nGw
      anchor_offset_mesh = np.tile(np.expand_dims(np.expand_dims(anchor_wh, -1), -1),
                                   (1, 1, nGh, nGw))  # Shape nA x 2 x nGh x nGw
      anchor_mesh = np.concatenate((mesh, anchor_offset_mesh), axis=1)  # Shape nA x 4 x nGh x nGw
      return anchor_mesh
  
  
  def encode_delta(gt_box_list, fg_anchor_list):
      px, py, pw, ph = fg_anchor_list[:, 0], fg_anchor_list[:, 1], \
                       fg_anchor_list[:, 2], fg_anchor_list[:, 3]
      gx, gy, gw, gh = gt_box_list[:, 0], gt_box_list[:, 1], \
                       gt_box_list[:, 2], gt_box_list[:, 3]
      dx = (gx - px) / pw
      dy = (gy - py) / ph
      dw = np.log(gw / pw)
      dh = np.log(gh / ph)
      return np.stack((dx, dy, dw, dh), axis=1)
  
  
  def map_to_orig_coords(dets, net_w, net_h, orig_w, orig_h):
      """
      :param dets: x1, y1, x2, y2, score, class: n×6
      :param net_w:
      :param net_h:
      :param orig_w:
      :param orig_h:
      :return:
      """
      def get_padding():
          """
          :return:
          """
          ratio_x = float(net_w) / orig_w
          ratio_y = float(net_h) / orig_h
          ratio = min(ratio_x, ratio_y)
  
          # new_w, new_h
          new_shape = (round(orig_w * ratio), round(orig_h * ratio))
          new_w, new_h = new_shape
  
          pad_x = (net_w - new_w) * 0.5  # width padding
          pad_y = (net_h - new_h) * 0.5  # height padding
  
          left, right = round(pad_x - 0.1), round(pad_x + 0.1)
          top, bottom = round(pad_y - 0.1), round(pad_y + 0.1)
  
          return top, bottom, left, right, new_shape
  
      # pad_tl, pad_rb, pad_type, new_shape = get_padding()
      top, bottom, left, right, new_shape = get_padding()
      new_w, new_h = new_shape
  
      dets[:, 0] = (dets[:, 0] - left) / new_w * orig_w   # x1
      dets[:, 2] = (dets[:, 2] - left) / new_w * orig_w   # x2
      dets[:, 1] = (dets[:, 1] - top)  / new_h * orig_h   # y1
      dets[:, 3] = (dets[:, 3] - top)  / new_h * orig_h   # y2
  
      # clamp
      clip_coords(dets[:, :4], (orig_h, orig_w))
  
      return dets
  
  
  def clip_coords(boxes, img_shape):
      # Clip bounding xyxy bounding boxes to image shape (height, width)
      boxes[:, 0].clamp_(0, img_shape[1])  # x1
      boxes[:, 1].clamp_(0, img_shape[0])  # y1
      boxes[:, 2].clamp_(0, img_shape[1])  # x2
      boxes[:, 3].clamp_(0, img_shape[0])  # y2