text: from __future__ import absolute_import
  import numpy as np
  import cv2
  import pickle
  import random
  import os
  from . import data_augment
  import itertools
  import keras.utils
  from joblib import Parallel, delayed
  import shutil
  import sys, traceback
  
  
  def union(au, bu, area_intersection):
      area_a = (au[2] - au[0]) * (au[3] - au[1])
      area_b = (bu[2] - bu[0]) * (bu[3] - bu[1])
      area_union = area_a + area_b - area_intersection
      return area_union
  
  
  def intersection(ai, bi):
      x = max(ai[0], bi[0])
      y = max(ai[1], bi[1])
      w = min(ai[2], bi[2]) - x
      h = min(ai[3], bi[3]) - y
      if w < 0 or h < 0:
          return 0
      return w * h
  
  
  def iou(a, b):
      # a and b should be (x1,y1,x2,y2)
  
      if a[0] >= a[2] or a[1] >= a[3] or b[0] >= b[2] or b[1] >= b[3]:
          return 0.0
  
      area_i = intersection(a, b)
      area_u = union(a, b, area_i)
  
      return float(area_i) / float(area_u + 1e-6)
  
  
  def get_new_img_size(width, height, img_min_side=600):
      if width <= height:
          f = float(img_min_side) / float(width)
          resized_height = int(f * height)
          resized_width = img_min_side
      else:
          f = float(img_min_side) / float(height)
          resized_width = int(f * width)
          resized_height = img_min_side
  
      return resized_width, resized_height
  
  
  class SampleSelector:
      def __init__(self, class_count):
          # ignore classes that have zero samples
          self.classes = [b for b in class_count.keys() if class_count[b] > 0]
          self.class_cycle = itertools.cycle(self.classes)
          self.curr_class = next(self.class_cycle)
  
      def skip_sample_for_balanced_class(self, img_data):
  
          class_in_img = False
  
          for bbox in img_data['bboxes']:
  
              cls_name = bbox['class']
  
              if cls_name == self.curr_class:
                  class_in_img = True
                  self.curr_class = next(self.class_cycle)
                  break
  
          return class_in_img
  
  
  def calc_rpn(C, img_data, width, height, resized_width, resized_height, img_length_calc_function):
      downscale = float(C.rpn_stride)
      anchor_sizes = C.anchor_box_scales
      anchor_ratios = C.anchor_box_ratios
      num_anchors = len(anchor_sizes) * len(anchor_ratios)
  
      # calculate the output map size based on the network architecture
  
      (output_width, output_height) = img_length_calc_function(resized_width, resized_height)
  
      n_anchratios = len(anchor_ratios)
  
      # initialise empty output objectives
      y_rpn_overlap = np.zeros((output_height, output_width, num_anchors)).astype(int)
      y_is_box_valid = np.zeros((output_height, output_width, num_anchors)).astype(int)
      y_rpn_regr = np.zeros((output_height, output_width, num_anchors * 4))
  
      num_bboxes = len(img_data['bboxes'])
  
      num_anchors_for_bbox = np.zeros(num_bboxes).astype(int)
      best_anchor_for_bbox = -1 * np.ones((num_bboxes, 4)).astype(int)
      best_iou_for_bbox = np.zeros(num_bboxes).astype(np.float32)
      best_x_for_bbox = np.zeros((num_bboxes, 4))
      best_dx_for_bbox = np.zeros((num_bboxes, 4))
  
      # get the GT box coordinates, and resize to account for image resizing
      gta = np.zeros((num_bboxes, 4))
      for bbox_num, bbox in enumerate(img_data['bboxes']):
          # get the GT box coordinates, and resize to account for image resizing
          gta[bbox_num, 0] = bbox['x1'] * (resized_width / float(width))
          gta[bbox_num, 1] = bbox['x2'] * (resized_width / float(width))
          gta[bbox_num, 2] = bbox['y1'] * (resized_height / float(height))
          gta[bbox_num, 3] = bbox['y2'] * (resized_height / float(height))
  
      # rpn ground truth
  
      for anchor_size_idx in range(len(anchor_sizes)):
          for anchor_ratio_idx in range(n_anchratios):
              anchor_x = anchor_sizes[anchor_size_idx] * anchor_ratios[anchor_ratio_idx][0]
              anchor_y = anchor_sizes[anchor_size_idx] * anchor_ratios[anchor_ratio_idx][1]
  
              for ix in range(output_width):
                  # x-coordinates of the current anchor box
                  x1_anc = downscale * (ix + 0.5) - anchor_x / 2
                  x2_anc = downscale * (ix + 0.5) + anchor_x / 2
  
                  # ignore boxes that go across image boundaries
                  if x1_anc < 0 or x2_anc > resized_width:
                      continue
  
                  for jy in range(output_height):
  
                      # y-coordinates of the current anchor box
                      y1_anc = downscale * (jy + 0.5) - anchor_y / 2
                      y2_anc = downscale * (jy + 0.5) + anchor_y / 2
  
                      # ignore boxes that go across image boundaries
                      if y1_anc < 0 or y2_anc > resized_height:
                          continue
  
                      # bbox_type indicates whether an anchor should be a target
                      bbox_type = 'neg'
  
                      # this is the best IOU for the (x,y) coord and the current anchor
                      # note that this is different from the best IOU for a GT bbox
                      best_iou_for_loc = 0.0
  
                      for bbox_num in range(num_bboxes):
  
                          # get IOU of the current GT box and the current anchor box
                          curr_iou = iou([gta[bbox_num, 0], gta[bbox_num, 2], gta[bbox_num, 1], gta[bbox_num, 3]],
                                         [x1_anc, y1_anc, x2_anc, y2_anc])
                          # calculate the regression targets if they will be needed
                          if curr_iou > best_iou_for_bbox[bbox_num] or curr_iou > C.rpn_max_overlap:
                              cx = (gta[bbox_num, 0] + gta[bbox_num, 1]) / 2.0
                              cy = (gta[bbox_num, 2] + gta[bbox_num, 3]) / 2.0
                              cxa = (x1_anc + x2_anc) / 2.0
                              cya = (y1_anc + y2_anc) / 2.0
  
                              tx = (cx - cxa) / (x2_anc - x1_anc)
                              ty = (cy - cya) / (y2_anc - y1_anc)
                              # calculate log of tw and th later
                              tw = 1.0 * (gta[bbox_num, 1] - gta[bbox_num, 0]) / (x2_anc - x1_anc)
                              th = 1.0 * (gta[bbox_num, 3] - gta[bbox_num, 2]) / (y2_anc - y1_anc)
  
                          if img_data['bboxes'][bbox_num]['class'] != 'bg':
  
                              # all GT boxes should be mapped to an anchor box, so we keep track of which anchor box was best
                              if curr_iou > best_iou_for_bbox[bbox_num]:
                                  best_anchor_for_bbox[bbox_num] = [jy, ix, anchor_ratio_idx, anchor_size_idx]
                                  best_iou_for_bbox[bbox_num] = curr_iou
                                  best_x_for_bbox[bbox_num, :] = [x1_anc, x2_anc, y1_anc, y2_anc]
                                  best_dx_for_bbox[bbox_num, :] = [tx, ty, tw, th]
  
                              # we set the anchor to positive if the IOU is >0.7 (it does not matter if there was another better box, it just indicates overlap)
                              if curr_iou > C.rpn_max_overlap:
                                  bbox_type = 'pos'
                                  num_anchors_for_bbox[bbox_num] += 1
                                  # we update the regression layer target if this IOU is the best for the current (x,y) and anchor position
                                  if curr_iou > best_iou_for_loc:
                                      best_iou_for_loc = curr_iou
                                      best_regr = (tx, ty, tw, th)
  
                              # if the IOU is >0.3 and <0.7, it is ambiguous and no included in the objective
                              if C.rpn_min_overlap < curr_iou < C.rpn_max_overlap:
                                  # gray zone between neg and pos
                                  if bbox_type != 'pos':
                                      bbox_type = 'neutral'
  
                      # turn on or off outputs depending on IOUs
                      if bbox_type == 'neg':
                          y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1
                          y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0
                      elif bbox_type == 'neutral':
                          y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0
                          y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0
                      elif bbox_type == 'pos':
                          y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1
                          y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1
                          start = 4 * (anchor_ratio_idx + n_anchratios * anchor_size_idx)
                          y_rpn_regr[jy, ix, start:start + 2] = best_regr[0:2]
                          y_rpn_regr[jy, ix, start + 2:start + 4] = np.log(best_regr[2:])
  
      # we ensure that every bbox has at least one positive RPN region
  
      for idx in range(num_anchors_for_bbox.shape[0]):
          if num_anchors_for_bbox[idx] == 0:
              # no box with an IOU greater than zero ...
              if best_anchor_for_bbox[idx, 0] == -1:
                  continue
              y_is_box_valid[
                  best_anchor_for_bbox[idx, 0], best_anchor_for_bbox[idx, 1], best_anchor_for_bbox[
                      idx, 2] + n_anchratios *
                  best_anchor_for_bbox[idx, 3]] = 1
              y_rpn_overlap[
                  best_anchor_for_bbox[idx, 0], best_anchor_for_bbox[idx, 1], best_anchor_for_bbox[
                      idx, 2] + n_anchratios *
                  best_anchor_for_bbox[idx, 3]] = 1
              start = 4 * (best_anchor_for_bbox[idx, 2] + n_anchratios * best_anchor_for_bbox[idx, 3])
              y_rpn_regr[
              best_anchor_for_bbox[idx, 0], best_anchor_for_bbox[idx, 1], start:start + 2] = best_dx_for_bbox[idx, 0:2]
              y_rpn_regr[
              best_anchor_for_bbox[idx, 0], best_anchor_for_bbox[idx, 1], start + 2:start + 4] = np.log(
                  best_dx_for_bbox[idx, 2:4])
  
      y_rpn_overlap = np.transpose(y_rpn_overlap, (2, 0, 1))
      y_rpn_overlap = np.expand_dims(y_rpn_overlap, axis=0)
  
      y_is_box_valid = np.transpose(y_is_box_valid, (2, 0, 1))
      y_is_box_valid = np.expand_dims(y_is_box_valid, axis=0)
  
      y_rpn_regr = np.transpose(y_rpn_regr, (2, 0, 1))
      y_rpn_regr = np.expand_dims(y_rpn_regr, axis=0)
  
      pos_locs = np.where(np.logical_and(y_rpn_overlap[0, :, :, :] == 1, y_is_box_valid[0, :, :, :] == 1))
      neg_locs = np.where(np.logical_and(y_rpn_overlap[0, :, :, :] == 0, y_is_box_valid[0, :, :, :] == 1))
  
      num_pos = len(pos_locs[0])
  
      # one issue is that the RPN has many more negative than positive regions, so we turn off some of the negative
      # regions. We also limit it to 256 regions.
      num_regions = 256
  
      if len(pos_locs[0]) > int(num_regions / 2):
          val_locs = random.sample(range(len(pos_locs[0])), k=int(len(pos_locs[0]) - num_regions / 2))
          y_is_box_valid[0, pos_locs[0][val_locs], pos_locs[1][val_locs], pos_locs[2][val_locs]] = 0
          num_pos = num_regions / 2
  
      if len(neg_locs[0]) + num_pos > num_regions:
          val_locs = random.sample(range(len(neg_locs[0])), k=int(len(neg_locs[0]) - num_pos))
          y_is_box_valid[0, neg_locs[0][val_locs], neg_locs[1][val_locs], neg_locs[2][val_locs]] = 0
  
      y_rpn_cls = np.concatenate([y_is_box_valid, y_rpn_overlap], axis=1)
      y_rpn_regr = np.concatenate([np.repeat(y_rpn_overlap, 4, axis=1), y_rpn_regr], axis=1)
  
      return y_rpn_cls, y_rpn_regr
  
  
  def save_obj(obj, name):
      with open(name + '.pkl', 'wb') as f:
          pickle.dump(obj, f, pickle.HIGHEST_PROTOCOL)
  
  
  def load_obj(name):
      with open(name + '.pkl', 'rb') as f:
          return pickle.load(f)
  
  
  class anchor_gt(keras.utils.Sequence):
      def __init__(self, all_img_data, class_count, C, img_length_calc_function, backend, mode='train',
                   do_preprocess=True):
          print(f"preprocessing data for mode {mode}")
          self._maxImage = len(all_img_data)
          self._idx = np.array([i for i in range(self._maxImage)])
          self.mode = mode
          self.img_length_calc_function = img_length_calc_function
          self.C = C
          self.backend = backend
          self.iterIdx = 0
          self._shuffle()
          self.all_img_data = all_img_data
          self.preprocessed = do_preprocess
          self.sample_selector = SampleSelector(class_count)
          if do_preprocess:
              self.preprocess(all_img_data)
          print("preprocess finished")
  
      def preprocess(self, all_img_data):
          shutil.rmtree("tmpdata\\" + self.mode, ignore_errors=True)
          self.__process(all_img_data)
  
      def __len__(self):
          return self._maxImage
  
      def _inc_idx(self):
          self.iterIdx += 1
          if self.iterIdx >= len(self):
              self._shuffle()
  
      def __next__(self):
          while True:
              try:
                  if self.C.balanced_classes and self.sample_selector.skip_sample_for_balanced_class(
                          self.all_img_data[self._idx[self.iterIdx]]):
                      self._inc_idx()
                  res = self[self.iterIdx]
                  self._inc_idx()
                  return res
              except Exception as e:
                  print("------------------------------------")
                  print(e)
                  traceback.print_exc(file=sys.stdout)
                  self._inc_idx()
                  continue
  
      def __iter__(self):
          return self
  
      def _shuffle(self):
          try:
              if self.mode == 'train':
                  print("shuffling")
                  np.random.shuffle(self._idx)
              self.iterIdx = 0
          except Exception as e:
              print("------------------------------------")
              print(e)
              traceback.print_exc(file=sys.stdout)
              raise
  
      def __del__(self):
          shutil.rmtree("tmpdata\\" + self.mode, ignore_errors=True)
  
      def __process(self, all_img_data):
          if not os.path.isdir("tmpdata"):
              os.mkdir("tmpdata")
          if not os.path.isdir("tmpdata\\" + self.mode):
              os.mkdir("tmpdata\\" + self.mode)
          with Parallel(n_jobs=-1, verbose=5) as parallel:
              parallel(delayed(self.__process_single)(idx, img_data, self.img_length_calc_function, self.C, self.backend,
                                                      self.mode) for idx, img_data in enumerate(all_img_data))
  
      @staticmethod
      def __process_single(idx, img_data, img_length_calc_function, C, backend, mode, save=True):
          try:
              # read in image, and optionally add augmentation
              if mode == 'train':
                  img_data_aug, x_img = data_augment.augment(img_data, C, augment=True)
              else:
                  img_data_aug, x_img = data_augment.augment(img_data, C, augment=False)
  
              (width, height) = (img_data_aug['width'], img_data_aug['height'])
              (rows, cols, _) = x_img.shape
  
              assert cols == width
              assert rows == height
  
              # get image dimensions for resizing
              (resized_width, resized_height) = get_new_img_size(width, height, C.im_size)
  
              # resize the image so that smalles side is length = 600px
              x_img = cv2.resize(x_img, (resized_width, resized_height), interpolation=cv2.INTER_CUBIC)
  
              y_rpn_cls, y_rpn_regr = calc_rpn(C, img_data_aug, width, height, resized_width, resized_height,
                                               img_length_calc_function)
  
              # Zero-center by mean pixel, and preprocess image
  
              x_img = x_img[:, :, (2, 1, 0)]  # BGR -> RGB
              x_img = x_img.astype(np.float32)
              x_img[:, :, 0] -= C.img_channel_mean[0]
              x_img[:, :, 1] -= C.img_channel_mean[1]
              x_img[:, :, 2] -= C.img_channel_mean[2]
              x_img /= C.img_scaling_factor
  
              x_img = np.transpose(x_img, (2, 0, 1))
              x_img = np.expand_dims(x_img, axis=0)
  
              y_rpn_regr[:, y_rpn_regr.shape[1] // 2:, :, :] *= C.std_scaling
  
              if backend == 'tf':
                  x_img = np.transpose(x_img, (0, 2, 3, 1))
                  y_rpn_cls = np.transpose(y_rpn_cls, (0, 2, 3, 1))
                  y_rpn_regr = np.transpose(y_rpn_regr, (0, 2, 3, 1))
              obj = (x_img, [y_rpn_cls, y_rpn_regr], img_data_aug)
              if save:
                  save_obj(obj, f"tmpdata\\{mode}\\{idx}")
              else:
                  return obj
          except Exception as e:
              print("------------------------------------")
              print(e)
              traceback.print_exc(file=sys.stdout)
              raise
  
      def __getitem__(self, idx):
          try:
              if self.preprocessed:
                  obj = load_obj(f"tmpdata\\{self.mode}\\{self._idx[idx]}")
              else:
                  # print("get ", self.all_img_data[self._idx[idx]])
                  obj = self.__process_single(0, self.all_img_data[self._idx[idx]], self.img_length_calc_function,
                                              self.C, self.backend, self.mode, save=False)
          except Exception as e:
              print("------------------------------------")
              print(e)
              traceback.print_exc(file=sys.stdout)
              raise
          return obj
  