# coding:utf-8
import glob
import csv
import cv2
import time
import os
import numpy as np
import scipy.optimize
from geo_map_cython_lib import gen_geo_map
from shapely.geometry import Polygon
from PIL import Image, ImageEnhance
import tensorflow as tf

from data_util import GeneratorEnqueuer

tf.app.flags.DEFINE_string('training_data_path', '/data1/UAVDT/UAV-benchmark-MR/UAVDT_ForGAnet',
                           'training dataset to use')

tf.app.flags.DEFINE_string('PerlinNoise_data_path', 'PerlinNoiseMaps',
                           'PerlinNoise dataset to use')

tf.app.flags.DEFINE_integer('max_image_large_side', 1280,
                            'max image size of training')
tf.app.flags.DEFINE_integer('max_text_size', 800,
                            'if the text in the input image is bigger than this, then we resize'
                            'the image according to this')
tf.app.flags.DEFINE_integer('min_text_size', 6,
                            'if the text size is smaller than this, we ignore it during training')
tf.app.flags.DEFINE_float('min_crop_side_ratio', 0.1,
                          'when doing random crop from input image, the'
                          'min length of min(H, W')
tf.app.flags.DEFINE_string('geometry', 'RBOX',
                           'which geometry to generate, RBOX or QUAD')

FLAGS = tf.app.flags.FLAGS


def get_images():
    files = []
    for ext in ['jpg', 'png', 'jpeg']:
        files.extend(glob.glob(
            os.path.join(FLAGS.training_data_path, '*.{}'.format(ext))))
    return files


def load_annoataion(p):
    '''
    load annotation from the text file
    :param p:
    :return:
    '''
    text_polys = []
    text_tags = []
    if not os.path.exists(p):
        return np.array(text_polys, dtype=np.float32)
    with open(p, 'r') as f:
        reader = csv.reader(f)
        for line in reader:
            label = line[-1]
            # strip BOM. \ufeff for python3,  \xef\xbb\bf for python2
            line = [i.strip('\ufeff').strip('\xef\xbb\xbf') for i in line]

            x1, y1, x2, y2, x3, y3, x4, y4 = list(map(float, line[:8]))
            text_polys.append([[x1, y1], [x2, y2], [x3, y3], [x4, y4]])
            if label == '*' or label == '###':
                text_tags.append(True)
            else:
                text_tags.append(False)
        return np.array(text_polys, dtype=np.float32), np.array(text_tags, dtype=np.bool)


def polygon_area(poly):
    '''
    compute area of a polygon
    :param poly:
    :return:
    '''
    edge = [
        (poly[1][0] - poly[0][0]) * (poly[1][1] + poly[0][1]),
        (poly[2][0] - poly[1][0]) * (poly[2][1] + poly[1][1]),
        (poly[3][0] - poly[2][0]) * (poly[3][1] + poly[2][1]),
        (poly[0][0] - poly[3][0]) * (poly[0][1] + poly[3][1])
    ]
    return np.sum(edge) / 2.


def check_and_validate_polys(polys, tags, xxx_todo_changeme, a):
    '''
    check so that the text poly is in the same direction,
    and also filter some invalid polygons
    :param polys:
    :param tags:
    :return:
    '''
    (h, w) = xxx_todo_changeme
    if polys.shape[0] == 0:
        return polys, tags
    polys[:, :, 0] = np.clip(polys[:, :, 0], 0, w - 1)
    polys[:, :, 1] = np.clip(polys[:, :, 1], 0, h - 1)

    validated_polys = []
    validated_tags = []
    for poly, tag in zip(polys, tags):
        p_area = polygon_area(poly)
        if abs(p_area) < 1:
            # print poly
            print(a)
            print(poly)
            print(tag)
            print('invalid poly')
            continue
        if p_area > 0:
            print('poly in wrong direction')
            poly = poly[(0, 3, 2, 1), :]
        validated_polys.append(poly)
        validated_tags.append(tag)
    return np.array(validated_polys), np.array(validated_tags)


def crop_area(im, polys, tags, crop_background=False, max_tries=50):
    '''
    make random crop from the input image
    :param im:
    :param polys:
    :param tags:
    :param crop_background:
    :param max_tries:
    :return:
    '''
    h, w, _ = im.shape
    pad_h = h // 10
    pad_w = w // 10
    h_array = np.zeros((h + pad_h * 2), dtype=np.int32)
    w_array = np.zeros((w + pad_w * 2), dtype=np.int32)
    for poly in polys:
        poly = np.round(poly, decimals=0).astype(np.int32)
        minx = np.min(poly[:, 0])
        maxx = np.max(poly[:, 0])
        w_array[minx + pad_w:maxx + pad_w] = 1
        miny = np.min(poly[:, 1])
        maxy = np.max(poly[:, 1])
        h_array[miny + pad_h:maxy + pad_h] = 1
    # ensure the cropped area not across a text
    h_axis = np.where(h_array == 0)[0]
    w_axis = np.where(w_array == 0)[0]
    if len(h_axis) == 0 or len(w_axis) == 0:
        return im, polys, tags
    for i in range(max_tries):
        xx = np.random.choice(w_axis, size=2)
        xmin = np.min(xx) - pad_w
        xmax = np.max(xx) - pad_w
        xmin = np.clip(xmin, 0, w - 1)
        xmax = np.clip(xmax, 0, w - 1)
        yy = np.random.choice(h_axis, size=2)
        ymin = np.min(yy) - pad_h
        ymax = np.max(yy) - pad_h
        ymin = np.clip(ymin, 0, h - 1)
        ymax = np.clip(ymax, 0, h - 1)
        if xmax - xmin < FLAGS.min_crop_side_ratio * w or ymax - ymin < FLAGS.min_crop_side_ratio * h:
            # area too small
            continue
        if polys.shape[0] != 0:
            poly_axis_in_area = (polys[:, :, 0] >= xmin) & (polys[:, :, 0] <= xmax) \
                                & (polys[:, :, 1] >= ymin) & (polys[:, :, 1] <= ymax)
            selected_polys = np.where(np.sum(poly_axis_in_area, axis=1) == 4)[0]
        else:
            selected_polys = []
        if len(selected_polys) == 0:
            # no text in this area
            if crop_background:
                return im[ymin:ymax + 1, xmin:xmax + 1, :], polys[selected_polys], tags[selected_polys]
            else:
                continue
        im = im[ymin:ymax + 1, xmin:xmax + 1, :]
        polys = polys[selected_polys]
        tags = tags[selected_polys]
        polys[:, :, 0] -= xmin
        polys[:, :, 1] -= ymin
        return im, polys, tags

    return im, polys, tags


def shrink_poly(poly, r, SR=0.01):
    '''
    fit a poly inside the origin poly, maybe bugs here...
    used for generate the score map
    :param poly: the text poly
    :param r: r in the paper
    :return: the shrinked poly
    '''
    # shrink ratio
    R = SR 
    # find the longer pair
    if np.linalg.norm(poly[0] - poly[1]) + np.linalg.norm(poly[2] - poly[3]) > \
            np.linalg.norm(poly[0] - poly[3]) + np.linalg.norm(poly[1] - poly[2]):
        # first move (p0, p1), (p2, p3), then (p0, p3), (p1, p2)
        ## p0, p1
        theta = np.arctan2((poly[1][1] - poly[0][1]), (poly[1][0] - poly[0][0]))
        poly[0][0] += R * r[0] * np.cos(theta)
        poly[0][1] += R * r[0] * np.sin(theta)
        poly[1][0] -= R * r[1] * np.cos(theta)
        poly[1][1] -= R * r[1] * np.sin(theta)
        ## p2, p3
        theta = np.arctan2((poly[2][1] - poly[3][1]), (poly[2][0] - poly[3][0]))
        poly[3][0] += R * r[3] * np.cos(theta)
        poly[3][1] += R * r[3] * np.sin(theta)
        poly[2][0] -= R * r[2] * np.cos(theta)
        poly[2][1] -= R * r[2] * np.sin(theta)
        ## p0, p3
        theta = np.arctan2((poly[3][0] - poly[0][0]), (poly[3][1] - poly[0][1]))
        poly[0][0] += R * r[0] * np.sin(theta)
        poly[0][1] += R * r[0] * np.cos(theta)
        poly[3][0] -= R * r[3] * np.sin(theta)
        poly[3][1] -= R * r[3] * np.cos(theta)
        ## p1, p2
        theta = np.arctan2((poly[2][0] - poly[1][0]), (poly[2][1] - poly[1][1]))
        poly[1][0] += R * r[1] * np.sin(theta)
        poly[1][1] += R * r[1] * np.cos(theta)
        poly[2][0] -= R * r[2] * np.sin(theta)
        poly[2][1] -= R * r[2] * np.cos(theta)
    else:
        ## p0, p3
        # print poly
        theta = np.arctan2((poly[3][0] - poly[0][0]), (poly[3][1] - poly[0][1]))
        poly[0][0] += R * r[0] * np.sin(theta)
        poly[0][1] += R * r[0] * np.cos(theta)
        poly[3][0] -= R * r[3] * np.sin(theta)
        poly[3][1] -= R * r[3] * np.cos(theta)
        ## p1, p2
        theta = np.arctan2((poly[2][0] - poly[1][0]), (poly[2][1] - poly[1][1]))
        poly[1][0] += R * r[1] * np.sin(theta)
        poly[1][1] += R * r[1] * np.cos(theta)
        poly[2][0] -= R * r[2] * np.sin(theta)
        poly[2][1] -= R * r[2] * np.cos(theta)
        ## p0, p1
        theta = np.arctan2((poly[1][1] - poly[0][1]), (poly[1][0] - poly[0][0]))
        poly[0][0] += R * r[0] * np.cos(theta)
        poly[0][1] += R * r[0] * np.sin(theta)
        poly[1][0] -= R * r[1] * np.cos(theta)
        poly[1][1] -= R * r[1] * np.sin(theta)
        ## p2, p3
        theta = np.arctan2((poly[2][1] - poly[3][1]), (poly[2][0] - poly[3][0]))
        poly[3][0] += R * r[3] * np.cos(theta)
        poly[3][1] += R * r[3] * np.sin(theta)
        poly[2][0] -= R * r[2] * np.cos(theta)
        poly[2][1] -= R * r[2] * np.sin(theta)
    return poly


def point_dist_to_line_old(p1, p2, p3):
    # compute the distance from p3 to p1-p2
    return np.linalg.norm(np.cross(p2 - p1, p1 - p3)) / np.linalg.norm(p2 - p1)


def point_dist_to_line(p1, p2, p3):
    if len(p3.shape) < 2:
        return np.linalg.norm(np.cross(p2 - p1, p1 - p3)) / np.linalg.norm(p2 - p1)
    else:
        points = p3.shape[0]
        cross_product = np.cross(p2 - p1, p1 - p3)
        cross_product = np.resize(cross_product, [points, 1])
    return np.linalg.norm(cross_product, axis=1) / np.linalg.norm(p2 - p1, axis=1)


def fit_line(p1, p2):
    # fit a line ax+by+c = 0
    if p1[0] == p1[1]:
        return [1., 0., -p1[0]]
    else:
        [k, b] = np.polyfit(p1, p2, deg=1)
        return [k, -1., b]


def line_cross_point(line1, line2):
    # line1 0= ax+by+c, compute the cross point of line1 and line2
    if line1[0] != 0 and line1[0] == line2[0]:
        print('Cross point does not exist')
        return None
    if line1[0] == 0 and line2[0] == 0:
        print('Cross point does not exist')
        return None
    if line1[1] == 0:
        x = -line1[2]
        y = line2[0] * x + line2[2]
    elif line2[1] == 0:
        x = -line2[2]
        y = line1[0] * x + line1[2]
    else:
        k1, _, b1 = line1
        k2, _, b2 = line2
        x = -(b1 - b2) / (k1 - k2)
        y = k1 * x + b1
    return np.array([x, y], dtype=np.float32)


def line_verticle(line, point):
    # get the verticle line from line across point
    if line[1] == 0:
        verticle = [0, -1, point[1]]
    else:
        if line[0] == 0:
            verticle = [1, 0, -point[0]]
        else:
            verticle = [-1. / line[0], -1, point[1] - (-1 / line[0] * point[0])]
    return verticle


def rectangle_from_parallelogram(poly):
    '''
    fit a rectangle from a parallelogram
    :param poly:
    :return:
    '''
    p0, p1, p2, p3 = poly
    angle_p0 = np.arccos(np.dot(p1 - p0, p3 - p0) / (np.linalg.norm(p0 - p1) * np.linalg.norm(p3 - p0)))
    if angle_p0 < 0.5 * np.pi:
        if np.linalg.norm(p0 - p1) > np.linalg.norm(p0 - p3):
            # p0 and p2
            ## p0
            p2p3 = fit_line([p2[0], p3[0]], [p2[1], p3[1]])
            p2p3_verticle = line_verticle(p2p3, p0)

            new_p3 = line_cross_point(p2p3, p2p3_verticle)
            ## p2
            p0p1 = fit_line([p0[0], p1[0]], [p0[1], p1[1]])
            p0p1_verticle = line_verticle(p0p1, p2)

            new_p1 = line_cross_point(p0p1, p0p1_verticle)
            return np.array([p0, new_p1, p2, new_p3], dtype=np.float32)
        else:
            p1p2 = fit_line([p1[0], p2[0]], [p1[1], p2[1]])
            p1p2_verticle = line_verticle(p1p2, p0)

            new_p1 = line_cross_point(p1p2, p1p2_verticle)
            p0p3 = fit_line([p0[0], p3[0]], [p0[1], p3[1]])
            p0p3_verticle = line_verticle(p0p3, p2)

            new_p3 = line_cross_point(p0p3, p0p3_verticle)
            return np.array([p0, new_p1, p2, new_p3], dtype=np.float32)
    else:
        if np.linalg.norm(p0 - p1) > np.linalg.norm(p0 - p3):
            # p1 and p3
            ## p1
            p2p3 = fit_line([p2[0], p3[0]], [p2[1], p3[1]])
            p2p3_verticle = line_verticle(p2p3, p1)

            new_p2 = line_cross_point(p2p3, p2p3_verticle)
            ## p3
            p0p1 = fit_line([p0[0], p1[0]], [p0[1], p1[1]])
            p0p1_verticle = line_verticle(p0p1, p3)

            new_p0 = line_cross_point(p0p1, p0p1_verticle)
            return np.array([new_p0, p1, new_p2, p3], dtype=np.float32)
        else:
            p0p3 = fit_line([p0[0], p3[0]], [p0[1], p3[1]])
            p0p3_verticle = line_verticle(p0p3, p1)

            new_p0 = line_cross_point(p0p3, p0p3_verticle)
            p1p2 = fit_line([p1[0], p2[0]], [p1[1], p2[1]])
            p1p2_verticle = line_verticle(p1p2, p3)

            new_p2 = line_cross_point(p1p2, p1p2_verticle)
            return np.array([new_p0, p1, new_p2, p3], dtype=np.float32)


def sort_rectangle(poly):
    # sort the four coordinates of the polygon, points in poly should be sorted clockwise
    # First find the lowest point
    p_lowest = np.argmax(poly[:, 1])
    if np.count_nonzero(poly[:, 1] == poly[p_lowest, 1]) == 2:
        # 底边平行于X轴, 那么p0为左上角
        p0_index = np.argmin(np.sum(poly, axis=1))
        p1_index = (p0_index + 1) % 4
        p2_index = (p0_index + 2) % 4
        p3_index = (p0_index + 3) % 4
        return poly[[p0_index, p1_index, p2_index, p3_index]], 0.
    else:
        # 找到最低点右边的点
        p_lowest_right = (p_lowest - 1) % 4
        p_lowest_left = (p_lowest + 1) % 4
        angle = np.arctan(
            -(poly[p_lowest][1] - poly[p_lowest_right][1]) / (poly[p_lowest][0] - poly[p_lowest_right][0]))
        # assert angle > 0
        if angle <= 0:
            print(angle, poly[p_lowest], poly[p_lowest_right])
        if angle / np.pi * 180 > 45:
            # 这个点为p2
            p2_index = p_lowest
            p1_index = (p2_index - 1) % 4
            p0_index = (p2_index - 2) % 4
            p3_index = (p2_index + 1) % 4
            return poly[[p0_index, p1_index, p2_index, p3_index]], -(np.pi / 2 - angle)
        else:
            # 这个点为p3
            p3_index = p_lowest
            p0_index = (p3_index + 1) % 4
            p1_index = (p3_index + 2) % 4
            p2_index = (p3_index + 3) % 4
            return poly[[p0_index, p1_index, p2_index, p3_index]], angle


def restore_rectangle_rbox(origin, geometry):
    d = geometry[:, :4]
    angle = geometry[:, 4]
    # for angle > 0
    origin_0 = origin[angle >= 0]
    d_0 = d[angle >= 0]
    angle_0 = angle[angle >= 0]
    if origin_0.shape[0] > 0:
        p = np.array([np.zeros(d_0.shape[0]), -d_0[:, 0] - d_0[:, 2],
                      d_0[:, 1] + d_0[:, 3], -d_0[:, 0] - d_0[:, 2],
                      d_0[:, 1] + d_0[:, 3], np.zeros(d_0.shape[0]),
                      np.zeros(d_0.shape[0]), np.zeros(d_0.shape[0]),
                      d_0[:, 3], -d_0[:, 2]])
        p = p.transpose((1, 0)).reshape((-1, 5, 2))  # N*5*2

        rotate_matrix_x = np.array([np.cos(angle_0), np.sin(angle_0)]).transpose((1, 0))
        rotate_matrix_x = np.repeat(rotate_matrix_x, 5, axis=1).reshape(-1, 2, 5).transpose((0, 2, 1))  # N*5*2

        rotate_matrix_y = np.array([-np.sin(angle_0), np.cos(angle_0)]).transpose((1, 0))
        rotate_matrix_y = np.repeat(rotate_matrix_y, 5, axis=1).reshape(-1, 2, 5).transpose((0, 2, 1))

        p_rotate_x = np.sum(rotate_matrix_x * p, axis=2)[:, :, np.newaxis]  # N*5*1
        p_rotate_y = np.sum(rotate_matrix_y * p, axis=2)[:, :, np.newaxis]  # N*5*1

        p_rotate = np.concatenate([p_rotate_x, p_rotate_y], axis=2)  # N*5*2

        p3_in_origin = origin_0 - p_rotate[:, 4, :]
        new_p0 = p_rotate[:, 0, :] + p3_in_origin  # N*2
        new_p1 = p_rotate[:, 1, :] + p3_in_origin
        new_p2 = p_rotate[:, 2, :] + p3_in_origin
        new_p3 = p_rotate[:, 3, :] + p3_in_origin

        new_p_0 = np.concatenate([new_p0[:, np.newaxis, :], new_p1[:, np.newaxis, :],
                                  new_p2[:, np.newaxis, :], new_p3[:, np.newaxis, :]], axis=1)  # N*4*2
    else:
        new_p_0 = np.zeros((0, 4, 2))
    # for angle < 0
    origin_1 = origin[angle < 0]
    d_1 = d[angle < 0]
    angle_1 = angle[angle < 0]
    if origin_1.shape[0] > 0:
        p = np.array([-d_1[:, 1] - d_1[:, 3], -d_1[:, 0] - d_1[:, 2],
                      np.zeros(d_1.shape[0]), -d_1[:, 0] - d_1[:, 2],
                      np.zeros(d_1.shape[0]), np.zeros(d_1.shape[0]),
                      -d_1[:, 1] - d_1[:, 3], np.zeros(d_1.shape[0]),
                      -d_1[:, 1], -d_1[:, 2]])
        p = p.transpose((1, 0)).reshape((-1, 5, 2))  # N*5*2

        rotate_matrix_x = np.array([np.cos(-angle_1), -np.sin(-angle_1)]).transpose((1, 0))
        rotate_matrix_x = np.repeat(rotate_matrix_x, 5, axis=1).reshape(-1, 2, 5).transpose((0, 2, 1))  # N*5*2

        rotate_matrix_y = np.array([np.sin(-angle_1), np.cos(-angle_1)]).transpose((1, 0))
        rotate_matrix_y = np.repeat(rotate_matrix_y, 5, axis=1).reshape(-1, 2, 5).transpose((0, 2, 1))

        p_rotate_x = np.sum(rotate_matrix_x * p, axis=2)[:, :, np.newaxis]  # N*5*1
        p_rotate_y = np.sum(rotate_matrix_y * p, axis=2)[:, :, np.newaxis]  # N*5*1

        p_rotate = np.concatenate([p_rotate_x, p_rotate_y], axis=2)  # N*5*2

        p3_in_origin = origin_1 - p_rotate[:, 4, :]
        new_p0 = p_rotate[:, 0, :] + p3_in_origin  # N*2
        new_p1 = p_rotate[:, 1, :] + p3_in_origin
        new_p2 = p_rotate[:, 2, :] + p3_in_origin
        new_p3 = p_rotate[:, 3, :] + p3_in_origin

        new_p_1 = np.concatenate([new_p0[:, np.newaxis, :], new_p1[:, np.newaxis, :],
                                  new_p2[:, np.newaxis, :], new_p3[:, np.newaxis, :]], axis=1)  # N*4*2
    else:
        new_p_1 = np.zeros((0, 4, 2))
    return np.concatenate([new_p_0, new_p_1])


def restore_rectangle(origin, geometry):
    return restore_rectangle_rbox(origin, geometry)


def generate_rbox(im_size, polys, tags):
    h, w = im_size
    poly_mask = np.zeros((h, w), dtype=np.uint8)
    score_map = np.zeros((h, w), dtype=np.uint8)

    # FIXME: NoteByCAI: 生成 position-sensitive map
    ps_score_map = np.zeros((h, w), dtype=np.uint8)
    ps_map1 = np.zeros((h, w, 1), dtype=np.uint8)  # top-left
    ps_map2 = np.zeros((h, w, 1), dtype=np.uint8)
    ps_map3 = np.zeros((h, w, 1), dtype=np.uint8)  # top-right
    ps_map4 = np.zeros((h, w, 1), dtype=np.uint8)
    ps_map5 = np.zeros((h, w, 1), dtype=np.uint8)
    ps_map6 = np.zeros((h, w, 1), dtype=np.uint8)
    ps_map7 = np.zeros((h, w, 1), dtype=np.uint8)  # down-left
    ps_map8 = np.zeros((h, w, 1), dtype=np.uint8)
    ps_map9 = np.zeros((h, w, 1), dtype=np.uint8)  # down-right

    geo_map = np.zeros((h, w, 5), dtype=np.float32)
    # mask used during traning, to ignore some hard areas
    training_mask = np.ones((h, w), dtype=np.uint8)
    # NoteBy CAI: 保存矩形.
    rectanges = []
    for poly_idx, poly_tag in enumerate(zip(polys, tags)):
        poly = poly_tag[0]
        tag = poly_tag[1]

        r = [None, None, None, None]
        for i in range(4):
            r[i] = min(np.linalg.norm(poly[i] - poly[(i + 1) % 4]),
                       np.linalg.norm(poly[i] - poly[(i - 1) % 4]))
        # FIXME: NoteBy CAI: 带有缩进的 score map
        shrinked_poly = shrink_poly(poly.copy(), r).astype(np.int32)[np.newaxis, :, :]

        #######################################"NOT CARE"###############################################################
        ###poly_1=poly.copy().astype(np.int32)[np.newaxis,:,:]
        ###cv2.fillPoly(training_mask,poly_1,0)
        ###cv2.fillPoly(training_mask,shrinked_poly,1)
        ################################################################################################################
        # FIXME: NoteBy CAI: position-sensitive mask
        poly_1 = poly.copy().astype(np.int32)[np.newaxis, :, :]
        cv2.fillPoly(ps_score_map, poly_1, 1)

        # FIXME: NoteBy CAI: text map and mask
        cv2.fillPoly(score_map, shrinked_poly, 1)
        cv2.fillPoly(poly_mask, shrinked_poly, poly_idx + 1)

        # if the poly is too small, then ignore it during training
        poly_h = min(np.linalg.norm(poly[0] - poly[3]), np.linalg.norm(poly[1] - poly[2]))
        poly_w = min(np.linalg.norm(poly[0] - poly[1]), np.linalg.norm(poly[2] - poly[3]))
        if min(poly_h, poly_w) < FLAGS.min_text_size:
            cv2.fillPoly(training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0)
        if tag:
            cv2.fillPoly(training_mask, poly.astype(np.int32)[np.newaxis, :, :], 0)

            # FIXME: 获取该文本框中所有点的坐标，这也就是为什么每一个文本框的给值不一样的原因。为的就是在这里好区分。
        xy_in_poly = np.argwhere(poly_mask == (poly_idx + 1))
        # if geometry == 'RBOX':
        # FIXME: 对任意两个顶点的组合生成一个平行四边形
        # FIXME: 这里会生成八个平行四边形，然后选择一个最匹配原来文本框的平行四边形作为计算计算距离的标准框。
        fitted_parallelograms = []
        for i in range(4):
            p0 = poly[i]
            p1 = poly[(i + 1) % 4]
            p2 = poly[(i + 2) % 4]
            p3 = poly[(i + 3) % 4]
            edge = fit_line([p0[0], p1[0]], [p0[1], p1[1]])
            backward_edge = fit_line([p0[0], p3[0]], [p0[1], p3[1]])
            forward_edge = fit_line([p1[0], p2[0]], [p1[1], p2[1]])
            if point_dist_to_line(p0, p1, p2) > point_dist_to_line(p0, p1, p3):
                # 平行线经过p2
                if edge[1] == 0:
                    edge_opposite = [1, 0, -p2[0]]
                else:
                    edge_opposite = [edge[0], -1, p2[1] - edge[0] * p2[0]]
            else:
                # 经过p3
                if edge[1] == 0:
                    edge_opposite = [1, 0, -p3[0]]
                else:
                    edge_opposite = [edge[0], -1, p3[1] - edge[0] * p3[0]]
            # move forward edge
            new_p0 = p0
            new_p1 = p1
            new_p2 = p2
            new_p3 = p3
            new_p2 = line_cross_point(forward_edge, edge_opposite)
            if point_dist_to_line(p1, new_p2, p0) > point_dist_to_line(p1, new_p2, p3):
                # across p0
                if forward_edge[1] == 0:
                    forward_opposite = [1, 0, -p0[0]]
                else:
                    forward_opposite = [forward_edge[0], -1, p0[1] - forward_edge[0] * p0[0]]
            else:
                # across p3
                if forward_edge[1] == 0:
                    forward_opposite = [1, 0, -p3[0]]
                else:
                    forward_opposite = [forward_edge[0], -1, p3[1] - forward_edge[0] * p3[0]]
            new_p0 = line_cross_point(forward_opposite, edge)
            new_p3 = line_cross_point(forward_opposite, edge_opposite)
            fitted_parallelograms.append([new_p0, new_p1, new_p2, new_p3, new_p0])
            # or move backward edge
            new_p0 = p0
            new_p1 = p1
            new_p2 = p2
            new_p3 = p3
            new_p3 = line_cross_point(backward_edge, edge_opposite)
            if point_dist_to_line(p0, p3, p1) > point_dist_to_line(p0, p3, p2):
                # across p1
                if backward_edge[1] == 0:
                    backward_opposite = [1, 0, -p1[0]]
                else:
                    backward_opposite = [backward_edge[0], -1, p1[1] - backward_edge[0] * p1[0]]
            else:
                # across p2
                if backward_edge[1] == 0:
                    backward_opposite = [1, 0, -p2[0]]
                else:
                    backward_opposite = [backward_edge[0], -1, p2[1] - backward_edge[0] * p2[0]]
            new_p1 = line_cross_point(backward_opposite, edge)
            new_p2 = line_cross_point(backward_opposite, edge_opposite)
            fitted_parallelograms.append([new_p0, new_p1, new_p2, new_p3, new_p0])
        areas = [Polygon(t).area for t in fitted_parallelograms]
        # FIXME: 选择最匹配的平行四边形
        parallelogram = np.array(fitted_parallelograms[np.argmin(areas)][:-1], dtype=np.float32)
        # sort thie polygon
        parallelogram_coord_sum = np.sum(parallelogram, axis=1)
        min_coord_idx = np.argmin(parallelogram_coord_sum)
        parallelogram = parallelogram[
            [min_coord_idx, (min_coord_idx + 1) % 4, (min_coord_idx + 2) % 4, (min_coord_idx + 3) % 4]]

        # FIXME: 平行四边形 变换为 矩形, 并根据矩形计算角度.
        rectange = rectangle_from_parallelogram(parallelogram)
        rectange, rotate_angle = sort_rectangle(rectange)

        # FIXME: NoteBy CAI: 保存矩形
        rectanges.append(rectange)

        # FIXME: NoteBy CAI: p0_rect：表示矩形中第一个点的坐标保存到这里。
        # FIXME: 完整文字框的坐标，x0,y0,x1,y1,x2,y2,x3,y3
        p0_rect, p1_rect, p2_rect, p3_rect = rectange
        p0_rect_sh, p1_rect_sh, p2_rect_sh, p3_rect_sh = rectange

        # FIXME: NoteBy CAI:calculate the x and y distance between point0 and point1,point2,and point3 by clockwise.
        x1 = (p0_rect_sh[0] - p1_rect_sh[0]) / 3
        y1 = (p0_rect_sh[1] - p1_rect_sh[1]) / 3
        x2 = (p0_rect_sh[0] - p2_rect_sh[0]) / 3
        y2 = (p0_rect_sh[1] - p2_rect_sh[1]) / 3
        x3 = (p0_rect_sh[0] - p3_rect_sh[0]) / 3
        y3 = (p0_rect_sh[1] - p3_rect_sh[1]) / 3

        # FIXME: top-left
        p1_0x = p0_rect_sh[0]
        p1_0y = p0_rect_sh[1]
        p1_1x = p0_rect_sh[0] - x1
        p1_1y = p0_rect_sh[1] - y1
        p1_2x = p0_rect_sh[0] - x2
        p1_2y = p0_rect_sh[1] - y2
        p1_3x = p0_rect_sh[0] - x3
        p1_3y = p0_rect_sh[1] - y3

        p2_0x = p1_1x
        p2_0y = p1_1y
        p2_1x = p2_0x - x1
        p2_1y = p2_0y - y1
        p2_2x = p2_0x - x2
        p2_2y = p2_0y - y2
        p2_3x = p2_0x - x3
        p2_3y = p2_0y - y3

        # FIXME: top-right
        p3_0x = p2_1x
        p3_0y = p2_1y
        p3_1x = p3_0x - x1
        p3_1y = p3_0y - y1
        p3_2x = p3_0x - x2
        p3_2y = p3_0y - y2
        p3_3x = p3_0x - x3
        p3_3y = p3_0y - y3

        p4_0x = p1_3x
        p4_0y = p1_3y
        p4_1x = p4_0x - x1
        p4_1y = p4_0y - y1
        p4_2x = p4_0x - x2
        p4_2y = p4_0y - y2
        p4_3x = p4_0x - x3
        p4_3y = p4_0y - y3

        p5_0x = p4_1x
        p5_0y = p4_1y
        p5_1x = p5_0x - x1
        p5_1y = p5_0y - y1
        p5_2x = p5_0x - x2
        p5_2y = p5_0y - y2
        p5_3x = p5_0x - x3
        p5_3y = p5_0y - y3

        p6_0x = p5_1x
        p6_0y = p5_1y
        p6_1x = p6_0x - x1
        p6_1y = p6_0y - y1
        p6_2x = p6_0x - x2
        p6_2y = p6_0y - y2
        p6_3x = p6_0x - x3
        p6_3y = p6_0y - y3

        # FIXME: down-left
        p7_0x = p4_3x
        p7_0y = p4_3y
        p7_1x = p7_0x - x1
        p7_1y = p7_0y - y1
        p7_2x = p7_0x - x2
        p7_2y = p7_0y - y2
        p7_3x = p7_0x - x3
        p7_3y = p7_0y - y3

        p8_0x = p7_1x
        p8_0y = p7_1y
        p8_1x = p8_0x - x1
        p8_1y = p8_0y - y1
        p8_2x = p8_0x - x2
        p8_2y = p8_0y - y2
        p8_3x = p8_0x - x3
        p8_3y = p8_0y - y3

        # FIXME: down-right
        p9_0x = p8_1x
        p9_0y = p8_1y
        p9_1x = p9_0x - x1
        p9_1y = p9_0y - y1
        p9_2x = p9_0x - x2
        p9_2y = p9_0y - y2
        p9_3x = p9_0x - x3
        p9_3y = p9_0y - y3

        # FIXME: top-left==1, top-right==3, down-left==7, down-right==9
        poly1 = np.array([[p1_0x, p1_0y], [p1_1x, p1_1y], [p1_2x, p1_2y], [p1_3x, p1_3y]], dtype=np.int32)
        poly2 = np.array([[p2_0x, p2_0y], [p2_1x, p2_1y], [p2_2x, p2_2y], [p2_3x, p2_3y]], dtype=np.int32)
        poly3 = np.array([[p3_0x, p3_0y], [p3_1x, p3_1y], [p3_2x, p3_2y], [p3_3x, p3_3y]], dtype=np.int32)
        poly4 = np.array([[p4_0x, p4_0y], [p4_1x, p4_1y], [p4_2x, p4_2y], [p4_3x, p4_3y]], dtype=np.int32)
        poly5 = np.array([[p5_0x, p5_0y], [p5_1x, p5_1y], [p5_2x, p5_2y], [p5_3x, p5_3y]], dtype=np.int32)
        poly6 = np.array([[p6_0x, p6_0y], [p6_1x, p6_1y], [p6_2x, p6_2y], [p6_3x, p6_3y]], dtype=np.int32)
        poly7 = np.array([[p7_0x, p7_0y], [p7_1x, p7_1y], [p7_2x, p7_2y], [p7_3x, p7_3y]], dtype=np.int32)
        poly8 = np.array([[p8_0x, p8_0y], [p8_1x, p8_1y], [p8_2x, p8_2y], [p8_3x, p8_3y]], dtype=np.int32)
        poly9 = np.array([[p9_0x, p9_0y], [p9_1x, p9_1y], [p9_2x, p9_2y], [p9_3x, p9_3y]], dtype=np.int32)

        poly1 = poly1[np.newaxis, :, :]
        poly2 = poly2[np.newaxis, :, :]
        poly3 = poly3[np.newaxis, :, :]
        poly4 = poly4[np.newaxis, :, :]
        poly5 = poly5[np.newaxis, :, :]
        poly6 = poly6[np.newaxis, :, :]
        poly7 = poly7[np.newaxis, :, :]
        poly8 = poly8[np.newaxis, :, :]
        poly9 = poly9[np.newaxis, :, :]

        cv2.fillPoly(ps_map1, poly1, 1)
        cv2.fillPoly(ps_map2, poly2, 1)
        cv2.fillPoly(ps_map3, poly3, 1)
        cv2.fillPoly(ps_map4, poly4, 1)
        cv2.fillPoly(ps_map5, poly5, 1)
        cv2.fillPoly(ps_map6, poly6, 1)
        cv2.fillPoly(ps_map7, poly7, 1)
        cv2.fillPoly(ps_map8, poly8, 1)
        cv2.fillPoly(ps_map9, poly9, 1)

        ###########################################################
        # Fixme: update the following 12 rows by gen_geo_map
        gen_geo_map.gen_geo_map(geo_map, xy_in_poly, rectange, rotate_angle)

        # for y, x in xy_in_poly:
        #     point = np.array([x, y], dtype=np.float32)
        #     # top
        #     geo_map[y, x, 0] = point_dist_to_line(p0_rect, p1_rect, point)
        #     # right
        #     geo_map[y, x, 1] = point_dist_to_line(p1_rect, p2_rect, point)
        #     # down
        #     geo_map[y, x, 2] = point_dist_to_line(p2_rect, p3_rect, point)
        #     # left
        #     geo_map[y, x, 3] = point_dist_to_line(p3_rect, p0_rect, point)
        #     # angle
        #     geo_map[y, x, 4] = rotate_angle
        ############################################################
        # Fixme: another faster version in github about calculate geo map
        # geo_map = caclulate_geo_map(geo_map, xy_in_poly, rectange, rotate_angle)

        ############################################################

    ps_true_maps = np.concatenate([ps_map1, ps_map3, ps_map7, ps_map9], axis=-1)
    return score_map, geo_map, training_mask, ps_true_maps, ps_score_map


def bright_dark(img, Br = 1.0):
    img_pil = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))
    enh_bri = ImageEnhance.Brightness(img_pil)
    img_pil_br = enh_bri.enhance(Br)
    img_opcv = cv2.cvtColor(np.asarray(img_pil_br),cv2.COLOR_RGB2BGR)
    return img_opcv

def perlin_noise():
    files = []
    img_perlinNoise = []
    for ext in ['jpg', 'png', 'jpeg']:
        files.extend(glob.glob(
            os.path.join(FLAGS.PerlinNoise_data_path, '*.{}'.format(ext))))

    for img_path in files:
        img_perlinNoise.append(cv2.imread(img_path))

    return img_perlinNoise


def addImage(img1, img2, alpha):
    h, w, _ = img1.shape
    img2 = cv2.resize(img2, (w, h), interpolation=cv2.INTER_AREA)

    beta = 1 - alpha
    gamma = 0
    img_add = cv2.addWeighted(img1, alpha, img2, beta, gamma)
    return img_add

def generator(input_size=512, batch_size=32,
              background_ratio=3. / 8,
              random_scale=np.array([0.5, 1, 2.0, 3.0]),
              vis=False):
    image_list = np.array(get_images())
    print('{} training images in {}'.format(
        image_list.shape[0], FLAGS.training_data_path))
    index = np.arange(0, image_list.shape[0])
    random_bright_dark = np.array([0.5, 0.8, 1.0, 1.3, 1.9])
    random_perlin_noise = np.array([0.1, 0.3, 0.5, 0.7, 0.8, 0.9, 1.0])
    perlin_noise_maps = perlin_noise()
    perlin_noise_maps_idxs = np.array([ip for ip in range(len(perlin_noise_maps))])


    while True:

        np.random.shuffle(index)
        images = []
        image_labels = []    
        image_fns = []
        score_maps = []
        geo_maps = []
        training_masks = []
        ps_maps = []
        ps_score_maps = []
        for i in index:
            try:
                im_fn = image_list[i]
                im = cv2.imread(im_fn)
                # print im_fn
                h, w, _ = im.shape

                txt_fn = im_fn.replace(os.path.basename(im_fn).split('.')[-1], 'txt')
                if not os.path.exists(txt_fn):
                    continue

                text_polys, text_tags = load_annoataion(txt_fn)

                text_polys, text_tags = check_and_validate_polys(text_polys, text_tags, (h, w), txt_fn)

                # random scale this image
                rd_scale = np.random.choice(random_scale)
                im = cv2.resize(im, dsize=None, fx=rd_scale, fy=rd_scale)
                text_polys *= rd_scale

                # random bright or dark this image
                rd_bright = np.random.choice(random_bright_dark)
                im = bright_dark(im, rd_bright)

                # Fixme: perlin noise
                rd_perlin_factor = np.random.choice(random_perlin_noise)
                rd_perlin_map_idx = np.random.choice(perlin_noise_maps_idxs)
                rd_perlin_map = cv2.resize(perlin_noise_maps[rd_perlin_map_idx], dsize=None, fx=rd_scale, fy=rd_scale)
                im = addImage(im, rd_perlin_map, rd_perlin_factor)

                # print rd_scale
                # random crop a area from image
                if np.random.rand() < background_ratio:
                    # crop background
                    im, text_polys, text_tags = crop_area(im, text_polys, text_tags, crop_background=True)
                    if text_polys.shape[0] > 0:
                        # cannot find background
                        continue
                    # pad and resize image
                    new_h, new_w, _ = im.shape
                    max_h_w_i = np.max([new_h, new_w, input_size])
                    im_padded = np.zeros((max_h_w_i, max_h_w_i, 3), dtype=np.uint8)
                    im_padded[:new_h, :new_w, :] = im.copy()
                    im = cv2.resize(im_padded, dsize=(input_size, input_size))
                    score_map = np.zeros((input_size, input_size), dtype=np.uint8)

                    ps_map = np.zeros((input_size, input_size, 4), dtype=np.uint8)

                    geo_map_channels = 5 if FLAGS.geometry == 'RBOX' else 8
                    geo_map = np.zeros((input_size, input_size, geo_map_channels), dtype=np.float32)
                    training_mask = np.ones((input_size, input_size), dtype=np.uint8)

                    ps_score_map = np.zeros((input_size, input_size), dtype=np.uint8)

                    # Fixme: classification
                    im_label = 0

                else:
                    im, text_polys, text_tags = crop_area(im, text_polys, text_tags, crop_background=False)
                    if text_polys.shape[0] == 0:
                        continue
                    h, w, _ = im.shape

                    # pad the image to the training input size or the longer side of image
                    new_h, new_w, _ = im.shape
                    max_h_w_i = np.max([new_h, new_w, input_size])
                    im_padded = np.zeros((max_h_w_i, max_h_w_i, 3), dtype=np.uint8)
                    im_padded[:new_h, :new_w, :] = im.copy()
                    im = im_padded
                    # resize the image to input size
                    new_h, new_w, _ = im.shape
                    resize_h = input_size
                    resize_w = input_size
                    im = cv2.resize(im, dsize=(resize_w, resize_h))
                    resize_ratio_3_x = resize_w / float(new_w)
                    resize_ratio_3_y = resize_h / float(new_h)
                    text_polys[:, :, 0] *= resize_ratio_3_x
                    text_polys[:, :, 1] *= resize_ratio_3_y
                    new_h, new_w, _ = im.shape

                    score_map, geo_map, training_mask, ps_map, ps_score_map = generate_rbox((new_h, new_w), text_polys,
                                                                                            text_tags)
                    # Fixme: classification
                    im_label = 1


                # FIXME: down-sampling by x/4 to match the pool2 (128*128) with input size (512*512)
                images.append(im[:, :, ::-1].astype(np.float32))
                image_labels.append(im_label)    #Fixme: classification
                image_fns.append(im_fn)
                score_maps.append(score_map[::2, ::2, np.newaxis].astype(np.float32))
                geo_maps.append(geo_map[::2, ::2, :].astype(np.float32))
                training_masks.append(training_mask[::2, ::2, np.newaxis].astype(np.float32))
                # FIXME: position-sensitive map
                # FIXME: ps_maps are calculated from rectangle, but ps_score_maps are calculated from the original poly
                ps_maps.append(ps_map[::2, ::2, :].astype(np.float32))
                ps_score_maps.append(ps_score_map[::2, ::2, np.newaxis].astype(np.float32))

                if len(images) == batch_size:
                    # Fixme: corner map and classification label
                    yield images, image_fns, score_maps, geo_maps, training_masks, ps_maps, ps_score_maps, image_labels
                    images = []
                    image_fns = []
                    score_maps = []
                    geo_maps = []
                    training_masks = []
                    ps_maps = []
                    ps_score_maps = []
                    image_labels = []
            except Exception as e:
                import traceback
                traceback.print_exc()
                continue


def get_batch(num_workers, **kwargs):
    try:
        enqueuer = GeneratorEnqueuer(generator(**kwargs), use_multiprocessing=True)
        enqueuer.start(max_queue_size=24, workers=num_workers)
        generator_output = None
        while True:
            while enqueuer.is_running():
                if not enqueuer.queue.empty():
                    generator_output = enqueuer.queue.get()
                    break
                else:
                    time.sleep(0.01)
            yield generator_output
            generator_output = None
    finally:
        if enqueuer is not None:
            enqueuer.stop()


if __name__ == '__main__':
    print("hello CAI ")
    generator()
    # print("hello cai ")
