Files
2026-07-13 13:24:13 +08:00

405 lines
16 KiB
Python

"""
Evaluation of -.tar.gz file.
Yu Fang - March 2019
"""
import os
import xml.dom.minidom
# from eval import eval
reg_gt_path = os.path.abspath("data/test")
reg_gt_path_archival = os.path.abspath("data/test")
reg_gt_path_modern = os.path.abspath("data/test")
str_gt_path_1 = os.path.abspath("data/test")
str_gt_path_2 = os.path.abspath("data/test")
str_gt_path_archival = os.path.abspath("data/test")
str_gt_path_modern = os.path.abspath("data/test")
import xml.dom.minidom
# from functools import cmp_to_key
from os.path import join as osj
from .data_structure import *
class eval:
STR = "-str"
REG = "-reg"
DEFAULT_ENCODING = "UTF-8"
# reg_gt_path = "./annotations/trackA/"
# str_gt_path = "./annotations/trackB/"
# reg_gt_path = os.path.abspath("data/test")
# reg_gt_path_archival = os.path.abspath("data/test")
# reg_gt_path_modern = os.path.abspath("data/test")
# str_gt_path_1 = os.path.abspath("data/test")
# str_gt_path_2 = os.path.abspath("data/test")
# str_gt_path_archival = os.path.abspath("data/test")
# str_gt_path_modern = os.path.abspath("data/test")
# dummyDom = xml.dom.minidom.parse("./dummyXML.xml")
def __init__(self, track, res_path):
self.return_result = None
self.reg = True
self.str = False
self.resultFile = res_path
self.inPrefix = os.path.split(res_path)[-1].split(".")[0][:-7]
if track == "-trackA":
self.reg = True
self.GTFile = osj(reg_gt_path, self.inPrefix + ".xml")
# self.GTFile = osj(self.reg_gt_path, self.inPrefix)
elif track == "-trackA1": # archival documents
self.reg = True
self.GTFile = osj(reg_gt_path_archival, self.inPrefix + ".xml")
elif track == "-trackA2": # modern documents
self.reg = True
self.GTFile = osj(reg_gt_path_modern, self.inPrefix + ".xml")
elif track == "-trackB1":
self.str = True
self.GTFile = osj(str_gt_path_1, self.inPrefix + ".xml")
# self.GTFile = osj(self.str_gt_path_1, self.inPrefix)
elif track == "-trackB2":
self.str = True
self.GTFile = osj(str_gt_path_2, self.inPrefix + ".xml")
# print(self.GTFile)
# self.GTFile = osj(self.str_gt_path_2, self.inPrefix)
elif track == "-trackB2_a":
self.str = True
self.GTFile = osj(str_gt_path_archival, self.inPrefix + ".xml")
elif track == "-trackB2_m":
self.str = True
self.GTFile = osj(str_gt_path_modern, self.inPrefix + ".xml")
else:
print(track)
print("Not a valid track, please check your spelling.")
# self.resultFile = res_path
# self.inPrefix = os.path.split(res_path)[-1].split("-")[0]
# if self.str:
# # self.GTFile = osj(self.str_gt_path, self.inPrefix + "-str.xml")
# self.GTFile = osj(self.str_gt_path, self.inPrefix + ".xml")
# elif self.reg:
# # self.GTFile = osj(self.reg_gt_path, self.inPrefix + "-reg.xml")
# self.GTFile = osj(self.reg_gt_path, self.inPrefix + ".xml")
# else:
# print("Not a valid track, please check your spelling.")
self.gene_ret_lst()
@property
def result(self):
return self.return_result
def gene_ret_lst(self):
ret_lst = []
for iou in [0.6, 0.7, 0.8, 0.9]:
temp = self.compute_retVal(iou)
ret_lst.append(temp)
# ret_lst.append(self.compute_retVal(iou))
ret_lst.append(self.inPrefix + ".xml")
# ret_lst.append(self.inPrefix)
# print("Done processing {}\n".format(self.resultFile))
self.return_result = ret_lst
def compute_retVal(self, iou):
gt_dom = xml.dom.minidom.parse(self.GTFile)
# incorrect submission format handling
try:
result_dom = xml.dom.minidom.parse(self.resultFile)
except Exception as e:
# result_dom = xml.dom.minidom.parse(dummyDom)
gt_tables = eval.get_table_list(gt_dom)
retVal = ResultStructure(truePos=0, gtTotal=len(gt_tables), resTotal=0)
return retVal
# result_dom = xml.dom.minidom.parse(self.resultFile)
if self.reg:
ret = self.evaluate_result_reg(gt_dom, result_dom, iou)
return ret
if self.str:
ret = self.evaluate_result_str(gt_dom, result_dom, iou)
return ret
@staticmethod
def get_table_list(dom):
"""
return a list of Table objects corresponding to the table element of the DOM.
"""
return [Table(_nd) for _nd in dom.documentElement.getElementsByTagName("table")]
@staticmethod
def evaluate_result_reg(gt_dom, result_dom, iou_value):
# parse the tables in input elements
gt_tables = eval.get_table_list(gt_dom)
result_tables = eval.get_table_list(result_dom)
# duplicate result table list
remaining_tables = result_tables.copy()
# map the tables in gt and result file
table_matches = [] # @param: table_matches - list of mapping of tables in gt and res file, in order (gt, res)
for gtt in gt_tables:
for rest in remaining_tables:
if gtt.compute_table_iou(rest) >= iou_value:
remaining_tables.remove(rest)
table_matches.append((gtt, rest))
break
assert len(table_matches) <= len(gt_tables)
assert len(table_matches) <= len(result_tables)
retVal = ResultStructure(truePos=len(table_matches), gtTotal=len(gt_tables), resTotal=len(result_tables))
return retVal
@staticmethod
def evaluate_result_str(gt_dom, result_dom, iou_value, table_iou_value=0.8):
# parse the tables in input elements
gt_tables = eval.get_table_list(gt_dom)
result_tables = eval.get_table_list(result_dom)
# duplicate result table list
remaining_tables = result_tables.copy()
gt_remaining = gt_tables.copy()
# map the tables in gt and result file
table_matches = [] # @param: table_matches - list of mapping of tables in gt and res file, in order (gt, res)
for gtt in gt_remaining:
for rest in remaining_tables:
# note: for structural analysis, use 0.8 for table mapping
if gtt.compute_table_iou(rest) >= table_iou_value:
table_matches.append((gtt, rest))
remaining_tables.remove(rest) # unsafe... should be ok with the break below
gt_remaining.remove(gtt)
break
total_gt_relation, total_res_relation, total_correct_relation = 0, 0, 0
for gt_table, ress_table in table_matches:
# set up the cell mapping for matching tables
cell_mapping = gt_table.find_cell_mapping(ress_table, iou_value)
# set up the adj relations, convert the one for result table to a dictionary for faster searching
gt_AR = gt_table.find_adj_relations()
total_gt_relation += len(gt_AR)
res_AR = ress_table.find_adj_relations()
total_res_relation += len(res_AR)
if False: # for DEBUG
Table.printCellMapping(cell_mapping)
Table.printAdjacencyRelationList(gt_AR, "GT")
Table.printAdjacencyRelationList(res_AR, "run")
# Now map GT adjacency relations to result
lMappedAR = []
for ar in gt_AR:
try:
resFromCell = cell_mapping[ar.fromText]
resToCell = cell_mapping[ar.toText]
# make a mapped adjacency relation
lMappedAR.append(AdjRelation(resFromCell, resToCell, ar.direction))
except:
# no mapping is possible
pass
# compare two list of adjacency relation
correct_dect = 0
for ar1 in res_AR:
for ar2 in lMappedAR:
if ar1.isEqual(ar2):
correct_dect += 1
break
total_correct_relation += correct_dect
# handle gt_relations in unmatched gt table
for gtt_remain in gt_remaining:
total_gt_relation += len(gtt_remain.find_adj_relations())
# handle gt_relation in unmatched res table
for res_remain in remaining_tables:
total_res_relation += len(res_remain.find_adj_relations())
retVal = ResultStructure(truePos=total_correct_relation, gtTotal=total_gt_relation, resTotal=total_res_relation)
return retVal
# calculate the gt adj_relations of the missing file
# @param: file_lst - list of missing ground truth file
# @param: cur_gt_num - current total of ground truth objects (tables / cells)
def process_missing_files(track, gt_file_lst, cur_gt_num):
if track in ["-trackA", "-trackA1", "-trackA2"]:
gt_file_lst_full = [osj(reg_gt_path, filename) for filename in gt_file_lst]
for file in gt_file_lst_full:
if os.path.split(file)[-1].split(".")[-1] == "xml":
gt_dom = xml.dom.minidom.parse(file)
gt_root = gt_dom.documentElement
# tables = []
table_elements = gt_root.getElementsByTagName("table")
for res_table in table_elements:
# t = Table(res_table)
# tables.append(t)
cur_gt_num += 1
return cur_gt_num
elif track == "-trackB1":
gt_file_lst_full = [osj(str_gt_path_1, filename) for filename in gt_file_lst]
for file in gt_file_lst_full:
if os.path.split(file)[-1].split(".")[-1] == "xml":
gt_dom = xml.dom.minidom.parse(file)
gt_root = gt_dom.documentElement
tables = []
table_elements = gt_root.getElementsByTagName("table")
for res_table in table_elements:
t = Table(res_table)
tables.append(t)
for table in tables:
cur_gt_num += len(table.find_adj_relations())
return cur_gt_num
elif track == "-trackB2":
gt_file_lst_full = [osj(str_gt_path_2, filename) for filename in gt_file_lst]
for file in gt_file_lst_full:
if os.path.split(file)[-1].split(".")[-1] == "xml":
gt_dom = xml.dom.minidom.parse(file)
gt_root = gt_dom.documentElement
tables = []
table_elements = gt_root.getElementsByTagName("table")
for res_table in table_elements:
t = Table(res_table)
tables.append(t)
for table in tables:
cur_gt_num += len(table.find_adj_relations())
return cur_gt_num
def calc(F1):
sum_a = 0.6 * F1[0] + 0.7 * F1[1] + 0.8 * F1[2] + 0.9 * F1[3]
sum_b = 0.6 + 0.7 + 0.8 + 0.9
return sum_a / sum_b
def calc_table_score(result_path):
# measure = eval(*sys.argv[1:])
gt_file_lst = os.listdir(reg_gt_path_archival)
track = "-trackA1"
untar_path = result_path
res_lst = []
for root, files, dirs in os.walk(untar_path):
for name in dirs:
if name.split(".")[-1] == "xml":
cur_filepath = osj(os.path.abspath(root), name)
res_lst.append(eval(track, cur_filepath))
# printing for debug
# print("Processing... {}".format(name))
# print("DONE WITH FILE PROCESSING\n")
# note: results are stored as list of each when iou at [0.6, 0.7, 0.8, 0.9, gt_filename]
# gt number should be the same for all files
gt_num = 0
correct_six, res_six = 0, 0
correct_seven, res_seven = 0, 0
correct_eight, res_eight = 0, 0
correct_nine, res_nine = 0, 0
for each_file in res_lst:
# print(each_file)
try:
gt_file_lst.remove(each_file.result[-1])
if each_file.result[-1].replace('.xml', '.jpg') in gt_file_lst:
gt_file_lst.remove(each_file.result[-1].replace('.xml', '.jpg'))
correct_six += each_file.result[0].truePos
gt_num += each_file.result[0].gtTotal
res_six += each_file.result[0].resTotal
# print("{} {} {}".format(each_file.result[0].truePos, each_file.result[0].gtTotal, each_file.result[0].resTotal))
correct_seven += each_file.result[1].truePos
res_seven += each_file.result[1].resTotal
correct_eight += each_file.result[2].truePos
res_eight += each_file.result[2].resTotal
correct_nine += each_file.result[3].truePos
res_nine += each_file.result[3].resTotal
except:
print("Error occur in processing result list.")
print(each_file.result[-1])
break
# print(each_file.result[-1])
# print(each_file)
# for file in gt_file_lst:
# if file.split(".") != "xml":
# gt_file_lst.remove(file)
# # print(gt_file_lst)
for i in range(len(gt_file_lst) - 1, -1, -1):
if gt_file_lst[i].split(".")[-1] != "xml":
del gt_file_lst[i]
if len(gt_file_lst) > 0:
print("\nWarning: missing result annotations for file: {}\n".format(gt_file_lst))
gt_total = process_missing_files(track, gt_file_lst, gt_num)
else:
gt_total = gt_num
try:
# print("Evaluation of {}".format(track.replace("-", "")))
# iou @ 0.6
p_six = correct_six / res_six
r_six = correct_six / gt_total
f1_six = 2 * p_six * r_six / (p_six + r_six)
print("IOU @ 0.6 -\nprecision: {}\nrecall: {}\nf1: {}".format(p_six, r_six, f1_six))
print("correct: {}, gt: {}, res: {}\n".format(correct_six, gt_total, res_six))
# iou @ 0.7
p_seven = correct_seven / res_seven
r_seven = correct_seven / gt_total
f1_seven = 2 * p_seven * r_seven / (p_seven + r_seven)
print("IOU @ 0.7 -\nprecision: {}\nrecall: {}\nf1: {}".format(p_seven, r_seven, f1_seven))
print("correct: {}, gt: {}, res: {}\n".format(correct_seven, gt_total, res_seven))
# iou @ 0.8
p_eight = correct_eight / res_eight
r_eight = correct_eight / gt_total
f1_eight = 2 * p_eight * r_eight / (p_eight + r_eight)
print("IOU @ 0.8 -\nprecision: {}\nrecall: {}\nf1: {}".format(p_eight, r_eight, f1_eight))
print("correct: {}, gt: {}, res: {}\n".format(correct_eight, gt_total, res_eight))
# iou @ 0.9
p_nine = correct_nine / res_nine
r_nine = correct_nine / gt_total
f1_nine = 2 * p_nine * r_nine / (p_nine + r_nine)
print("IOU @ 0.9 -\nprecision: {}\nrecall: {}\nf1: {}".format(p_nine, r_nine, f1_nine))
print("correct: {}, gt: {}, res: {}".format(correct_nine, gt_total, res_nine))
F1 = [f1_six, f1_seven, f1_eight, f1_nine]
wF1 = calc(F1)
print("Average weight F1: {}".format(wF1))
return {
'p_six':p_six * 100,
"r_six":r_six * 100,
"f1_six":f1_six * 100,
"p_seven":p_seven * 100,
"r_seven":r_seven * 100,
"f1_seven":f1_seven * 100,
"p_eight":p_eight * 100,
"r_eight":r_eight * 100,
"f1_eight":f1_eight * 100,
"p_nine":p_nine * 100,
"r_nine":r_nine * 100,
"f1_nine":f1_nine * 100,
"wF1":wF1 * 100
}
except ZeroDivisionError:
print(
"Error: zero devision error found, (possible that no adjacency relations are found), please check the file input.")
return {"wF1": 0}
if __name__=="__main__":
pass