<jupyter_start><jupyter_text>表格图像数据生成<jupyter_code>import numpy as np
import pandas as pd
import cv2
def get_neighbor_units_direction(r_i, c_i, units_comb_directions):
    # Get the direction indexes of the 4 neighbor units (left, right, up and down) 
    # input: r_i, the row index of the given unit
    # input: c_i, the column index of the given unit
    # input: units_comb_directions, the direction status matrix of the units
    # return: l_direction, u_direction, r_direction, d_direction,
    #       the direction indexes of the neighbor units
    
    # left
    mask1 = units_comb_directions['r_i'] == r_i
    mask2 = units_comb_directions['c_i'] == c_i - 1
    mask = mask1 & mask2
    if np.sum(mask) == 1:
        l_direction = units_comb_directions.loc[mask, 'direction'].values[0]
    else:
        l_direction = None
        
    # right
    mask1 = units_comb_directions['r_i'] == r_i
    mask2 = units_comb_directions['c_i'] == c_i + 1
    mask = mask1 & mask2
    if np.sum(mask) == 1:
        r_direction = units_comb_directions.loc[mask, 'direction'].values[0]
    else:
        r_direction = None
        
    
    # up
    mask1 = units_comb_directions['r_i'] == r_i - 1
    mask2 = units_comb_directions['c_i'] == c_i
    mask = mask1 & mask2
    if np.sum(mask) == 1:
        u_direction = units_comb_directions.loc[mask, 'direction'].values[0]
    else:
        u_direction = None
        
    # down
    mask1 = units_comb_directions['r_i'] == r_i + 1
    mask2 = units_comb_directions['c_i'] == c_i
    mask = mask1 & mask2
    if np.sum(mask) == 1:
        d_direction = units_comb_directions.loc[mask, 'direction'].values[0]
    else:
        d_direction = None
        
    return l_direction, u_direction, r_direction, d_direction
def check_comb_direction_OK(r_i, c_i, d, units_comb_directions):
    # Check if the selected combining direction of the unit (denoted by r_i and c_i) is applicable.
    # input: r_i, the row index of the given unit
    # input: c_i, the column index of the given unit
    # input: d, the target direction of (r_i, c_i) indexed unit
    # input: units_comb_directions, the direction status matrix of the units
    # return: 0, can not be combined, 1, OK to be combined
    
    is_exist = check_comb_target_exist(r_i, c_i, d)

    if is_exist == False:
        return 0
    
    # the combining direction for the given unit
    # should not be vertical to the up unit nor the left unit
    
    l_d, u_d, r_d, d_d = get_neighbor_units_direction(r_i, c_i, units_comb_directions)
    
    if l_d is not None and l_d != 0 and np.dot(comb_v[d], comb_v[l_d]) == 0:
        return 0
    
    if u_d is not None and u_d != 0 and  np.dot(comb_v[d], comb_v[u_d]) == 0:
        return 0
    
    if r_d is not None and r_d != 0 and  np.dot(comb_v[d], comb_v[r_d]) == 0:
        return 0
    
    if d_d is not None and d_d != 0 and  np.dot(comb_v[d], comb_v[d_d]) == 0:
        return 0
    
    return 1
def check_comb_target_exist(r_i, c_i, d):
    # Check if the selected combining direction is beyond the arange of the pre-defined table rows and columns or not.
    # input: r_i, the row index of the given unit
    # input: c_i, the column index of the given unit
    # input: d, the target direction of (r_i, c_i) indexed unit
    # return: False can not be existed, True the combined target is existed
    
    t_c_i, t_r_i, t_d = get_combined_unit(r_i, c_i, d)
    
    if t_c_i not in np.arange(col_units):
        return False
    if t_r_i not in np.arange(row_units):
        return False
    
    return True
def get_combined_unit(r_i, c_i, d):
    # Get the (r_i, c_i) index of the target unit, and the corresponding direction index
    #    if the combined is conducted.
    # input: r_i, the row index of the given unit
    # input: c_i, the column index of the given unit
    # input: d, the target direction of (r_i, c_i) indexed unit
    # return: target_unit[0] is the c_i of the target
    #         target_unit[1] is the r_i of the target
    #         t_d is the assigned direction of the target, actually is the reverse direction of the given d
    
    target_unit = np.array([c_i, r_i]) + np.array(comb_v[d])
    
    # t_d is the direction of the target unit of the combing
    # the direction should be the reverse direction of the give d
    if d == 1:
        t_d = 3
    elif d == 2:
        t_d = 4
    elif d == 3:
        t_d = 1
    elif d == 4:
        t_d = 2
    
    # [x , y]
    # x is col index
    # y is row index
    return target_unit[0], target_unit[1], t_d
def generate_table(combined_records, units_pos, units_comb_directions):
    # Generate a tabel by randomly combing neighbor units.
    # input: units_pos, dataframe to record the r_i, c_i, l_x, l_y, r_x, r_y for each unit
    # input: units_comb_directions, dataframe to record the r_i, c_i, direction for each unit
    
#     combined_records = []
    for r_i in np.arange(row_units):
        unit_h = unit_h_list[r_i]
        
        for c_i in np.arange(col_units):
            unit_w = unit_w_list[c_i]
            
            ## for the constaint width and height of the unit
#             x_center = int((1 + c_i * 2) * (unit_w / 2) + padding / 2)
#             y_center = int((1 + r_i * 2) * (unit_h / 2) + padding / 2)
#             l_x = x_center - int(unit_w / 2)
#             l_y = y_center - int(unit_h / 2)
#             r_x = x_center + int(unit_w / 2)
#             r_y = y_center + int(unit_h / 2)

            ## for the randomly set widht and height of the unit
            l_x = int(np.sum(unit_w_list[0:c_i]) + padding_l)
            l_y = int(np.sum(unit_h_list[0:r_i]) + padding_u)
            r_x = int(np.sum(unit_w_list[0:c_i]) + unit_w_list[c_i] + padding_l)
            r_y = int(np.sum(unit_h_list[0:r_i]) + unit_h_list[r_i] + padding_u)
#             print(l_x, l_y, r_x, r_y)
            
            
#             l_x = x_center - int(unit_w / 2)
#             l_y = y_center - int(unit_h / 2)
#             r_x = x_center + int(unit_w / 2)
#             r_y = y_center + int(unit_h / 2)

            units_pos.iloc[c_i + r_i * col_units, :] = [r_i, c_i, l_x, l_y, r_x, r_y]
            
            # !!! Attention wrong
            # units_comb_directions.iloc[c_i + r_i * col_units, :] = [r_i, c_i, 0]

            # select the comining direction
            # 0 none, 1 right, 2 up, 3 left, 4 down
            # randomly decide to combine or not
            is_comb = np.random.randint(0,2)
            if is_comb:
                # randomly select the combining direction
                direction = np.random.randint(1,5)

                # check if the combining direction is OK or NOT
                comb_OK = check_comb_direction_OK(r_i, c_i, direction, units_comb_directions)
#                 print(r_i, c_i, direction, comb_OK)
                if comb_OK == 1:
                    # record the combined diection for the current unit
                    units_comb_directions.iloc[c_i + r_i * col_units, :] = [r_i, c_i, direction]
                    target_c_i, target_r_i, target_d = get_combined_unit(r_i, c_i, direction)
                                        
#                     print("=============")
#                     ld, ud, rd, dd = get_neighbor_units_direction(r_i, c_i)
#                     print(ld, ud, rd, dd)
#                     print(r_i, c_i, direction, '==>', target_r_i, target_c_i, target_d)

                    # record the combined diection for the combined unit, actually is the reverse direction of the current one
                    units_comb_directions.iloc[target_c_i + target_r_i * col_units, :] = [target_r_i, target_c_i, target_d]
                    
#                     print(units_comb_directions)

                    # record the combined units index
                    u_a = "{0}-{1}".format(r_i, c_i)
                    u_b = "{0}-{1}".format(target_r_i, target_c_i)
                    update_combined_records(combined_records, u_a, u_b)

def update_combined_records(combined_records, u_a, u_b):
    # update if u_a, u_b combining reocrd is existed in combined_records

    for i in np.arange(len(combined_records)):
        one = combined_records[i]
        if u_a in one and u_b in one:
            return combined_records
        elif u_a in one:
            combined_records[i].append(u_b)
            return combined_records
        elif u_b in one:
            combined_records[i].append(u_a)
            return combined_records
    
    combined_records.append([u_a, u_b])
    return combined_records
def check_if_combined(r_i, c_i, combined_records):
    # Check if the given r_i, c_i unit is combined or not
    u_a = "{0}-{1}".format(r_i, c_i)
    for one in combined_records:
        if u_a in one:
            return True
    
    return False
def get_unit_position(r_i, c_i, units_pos):
    mask1 = units_pos['r_i'] == r_i
    mask2 = units_pos['c_i'] == c_i
    mask = mask1 & mask2
    if np.sum(mask) == 1:
        one_row = units_pos.loc[mask,:]
        l_x, l_y, r_x, r_y = one_row['l_x'].values[0], one_row['l_y'].values[0], one_row['r_x'].values[0], one_row['r_y'].values[0]
        return l_x, l_y, r_x, r_y
    
    return None, None, None, None
def get_combined_position(one_record, units_pos):
    x_s = []
    y_s = []
    for one in one_record:
        r_i = int(one.split('-')[0])
        c_i = int(one.split('-')[1])
        l_x, l_y, r_x, r_y = get_unit_position(r_i, c_i,units_pos)

        x_s.append(l_x)
        y_s.append(l_y)
        x_s.append(r_x)
        y_s.append(r_y)
        
    return np.min(x_s), np.max(y_s), np.max(x_s), np.min(y_s)
        
def check_txt_area_size_OK(txt_string, s_x, s_y, lx, ly, rx, ry, textSize=15):
    fontStyle = ImageFont.truetype(
        "Font/simsun.ttc", textSize, encoding="utf-8")
    
    lines = txt_string.split('\n')
    widths = []
    height = 0
    for one in lines:
        (width_i, height_i), (offset_x, offset_y) = fontStyle.font.getsize(one.strip())
        widths.append(width_i)
        height = height + height_i
    width = np.max(widths)
    
    if (s_x - np.min([lx, rx]) + width) > (np.abs(lx - rx) + 2):
        return False
    elif (s_y - np.min([ly, ry]) + height) > (np.abs(ly - ry) + 2):
        return False
    
    return True
def assign_text(image, txts, lx, ly, rx, ry):
    # get the text area location: s_x: left, s_y: top
#     s_x = np.random.choice(np.arange(np.min([lx, rx]), np.max([lx, rx])), 1)
#     s_y = np.random.choice(np.arange(np.min([ly, ry]), np.max([ly, ry])), 1)
    s_x = np.min([lx, rx]) + np.random.choice([2, int(np.abs(lx - rx) / 3)])
    s_y = np.min([ly, ry]) + np.random.choice([2, int(np.abs(ly - ry)/ 3)])
    
    max_lines = int((np.abs(ly - ry) - (s_y - np.min([ly, ry]))) / 13)
    try_i = 0
    while(1):
        txt_lines = np.random.choice([1, np.ceil(max_lines * 2 / 3)])
        txt_strs = []
        for i in np.arange(txt_lines):
            txt_i = np.random.randint(0, len(txts))
            txt_strs.append(txts[txt_i])
        txt_string = "\n".join(txt_strs)
        
        flag = check_txt_area_size_OK(txt_string, s_x, s_y, lx, ly, rx, ry)
        try_i = try_i + 1
        if flag == True or try_i > 10:
            break
    
#     print(txt_strs, txt_lines)
    
    # assign txt_string, and get the text area size: w, h
    image, (w, h) = cv2ImgAddText(image, txt_string, s_x, s_y, (0,0,0))
    
    return image, txt_string, s_x, s_y, w, h
def generate_save_talbes(table_i, img_path, label_path):
    
#     unit_h_max = np.max(unit_h_list)
#     unit_w_max = np.max(unit_w_list)

# table_i = i

    line_w = line_w_list[table_i]

    img_h = np.sum(unit_h_list) + padding_u + padding_d
    img_w = np.sum(unit_w_list) + padding_l + padding_r

    img_data = np.zeros((img_h, img_w), np.uint8)
    img_data.fill(255)
    image = cv2.cvtColor(img_data, cv2.COLOR_GRAY2BGR)

    # r_i, index of row
    # c_i index of column
    # l_x, l_y, left down point x, y
    # r_x, r_y, right up point x, y
    units_pos = pd.DataFrame(np.zeros((row_units * col_units, 6)), 
                             columns=['r_i', 'c_i', 'l_x', 'l_y', 'r_x', 'r_y'], dtype=int)

    com_dir_data = np.zeros((row_units * col_units, 3))
    com_dir_data.fill(-1)
    units_comb_directions = pd.DataFrame(com_dir_data,
                                         columns=['r_i', 'c_i', 'direction'], dtype=int)

    combined_records = []

    # opencv table first row is at the bottom
    # opencv table first col is at the left
    generate_table(combined_records, units_pos, units_comb_directions)
    unit_pos_final = []
    
    for r_i in np.arange(row_units):
        for c_i in np.arange(col_units):
            if not check_if_combined(r_i, c_i, combined_records):
                l_x, l_y, r_x, r_y = get_unit_position(r_i, c_i, units_pos)
                cv2.rectangle(image, (l_x,l_y), (r_x, r_y), (0, 0, 0), line_w)
                
#                 txt_i = np.random.randint(0, len(words))
#                 cv2.putText(image, words[txt_i], (l_x,l_y), 
#                             fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(0, 0, 0))

#                 image = cv2ImgAddText(image, words[txt_i], l_x + txt_padding, l_y + txt_padding, (0,0,0))
                
                if np.random.rand() < 0.5:
                    words = words_ZH
                else:
                    words = words_EN
    
                image, txt_string, s_x, s_y, w, h = assign_text(image, words, l_x, l_y, r_x, r_y)
#                 cv2.rectangle(image, (s_x,s_y), (s_x + w, s_y + h), (0, 255, 0), 1)
                # top left -> bottom right
#                 unit_pos_final.append([l_x, l_y, r_x, r_y, words[txt_i]])
                unit_txt = txt_string.replace('\n', '--')
                unit_pos_final.append([l_x, l_y, r_x, r_y, line_w, s_x, s_y, w, h, unit_txt])

    for one_record in combined_records:
        l_x, l_y, r_x, r_y = get_combined_position(one_record, units_pos)
        cv2.rectangle(image, (l_x,l_y), (r_x, r_y), (0, 0, 0), line_w)
        
#         txt_i = np.random.randint(0, len(words))
#         cv2.putText(image, words[txt_i], (l_x,l_y), 
#                     fontFace=cv2.FONT_HERSHEY_SIMPLEX, fontScale=1, color=(0, 0, 0))

#         image = cv2ImgAddText(image, words[txt_i], l_x + txt_padding, r_y + txt_padding, (0, 0, 0))
        if np.random.rand() < 0.5:
            words = words_ZH
        else:
            words = words_EN

        image, txt_string, s_x, s_y, w, h = assign_text(image, words, l_x, l_y, r_x, r_y)
#         cv2.rectangle(image, (s_x,s_y), (s_x + w, s_y + h), (0, 0, 255), 1)
        # top left -> bottom right
#         unit_pos_final.append([l_x, r_y, r_x, l_y, words[txt_i]])
        unit_txt = txt_string.replace('\n', '--')
        unit_pos_final.append([l_x, l_y, r_x, r_y, line_w, s_x, s_y, w, h, unit_txt])
        
    title_words = len(unit_w_list) #np.random.randint(int(len(unit_w_list)/2), )
    title_words_ids = np.random.randint(0, len(words), title_words)
    title_strs = [words[i] for i in title_words_ids]
    title_string = "".join(title_strs)
    image, (w, h) = cv2ImgAddText(image, title_string, padding_l*4, int(padding_u / 2), (0,0,0))
    
    # label the title text area
    unit_pos_final.append([-1, -1, -1, -1, 0, padding_l*4, int(padding_u / 2), w, h, title_string])
        
    unit_pos_final_df = pd.DataFrame(unit_pos_final)
    unit_pos_final_df.to_csv(label_path + ".label")
    
    
    
    
    cv2.imwrite(img_path, image)
    
#     cv2.imshow('Table',image)
#     cv2.waitKey(0)

from PIL import Image, ImageDraw, ImageFont

def cv2ImgAddText(img, text, left, top, textColor=(0, 0, 0), textSize=15):
    
    if (isinstance(img, np.ndarray)):  # 判断是否OpenCV图片类型
        img = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        
    # 创建一个可以在给定图像上绘图的对象
    draw = ImageDraw.Draw(img)
    
    # 字体的格式
    fontStyle = ImageFont.truetype(
        "Font/simsun.ttc", textSize, encoding="utf-8")
    
    # 绘制文本
    draw.text((left, top), text, textColor, font=fontStyle)
    
    lines = text.split('\n')
    widths = []
    height = 0
    for one in lines:
        (width_i, height_i), (offset_x, offset_y) = fontStyle.font.getsize(one.strip())
        widths.append(width_i)
    height = 15 * len(widths)
    
    # 转换回OpenCV格式
    # width, height 打印文本的区域宽、高
    return cv2.cvtColor(np.asarray(img), cv2.COLOR_RGB2BGR), (np.max(widths), height)
# row_units = 5
# col_units = 10
with open("./Text_ZH/CollectedWords.txt", 'r', encoding='utf-8') as txt_f:
    words = txt_f.readlines()
words_ZH = [one.strip() for one in words]
    
with open("./Text_EN/CollectedWords_EN.txt", 'r', encoding='utf-8') as txt_f:
    words = txt_f.readlines()
words_EN = [one.strip() for one in words]

table_nums = 20000
line_w_list = np.random.randint(1, 5, table_nums)
padding_u = 50
padding_l = 20
padding_d = 20
padding_r = 20

txt_padding = 5

# 0 none, 1 right, 2 up, 3 left, 4 down
# combine dirction vectors
# x-axis: right
# y-axis: up
comb_v = {1: [1, 0],
          2: [0, -1],
          3: [-1, 0],
          4: [0, 1]}

dataset = "./dataset04/"
img_folder = dataset + "imgs/"
label_folder = dataset + "labels/"

for i in np.arange(table_nums):
    filename = "{0}.png".format(i).rjust(10, '0')
    img_path = img_folder + filename
    label_path = label_folder + filename
    
    row_units = np.random.randint(3, 10)
    col_units = np.random.randint(3, 10)
    
    unit_w_list = np.random.randint(50, 150, col_units)
    unit_h_list = np.random.randint(20, 100, row_units)
    
    generate_save_talbes(i, img_path, label_path)
    
    print("Saving table {0} to image".format(i))<jupyter_output>Saving table 0 to image
Saving table 1 to image
Saving table 2 to image
Saving table 3 to image
Saving table 4 to image
Saving table 5 to image
Saving table 6 to image
Saving table 7 to image
Saving table 8 to image
Saving table 9 to image
Saving table 10 to image
Saving table 11 to image
Saving table 12 to image
Saving table 13 to image
Saving table 14 to image
Saving table 15 to image
Saving table 16 to image
Saving table 17 to image
Saving table 18 to image
Saving table 19 to image
Saving table 20 to image
Saving table 21 to image
Saving table 22 to image
Saving table 23 to image
Saving table 24 to image
Saving table 25 to image
Saving table 26 to image
Saving table 27 to image
Saving table 28 to image
Saving table 29 to image
Saving table 30 to image
Saving table 31 to image
Saving table 32 to image
Saving table 33 to image
Saving table 34 to image
Saving table 35 to image
Saving table 36 to image
Saving table 37 to image
Saving table 38 to image
Saving table 39 to image
Saving tab[...]
{'fraction_non_alphanumeric': 0.11353805073431242, 'fraction_numerical': 0.019065420560747663, 'mean_word_length': 2.3649595687331537, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 5, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '15142', 'license': 'MIT', 'n_tokens_mistral': 7229, 'n_tokens_neox': 6954, 'n_words': 1849, 'path': 'table-generation/TableGeneration-V3.ipynb', 'repo_name': 'hainan89/TableGraph', 'size': '18725'}