text: #coding=utf-8
  '''
  @author: dengdan
  '''
  import cv2
  import numpy as np
  import logging
  import math
  from . import event
  import util
  
  IMREAD_GRAY = 0
  IMREAD_COLOR = 1
  IMREAD_UNCHANGED = -1
  
  
  
  COLOR_WHITE =(255, 255, 255)
  COLOR_BLACK = (0, 0, 0)
  COLOR_GREEN = (0, 255, 0)
  
  COLOR_RGB_RED = (255, 0, 0)
  COLOR_BGR_RED = (0, 0, 255)
  
  COLOR_RGB_BLUE = (0, 0, 255)
  COLOR_BGR_BLUE = (255, 0, 0)
  
  COLOR_RGB_YELLOW = (255, 255, 0)
  COLOR_BGR_YELLOW = (0, 255, 255)
  
  
  COLOR_RGB_GRAY = (47, 79, 79)
  
  COLOR_RGB_PINK = (255, 192, 203)
  def imread(path, rgb = False, mode = cv2.IMREAD_COLOR):
      path = util.io.get_absolute_path(path)
      img = cv2.imread(path, mode)
      if img is None:
          raise IOError('File not found:%s'%(path))
          
      if rgb:
          img = bgr2rgb(img)
      return img
  
  def imshow(winname, img, block = True, position = None, maximized = False, rgb = False):
      if isinstance(img, str):
          img = imread(path = img)
      
      cv2.namedWindow(winname, cv2.WINDOW_NORMAL)
      if rgb:
          img = rgb2bgr(img)
      cv2.imshow(winname, img)
      if position is not None:
  #         cv2.moveWindow(winname, position[0], position[1])
          move_win(winname, position)
      
      if maximized:
          maximize_win(winname)  
          
          
      if block:
  #         cv2.waitKey(0)
          event.wait_key(" ")
          cv2.destroyAllWindows()
  
  
  def imwrite(path, img, rgb = False):
      if rgb:
          img = rgb2bgr(img)
      path = util.io.get_absolute_path(path)
      util.io.make_parent_dir(path)
      cv2.imwrite(path, img)
  
  def move_win(winname, position = (0, 0)):
      """
      move pyplot window
      """
      cv2.moveWindow(winname, position[0], position[1])
  
  def maximize_win(winname):
      cv2.setWindowProperty(winname, cv2.WND_PROP_FULLSCREEN, True);
  
  def eq_color(target, color):
      for i, c in enumerate(color):
          if target[i] != color[i]:
              return False
      return True
      
  def is_white(color):
      for c in color:
          if c < 255:
              return False
      return True
      
  def black(shape):
      if len(np.shape(shape)) >= 2:
          shape = get_shape(shape)
      shape = [int(v) for v in shape]
      return np.zeros(shape, np.uint8)
      
  def white(shape, value = 255):
      if len(np.shape(shape)) >= 2:
          shape = get_shape(shape)
      return np.ones(shape, np.uint8) * np.uint8(value)
      
  def bgr2rgb(img):
      return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  
  def rgb2bgr(img):
      return cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
  
  
  def rgb2gray(img):
      return cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
  
  def bgr2gray(img):
      return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  
  
  def ds_size(image_size, kernel_size, stride):
      """calculate the size of downsampling result"""
      image_x, image_y = image_size
      
  
      kernel_x, kernel_y = kernel_size
      stride_x, stride_y = stride
      
      def f(iw, kw, sw):
          return int(np.floor((iw - kw) / sw) + 1)
      
      output_size = (f(image_x, kernel_x, stride_x), f(image_y, kernel_y, stride_y))
      return output_size
  
  
      
  def get_roi(img, p1, p2):
      """
      extract region of interest from an image.
      p1, p2: two tuples standing for two opposite corners of the rectangle bounding the roi. 
      Their order is arbitrary.
      """
      x1, y1 = p1
      x2, y2 = p2
      
      x_min = min([x1, x2])
      y_min = min([y1, y2])
      x_max = max([x1, x2]) + 1
      y_max = max([y1, y2]) + 1
      
      return img[y_min: y_max, x_min: x_max]
      
  def rectangle(img, left_up, right_bottom, color, border_width = 1):
      left_up = (int(left_up[0]), int(left_up[1]))
      right_bottom = (int(right_bottom[0]), int(right_bottom[1]))
      cv2.rectangle(img, left_up, right_bottom, color, border_width)
  
  
  def circle(img, center, r, color, border_width = 1):
      center = (int(center[0]), int(center[1]))
      cv2.circle(img, center, r, color, border_width)
  
  def render_points(img, points, color):
      for p in points:
          x, y = p
          img[y][x] = color
  
      
  def draw_contours(img, contours, idx = -1, color = 1, border_width = 1):
  #     img = img.copy()
      cv2.drawContours(img, contours, idx, color, border_width)
      return img
  
  def get_contour_rect_box(contour):
      x,y,w,h = cv2.boundingRect(contour)
      return x, y, w, h
  
  def get_contour_region_in_rect(img, contour):
      x, y, w, h = get_contour_rect_box(contour)
      lu, rb = (x, y), (x + w, y + h)
      return get_roi(img, lu, rb)
  
  def get_contour_min_area_box(contour):
      rect = cv2.minAreaRect(contour)
      box = cv2.cv.BoxPoints(rect)
      box = np.int0(box)
      return box
  
  def get_contour_region_in_min_area_rect(img, cnt):
      # find the min area rect of contour
      rect = cv2.minAreaRect(cnt)
      angle = rect[-1]
      box = cv2.cv.BoxPoints(rect)
      box_cnt = points_to_contour(box)
      
      # find the rectangle containing box_cnt, and set it as ROI
      outer_rect = get_contour_rect_box(box_cnt)
      x, y, w, h = outer_rect
      img = get_roi(img, (x, y), (x + w,  y + h))
      box = [(ox - x, oy - y) for (ox, oy) in box]
      
      # rotate ROI and corner points
      rows, cols = get_shape(img)
      M = cv2.getRotationMatrix2D((cols/2,rows/2), angle, scale = 1)
      dst = cv2.warpAffine(img,M,(cols,rows))
      bar_xy = np.hstack((box, np.ones((4, 1))))
      new_corners = np.dot(M, np.transpose(bar_xy))
      new_corners = util.dtype.int(np.transpose(new_corners))
  #     cnt = points_to_contour(new_corners)
      
      xs = new_corners[:, 0]
      ys = new_corners[:, 1]
      lu = (min(xs), min(ys))
      rb = (max(xs), max(ys))
      return get_roi(dst, lu, rb)
  
  
  def contour_to_points(contour):
      return np.asarray([c[0] for c in contour])
  
  
  def points_to_contour(points):
      contours = [[list(p)]for p in points]
      return np.asarray(contours, dtype = np.int32)
  
  def points_to_contours(points):
      return np.asarray([points_to_contour(points)])
  
  def get_contour_region_iou(I, cnt1, cnt2):
      """
      calculate the iou of two contours
      """
      mask1 = util.img.black(I)
      draw_contours(mask1, [cnt1], color = 1, border_width = -1)
      
      mask2 = util.img.black(I)
      draw_contours(mask2, [cnt2], color = 1, border_width = -1)
      
      union_mask = ((mask1 + mask2) >=1) * 1
      intersect_mask = (mask1 * mask2 >= 1) * 1
      
      return np.sum(intersect_mask) * 1.0 / np.sum(union_mask)
  
      
  def fill_bbox(img, box, color = 1):
      """
      filling a bounding box with color.
      box: a list of 4 points, in clockwise order, as the four vertice of a bounding box
      """
      util.test.assert_equal(np.shape(box), (4, 2))
      cnt = to_contours(box)
      draw_contours(img, cnt, color = color, border_width = -1)
      
  def get_rect_points(left_up, right_bottom):
      """
      given the left up and right bottom points of a rectangle, return its four points
      """
      right_bottom, left_up = np.asarray(right_bottom), np.asarray(left_up)
      w, h = right_bottom - left_up
      x, y = left_up
      points = [(x, y), (x + w, y), (x + w, y + h), (x, y + h)]
      return points
      
  def rect_perimeter(left_up, right_bottom):
      """
      calculate the perimeter of the rectangle described by its left-up and right-bottom point.
      """
      return sum(np.asarray(right_bottom) -  np.asarray(left_up)) * 2
  
  def rect_area(left_up, right_bottom):
      wh = np.asarray(right_bottom) - np.asarray(left_up) + 1
      return np.prod(wh)
      
  def apply_mask(img, mask):
      """
      the img will be masked in place. 
      """
      c = np.shape(img)[-1]
      for i in range(c):
          img[:, :, i] = img[:, :, i] * mask 
      return img
      
  def get_shape(img):
      """
      return the height and width of an image
      """
      return np.shape(img)[0:2]
  
  def get_wh(img):
      return np.shape(img)[0:2][::-1]
  
  def get_value(img, x, y = None):
      if y == None:
          y = x[1]
          x = x[0]
      
      return img[y][x]        
      
  def set_value(img, xy, val):
      x, y = xy
      img[y][x] = val
  
  
  def filter2D(img, kernel):
      dst = cv2.filter2D(img, -1, kernel)
      return dst
  
  def average_blur(img, shape = (5, 5)):
      return cv2.blur(img, shape)
  
  def gaussian_blur(img, shape = (5, 5), sigma = 0):
      # sigma --> sigmaX, sigmaY
      blur = cv2.GaussianBlur(img,shape, sigma)
      return blur
  
  def bilateral_blur(img, d = 9, sigmaColor = 75, sigmaSpace = 75):
      dst = cv2.bilateralFilter(img, d, sigmaColor, sigmaSpace)
      return dst
  
  BLUR_AVERAGE =  'average'
  BLUR_GAUSSIAN = 'gaussian'
  BLUR_BILATERAL = 'bilateral'
  
  
  _blur_dict = {
                BLUR_AVERAGE: average_blur,
                BLUR_GAUSSIAN: gaussian_blur,
                BLUR_BILATERAL: bilateral_blur
  }
  
  def blur(img, blur_type):
      fn = _blur_dict[blur_type]
      return fn(img)
      
  def put_text(img, text, pos, scale = 1, color = COLOR_WHITE, thickness = 1):
      pos = np.int32(pos)
      font = cv2.FONT_HERSHEY_SIMPLEX
      cv2.putText(img = img, text = text, org = tuple(pos), fontFace = font,  fontScale = scale,  color = color, thickness = thickness)
  
  def resize(img, f = None, fx = None, fy = None, size = None, interpolation = cv2.INTER_LINEAR):
      """
      size: (w, h)
      """
      h, w = get_shape(img)
      if fx != None and fy != None:
          return cv2.resize(img, None, fx = fx, fy = fy, interpolation = interpolation)
          
      if size != None:
          size = util.dtype.int(size)
  #         size = (size[1], size[0])
          size = tuple(size)
          return cv2.resize(img, size, interpolation = interpolation)
      
      return cv2.resize(img, None, fx = f, fy = f, interpolation = interpolation)
  
  def translate(img, delta_x, delta_y, size = None):
      M = np.float32([[1,0, delta_x],[0,1, delta_y]])
      if size == None:
          size = get_wh(img)
      
      dst = cv2.warpAffine(img,M, size)
      return dst
  
  
  def rotate_about_center(src, angle, scale=1.):
      """https://www.oschina.net/translate/opencv-rotation"""
      w = src.shape[1]
      h = src.shape[0]
      rangle = np.deg2rad(angle)  # angle in radians
      # now calculate new image width and height
      nw = (abs(np.sin(rangle)*h) + abs(np.cos(rangle)*w))*scale
      nh = (abs(np.cos(rangle)*h) + abs(np.sin(rangle)*w))*scale
      # ask OpenCV for the rotation matrix
      rot_mat = cv2.getRotationMatrix2D((nw*0.5, nh*0.5), angle, scale)
      # calculate the move from the old center to the new center combined
      # with the rotation
      rot_move = np.dot(rot_mat, np.array([(nw-w)*0.5, (nh-h)*0.5,0]))
      # the move only affects the translation, so update the translation
      # part of the transform
      rot_mat[0,2] += rot_move[0]
      rot_mat[1,2] += rot_move[1]
      return cv2.warpAffine(src, rot_mat, (int(math.ceil(nw)), int(math.ceil(nh))), flags=cv2.INTER_LANCZOS4), rot_mat
  
  
  def get_rect_iou(rects1, rects2):
      """
      calculate the iou between rects1 and rects2
      each rect consists of four points:[min_x, min_y, max_x, max_y]
      return: a iou matrix, len(rects1) * len(rects2)
      """
      rects1, rects2 = np.asarray(rects1), np.asarray(rects2)
  
      def _to_matrix(p, ps):
          p = np.ones((len(ps), 1)) * p
          ps = np.reshape(ps, (len(ps), 1))
          temp =np.hstack([p, ps])
          return temp
      
      def _get_max(p, ps):
          return np.max(_to_matrix(p, ps), axis = 1)
      
      def _get_min(p, ps):
          return np.min(_to_matrix(p, ps), axis = 1)
   
      
      def _get_area(rect):
          w, h = rect[:, 2] - rect[:, 0] + 1.0 , rect[:, 3] - rect[:, 1] + 1.0
          return w * h
  
      def _get_inter(rect1, rects2):
              x1 = _get_max(rect1[0], rects2[:, 0])
              y1 = _get_max(rect1[1], rects2[:, 1])
              
              x2 = _get_min(rect1[2], rects2[:, 2])
              y2 = _get_min(rect1[3], rects2[:, 3])
          
              w,h = x2-x1 +1, y2 - y1 + 1
              areas = w * h
              areas[np.where(w < 0)] = 0
              areas[np.where(h < 0)] = 0
              return areas
              
      area2 = _get_area(rects2)
      area1 = _get_area(rects1)
      iou = np.zeros((len(rects1), len(rects2)))
      for ri in range(len(rects1)):
          inter = _get_inter(rects1[ri, :], rects2)
          union = area1[ri] + area2 - inter
          iou[ri, :] = np.transpose( inter / union)
      return iou
  
  def find_contours(mask):
      mask = np.asarray(mask, dtype = np.uint8)
      mask = mask.copy()
      contours, _ = cv2.findContours(mask, mode = cv2.RETR_CCOMP, 
                                     method = cv2.CHAIN_APPROX_SIMPLE)
      return contours
  
  def find_two_level_contours(mask):
      mask = mask.copy()
      contours, tree = cv2.findContours(mask, mode = cv2.RETR_CCOMP, 
                                    method = cv2.CHAIN_APPROX_SIMPLE)
      return contours, tree
      
      
  def is_in_contour(point, cnt):
      """tell whether a point is in contour or not. 
              In-contour here includes both the 'in contour' and 'on contour' cases.
         point:(x, y)
         cnt: a cv2 contour
      """
      # doc of pointPolygonTest: http://docs.opencv.org/2.4/modules/imgproc/doc/structural_analysis_and_shape_descriptors.html?highlight=pointpolygontest#cv.PointPolygonTest
      # the last argument means only tell if in or not, without calculating the shortest distance
      in_cnt = cv2.pointPolygonTest(cnt, point, False)
      return in_cnt >= 0;
  
  def convex_hull(contour):
      hull = cv2.convexHull(contour, returnPoints=1)
      return hull
      
  def random_color_3():
      c = util.rand.randint(low = 0, high = 255, shape = (3, ))
  #     c = np.uint8(c)
      return c
  
  def get_contour_area(cnt):
      return cv2.contourArea(cnt)
      
  def is_valid_jpg(jpg_file):
      with open(jpg_file, 'rb') as f:     
          f.seek(-2, 2)
          return f.read() == '\xff\xd9'
  
  
  
  def rotate_point_by_90(x, y, k, w = 1.0, h = 1.0):
      """
      Rotate a point xy on an image by k * 90
      degrees.
      Params:
          x, y: a point, (x, y). If not normalized within 0 and 1, the 
              width and height of the image should be specified clearly.
          w, h: the width and height of image
          k: k * 90 degrees will be rotated
      """
      k = k % 4
      
      if k == 0:
          return x, y
      elif k == 1:
          return y, w - x
      elif k == 2:
          return w - x, h - y
      elif k == 3:
          return h - y, x
      
      
  def min_area_rect(xs, ys):
      """
      Args:
          xs: numpy ndarray with shape=(N,4). N is the number of oriented bboxes. 4 contains [x1, x2, x3, x4]
          ys: numpy ndarray with shape=(N,4), [y1, y2, y3, y4]
              Note that [(x1, y1), (x2, y2), (x3, y3), (x4, y4)] can represent an oriented bbox.
      Return:
          the oriented rects sorrounding the box, in the format:[cx, cy, w, h, theta]. 
      """
      xs = np.asarray(xs, dtype = np.float32)
      ys = np.asarray(ys, dtype = np.float32)
          
      num_rects = xs.shape[0]
      box = np.empty((num_rects, 5))#cx, cy, w, h, theta
      for idx in xrange(num_rects):
          points = zip(xs[idx, :], ys[idx, :])
          cnt = points_to_contour(points)
          rect = cv2.minAreaRect(cnt)
          cx, cy = rect[0]
          w, h = rect[1]
          theta = rect[2]
          box[idx, :] = [cx, cy, w, h, theta]
      
      box = np.asarray(box, dtype = xs.dtype)
      return box
  