t29mato's picture
Initial deploy: AutoLineDigitizer Gradio API for HuggingFace Spaces
27c91ef
Raw
History Blame Contribute Delete
11.4 kB
from PIL import Image, ImageDraw, ImageStat
import numpy as np
import cv2
def polygon2bbox(polygon_dic):
x_coords = []
y_coords = []
for item in polygon_dic.keys():
if "x" in item:
x_coords.append(polygon_dic[item])
elif "y" in item:
y_coords.append(polygon_dic[item])
x0 = min(x_coords)
y0 = min(y_coords)
x1 = max(x_coords)
y1 = max(y_coords)
return (x0, y0, x1, y1)
def get_legend_boxes(annot):
# ============================================================
# This is used to extract the text bbox and text role from task3 field in gt
text_role_dic_id = {}
id_text_bb_dic = {}
#Used role is what role you would like to extract,
#if you only want to process the legend area, then legend_label & legend_title is enough
for role in ['legend_label', 'legend_title']:
text_role_dic_id[role] = []
text_block_list = annot["task3"]["input"]["task2_output"]["text_blocks"]
for item in text_block_list:
item_id = item["id"]
if "polygon" in item:
polygon_dic = item["polygon"]
# Convert the polygon to the bbox
(bbo_x0, bbox_y0, bbo_x1, bbo_y1) = polygon2bbox(polygon_dic)
# id_text_bb_dic[item_id] = [x0, y0, x1, y1]
poly_x0,poly_x1,poly_x2,poly_x3,poly_y0,poly_y1,poly_y2,poly_y3 = polygon_dic.values()
id_text_bb_dic[item_id] = {"bbox":[bbo_x0, bbox_y0, bbo_x1, bbo_y1], "polygon":[poly_x0,poly_x1,poly_x2,poly_x3,poly_y0,poly_y1,poly_y2,poly_y3]}
else:
# Handle cleaning of adobe synth data
id_text_bb_dic[item_id] = {"bbox":[item['bb']['x0'], item['bb']['y0'],
item['bb']['x0']+item['bb']['width']-1, item['bb']['y0']+item['bb']['height']-1]}
text_role_list = annot["task3"]["output"]["text_roles"]
for item in text_role_list:
role = item["role"]
item_id = item["id"]
# if role in used_role:
if role not in text_role_dic_id.keys():
text_role_dic_id[role] = []
text_role_dic_id[role].append(item_id)
# ============================================================
# ============================================================
# Handle the legend patch and generate the legend area bbox
legend_area_bb_list = []
legend_patch_list = []
for item in annot["task5"]["output"]["legend_pairs"]:
x0 = item["bb"]["x0"]
y0 = item["bb"]["y0"]
x1 = x0 + item["bb"]["width"]
y1 = y0 + item["bb"]["height"]
legend_patch_list.append({"bbox":[x0,y0,x1,y1]})
for legend_id in text_role_dic_id["legend_title"] + text_role_dic_id["legend_label"]:
legend_area_bb_list.append(id_text_bb_dic[legend_id])
for bbox_item in legend_patch_list:
legend_area_bb_list.append(bbox_item)
return legend_area_bb_list
def get_plot_area(annot):
plot_area = annot['task6']['input']['task4_output']['_plot_bb']
img_plot_area = {key: max(value, 0) for key, value in plot_area.items()}
return img_plot_area
def get_legend_area(bbox_item_list):
if bbox_item_list == [] or {}:
return ()
bbox_list = []
# print(bbox_item_list)
for bbox_item in bbox_item_list:
# print(bbox_item["bbox"])
bbox_list.append(bbox_item["bbox"])
x0 = sorted(bbox_list, key = lambda i:i[0])[0][0]
y0 = sorted(bbox_list, key = lambda i:i[1])[0][1]
x1 = sorted(bbox_list, key = lambda i:i[2])[-1][2]
y1 = sorted(bbox_list, key = lambda i:i[3])[-1][3]
# color = (random.random()*255,random.random()*255,random.random()*255)
# cv2.rectangle(img,(int(x0),int(y0)),(int(x1),int(y1)),color,2)
# cv2.putText(img, bbox_name, (int(x0),int(y0)), cv2.FONT_HERSHEY_PLAIN, 1.2, color, 1, cv2.LINE_AA)
return (x0,y0,x1,y1)
def crop_to_plot_area(img, annot, crop_margin=1):
plot_area = get_plot_area(annot)
# print('crop_margin:', crop_margin)
plot_x = plot_area['x0'] + crop_margin; plot_y = plot_area['y0'] + crop_margin;
plot_w = plot_area['width'] - 2*crop_margin; plot_h = plot_area['height'] - 2*crop_margin
# print(img.shape)
# print(plot_area)
# print(plot_x, plot_y, plot_w, plot_h)
# crop out so we only have the plot area
cropped_img = img[plot_y:plot_y+plot_h, plot_x:plot_x+plot_w].copy()
# print(cropped_img.shape)
return cropped_img, (plot_x, plot_y)
def clean_nonline_elements(img, annot, legend_margin=1):
im = Image.fromarray(img)
js_obj = annot
imd = ImageDraw.Draw(im)
if js_obj['task6']['input']['task4_output'] is not None:
plot_bb = js_obj['task6']['input']['task4_output']['_plot_bb']
ploth, plotw , x0, y0 = plot_bb['height'], plot_bb['width'], plot_bb['x0'], plot_bb['y0']
ctp = js_obj['task6']['input']['task1_output']['chart_type']
tb = js_obj['task6']['input']['task2_output']['text_blocks']
legend_boxes = get_legend_boxes(annot=js_obj)
# lp = js_obj['task6']['input']['task5_output']['legend_pairs']
ln_data = js_obj['task6']['output']['visual elements']['lines']
x_axis = js_obj['task6']['input']['task4_output']['axes']['x-axis']
y_axis = js_obj['task6']['input']['task4_output']['axes']['y-axis']
for pt in x_axis :
x_, y_ = pt['tick_pt']['x'], pt['tick_pt']['y']
shape = [(x_, y_), (x_+2, y_+5)]
# print('axis tb', [(x_, y_), (x_+2, y_+5)])
cl = ImageStat.Stat(im).median
imd.rectangle(shape, fill =tuple(cl),outline=None)
for pt in y_axis :
x_, y_ = pt['tick_pt']['x'], pt['tick_pt']['y']
shape = [(x_, y_), (x_+5, y_+2)]
# print('axis tb', [(x_, y_), (x_+2, y_+5)])
cl = ImageStat.Stat(im).median
imd.rectangle(shape, fill =tuple(cl),outline=None)
## remove text box
for bx in tb :
poly = bx['polygon'] if 'polygon' in bx else bx['bb']
# print(poly)
# Handle adobe synth format..
if 'height' in poly:
x_min = poly['x0']
x_max = poly['x0'] + poly['width'] - 1
y_min = poly['y0']
y_max = poly['y0'] + poly['height'] - 1
else:
x_min = min(int(poly['x0']), int(poly['x1']), int(poly['x2']), int(poly['x3']))
x_max = max(int(poly['x0']), int(poly['x1']), int(poly['x2']), int(poly['x3']))
y_min = min(int(poly['y0']), int(poly['y1']), int(poly['y2']), int(poly['y3']))
y_max = max(int(poly['y0']), int(poly['y1']), int(poly['y2']), int(poly['y3']))
# print(x_min,x_max, y_min,y_max)
# img_[y_min:y_max, x_min :x_max, :] = 255
shape = [(x_min, y_min), (x_max, y_max)]
# print('removed tb', [(x_min, y_min), (x_max, y_max)])
cl = ImageStat.Stat(im).median
imd.rectangle(shape, fill =tuple(cl),outline=None)
## remove legend
if legend_boxes:
x_min, y_min, x_max, y_max = get_legend_area(legend_boxes)
x_min -= legend_margin; y_min -= legend_margin
x_max += legend_margin; y_max += legend_margin
shape = [(x_min, y_min), (x_max, y_max)]
# print('removed leg', [(x_min, y_min), (x_max, y_max)])
cl = ImageStat.Stat(im).median
imd.rectangle(shape, fill = tuple(cl),outline=None)
# imd.rectangle(shape, fill=(0,0,0),outline=None)
# print('crop', (x0, y0, x0+plotw, y0+ploth))
# im = im.crop((x0, y0, x0+plotw, y0+ploth))
return np.array(im)
return img
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
# (Borrowed from imutils)initialize the dimensions of the image to be resized and
# grab the image size
dim = None
(h, w) = image.shape[:2]
# if both the width and height are None, then return the
# original image
if width is None and height is None:
return image
# check to see if the width is None
if width is None:
# calculate the ratio of the height and construct the
# dimensions
r = height / float(h)
dim = (int(w * r), height)
# otherwise, the height is None
else:
# calculate the ratio of the width and construct the
# dimensions
r = width / float(w)
dim = (width, int(h * r))
# resize the image
resized = cv2.resize(image, dim, interpolation=inter)
# return the resized image
return resized
def _get_interpolation(inter_string):
# In case it's already an interpolation code..
if isinstance(inter_string, int):
return inter_string
inter_string = inter_string.lower().strip()
inter_methods = {'area':cv2.INTER_AREA,
'linear':cv2.INTER_LINEAR,
'cubic':cv2.INTER_CUBIC,
'nearest':cv2.INTER_NEAREST}
if inter_string not in inter_methods:
raise Exception("Unknown Interpolation Method: '{}'".format(inter_string))
return inter_methods[inter_string]
def padd_square(img, desired_size, padd_color=255):
"""
resize and square padd
img: np.array of image shaped (h,w,c)
desired_size: int size of the image after resize and padding
"""
if padd_color==255 and img.ndim == 3:
padd_color = [255, 255, 255]
size = img.shape[:2]
delta_w = desired_size - size[1]
delta_h = desired_size - size[0]
top, bottom = delta_h//2, delta_h-(delta_h//2)
left, right = delta_w//2, delta_w-(delta_w//2)
new_img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT,
value=padd_color)
return new_img, (left, top)
def get_clean_input(img, annot, crop_to_plot=True, remove_text_legend=True, legend_margin=1, crop_margin=1, max_size=512, padd=True):
"""
img: rgb image of line chart
annot: json obj of PMC groundtruth
max_size: resize max dimension of input to this size (maintaining aspect ratio)
padd: whether to square padd the image after resizing
crop_to_plot: whether to crop the chart image to plot area based on annotation provided
remove_text_legend: whether to remove the text and legend boxes from chart image based on annotation provided
returns: rgb image of cleaned line chart with plot area cropped
"""
clean_img = img if not remove_text_legend else clean_nonline_elements(img, annot, legend_margin)
if crop_to_plot:
clean_img, (tx_crop, ty_crop)= crop_to_plot_area(clean_img, annot, crop_margin)
else:
tx_crop, ty_crop = 0,0
sx, sy= 1,1
tx_padd, ty_padd = 0,0
h_cropped, w_cropped = clean_img.shape[:2]
if max_size:
if clean_img.shape[0] > clean_img.shape[1]:
clean_img = resize(clean_img, height=max_size)
else:
clean_img = resize(clean_img, width=max_size)
sx, sy = float(clean_img.shape[1])/w_cropped, float(clean_img.shape[0])/h_cropped
if padd:
clean_img, (tx_padd, ty_padd) = padd_square(clean_img, max_size)
transformation = (sx, sy, tx_crop, ty_crop, tx_padd, ty_padd)
return clean_img, transformation
# with open(annot_path, 'r') as f:
# annot = json.load(f)