text: # -*- coding: utf-8 -*-
  
  import numpy as np
  import tensorflow as tf
  from PIL import ImageDraw
  
  def get_boxes_and_inputs_pb(frozen_graph):
  
      with frozen_graph.as_default():
          boxes = tf.get_default_graph().get_tensor_by_name("output_boxes:0")
          inputs = tf.get_default_graph().get_tensor_by_name("inputs:0")
  
      return boxes, inputs
  
  def get_boxes_and_inputs(model, num_classes, size, data_format):
  
      inputs = tf.placeholder(tf.float32, [1, size, size, 3])
  
      with tf.variable_scope('detector'):
          detections = model(inputs, num_classes,
                             data_format=data_format)
  
      boxes = detections_boxes(detections)
  
      return boxes, inputs
  
  
  def load_graph(frozen_graph_filename):
  
      with tf.gfile.GFile(frozen_graph_filename, "rb") as f:
          graph_def = tf.GraphDef()
          graph_def.ParseFromString(f.read())
  
      with tf.Graph().as_default() as graph:
          tf.import_graph_def(graph_def, name="")
  
      return graph
  
  def freeze_graph(sess, output_graph):
  
      output_node_names = [
          "output_boxes",
          "inputs",
      ]
      output_node_names = ",".join(output_node_names)
  
      output_graph_def = tf.graph_util.convert_variables_to_constants(
          sess,
          tf.get_default_graph().as_graph_def(),
          output_node_names.split(",")
      )
  
      with tf.gfile.GFile(output_graph, "wb") as f:
          f.write(output_graph_def.SerializeToString())
  
      print("{} ops written to {}.".format(len(output_graph_def.node), output_graph))
  
  def load_weights(var_list, weights_file):
      """
      Loads and converts pre-trained weights.
      :param var_list: list of network variables.
      :param weights_file: name of the binary file.
      :return: list of assign ops
      """
      with open(weights_file, "rb") as fp:
          _ = np.fromfile(fp, dtype=np.int32, count=5)
  
          weights = np.fromfile(fp, dtype=np.float32)
  
      ptr = 0
      i = 0
      assign_ops = []
      while i < len(var_list) - 1:
          var1 = var_list[i]
          var2 = var_list[i + 1]
          # do something only if we process conv layer
          if 'Conv' in var1.name.split('/')[-2]:
              # check type of next layer
              if 'BatchNorm' in var2.name.split('/')[-2]:
                  # load batch norm params
                  gamma, beta, mean, var = var_list[i + 1:i + 5]
                  batch_norm_vars = [beta, gamma, mean, var]
                  for var in batch_norm_vars:
                      shape = var.shape.as_list()
                      num_params = np.prod(shape)
                      var_weights = weights[ptr:ptr + num_params].reshape(shape)
                      ptr += num_params
                      assign_ops.append(
                          tf.assign(var, var_weights, validate_shape=True))
  
                  # we move the pointer by 4, because we loaded 4 variables
                  i += 4
              elif 'Conv' in var2.name.split('/')[-2]:
                  # load biases
                  bias = var2
                  bias_shape = bias.shape.as_list()
                  bias_params = np.prod(bias_shape)
                  bias_weights = weights[ptr:ptr +
                                         bias_params].reshape(bias_shape)
                  ptr += bias_params
                  assign_ops.append(
                      tf.assign(bias, bias_weights, validate_shape=True))
  
                  # we loaded 1 variable
                  i += 1
              # we can load weights of conv layer
              shape = var1.shape.as_list()
              num_params = np.prod(shape)
  
              var_weights = weights[ptr:ptr + num_params].reshape(
                  (shape[3], shape[2], shape[0], shape[1]))
              # remember to transpose to column-major
              var_weights = np.transpose(var_weights, (2, 3, 1, 0))
              ptr += num_params
              assign_ops.append(
                  tf.assign(var1, var_weights, validate_shape=True))
              i += 1
  
      return assign_ops
  
  
  def detections_boxes(detections):
      """
      Converts center x, center y, width and height values to coordinates of top left and bottom right points.
  
      :param detections: outputs of YOLO v3 detector of shape (?, 10647, (num_classes + 5))
      :return: converted detections of same shape as input
      """
      center_x, center_y, width, height, attrs = tf.split(
          detections, [1, 1, 1, 1, -1], axis=-1)
      w2 = width / 2
      h2 = height / 2
      x0 = center_x - w2
      y0 = center_y - h2
      x1 = center_x + w2
      y1 = center_y + h2
  
      boxes = tf.concat([x0, y0, x1, y1], axis=-1)
      detections = tf.concat([boxes, attrs], axis=-1, name="output_boxes")
      return detections
  
  
  def _iou(box1, box2):
      """
      Computes Intersection over Union value for 2 bounding boxes
  
      :param box1: array of 4 values (top left and bottom right coords): [x0, y0, x1, x2]
      :param box2: same as box1
      :return: IoU
      """
      b1_x0, b1_y0, b1_x1, b1_y1 = box1
      b2_x0, b2_y0, b2_x1, b2_y1 = box2
  
      int_x0 = max(b1_x0, b2_x0)
      int_y0 = max(b1_y0, b2_y0)
      int_x1 = min(b1_x1, b2_x1)
      int_y1 = min(b1_y1, b2_y1)
  
      int_area = (int_x1 - int_x0) * (int_y1 - int_y0)
  
      b1_area = (b1_x1 - b1_x0) * (b1_y1 - b1_y0)
      b2_area = (b2_x1 - b2_x0) * (b2_y1 - b2_y0)
  
      # we add small epsilon of 1e-05 to avoid division by 0
      iou = int_area / (b1_area + b2_area - int_area + 1e-05)
      return iou
  
  
  def non_max_suppression(predictions_with_boxes, confidence_threshold, iou_threshold=0.4):
      """
      Applies Non-max suppression to prediction boxes.
  
      :param predictions_with_boxes: 3D numpy array, first 4 values in 3rd dimension are bbox attrs, 5th is confidence
      :param confidence_threshold: the threshold for deciding if prediction is valid
      :param iou_threshold: the threshold for deciding if two boxes overlap
      :return: dict: class -> [(box, score)]
      """
      conf_mask = np.expand_dims(
          (predictions_with_boxes[:, :, 4] > confidence_threshold), -1)
      predictions = predictions_with_boxes * conf_mask
  
      result = {}
      for i, image_pred in enumerate(predictions):
          shape = image_pred.shape
          non_zero_idxs = np.nonzero(image_pred)
          image_pred = image_pred[non_zero_idxs]
          image_pred = image_pred.reshape(-1, shape[-1])
  
          bbox_attrs = image_pred[:, :5]
          classes = image_pred[:, 5:]
          classes = np.argmax(classes, axis=-1)
  
          unique_classes = list(set(classes.reshape(-1)))
  
          for cls in unique_classes:
              cls_mask = classes == cls
              cls_boxes = bbox_attrs[np.nonzero(cls_mask)]
              cls_boxes = cls_boxes[cls_boxes[:, -1].argsort()[::-1]]
              cls_scores = cls_boxes[:, -1]
              cls_boxes = cls_boxes[:, :-1]
  
              while len(cls_boxes) > 0:
                  box = cls_boxes[0]
                  score = cls_scores[0]
                  if cls not in result:
                      result[cls] = []
                  result[cls].append((box, score))
                  cls_boxes = cls_boxes[1:]
                  cls_scores = cls_scores[1:]
                  ious = np.array([_iou(box, x) for x in cls_boxes])
                  iou_mask = ious < iou_threshold
                  cls_boxes = cls_boxes[np.nonzero(iou_mask)]
                  cls_scores = cls_scores[np.nonzero(iou_mask)]
  
      return result
  
  
  def load_coco_names(file_name):
      names = {}
      with open(file_name) as f:
          for id, name in enumerate(f):
              names[id] = name
      return names
  
  
  def draw_boxes(boxes, img, cls_names, detection_size):
      draw = ImageDraw.Draw(img)
  
      for cls, bboxs in boxes.items():
          color = tuple(np.random.randint(0, 256, 3))
          for box, score in bboxs:
              box = convert_to_original_size(box, np.array(detection_size),
                                             np.array(img.size))
              draw.rectangle(box, outline=color)
              draw.text(box[:2], '{} {:.2f}%'.format(
                  cls_names[cls], score * 100), fill=color)
  
  
  def convert_to_original_size(box, size, original_size):
      ratio = original_size / size
      box = box.reshape(2, 2) * ratio
      return list(box.reshape(-1))
  