magic_model_utils.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. """
  2. 包含两个MagicModel类中重复使用的方法和逻辑
  3. """
  4. from typing import List, Dict, Any, Callable
  5. from loguru import logger
  6. from mineru.utils.boxbase import bbox_distance, bbox_center_distance, is_in
  7. def reduct_overlap(bboxes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
  8. """
  9. 去除重叠的bbox,保留不被其他bbox包含的bbox
  10. Args:
  11. bboxes: 包含bbox信息的字典列表
  12. Returns:
  13. 去重后的bbox列表
  14. """
  15. N = len(bboxes)
  16. keep = [True] * N
  17. for i in range(N):
  18. for j in range(N):
  19. if i == j:
  20. continue
  21. if is_in(bboxes[i]['bbox'], bboxes[j]['bbox']):
  22. keep[i] = False
  23. return [bboxes[i] for i in range(N) if keep[i]]
  24. def tie_up_category_by_distance_v3(
  25. get_subjects_func: Callable,
  26. get_objects_func: Callable,
  27. extract_subject_func: Callable = None,
  28. extract_object_func: Callable = None
  29. ):
  30. """
  31. 通用的类别关联方法,用于将主体对象与客体对象进行关联
  32. 参数:
  33. get_subjects_func: 函数,提取主体对象
  34. get_objects_func: 函数,提取客体对象
  35. extract_subject_func: 函数,自定义提取主体属性(默认使用bbox和其他属性)
  36. extract_object_func: 函数,自定义提取客体属性(默认使用bbox和其他属性)
  37. 返回:
  38. 关联后的对象列表
  39. """
  40. subjects = get_subjects_func()
  41. objects = get_objects_func()
  42. # 如果没有提供自定义提取函数,使用默认函数
  43. if extract_subject_func is None:
  44. extract_subject_func = lambda x: x
  45. if extract_object_func is None:
  46. extract_object_func = lambda x: x
  47. ret = []
  48. N, M = len(subjects), len(objects)
  49. subjects.sort(key=lambda x: x["bbox"][0] ** 2 + x["bbox"][1] ** 2)
  50. objects.sort(key=lambda x: x["bbox"][0] ** 2 + x["bbox"][1] ** 2)
  51. OBJ_IDX_OFFSET = 10000
  52. SUB_BIT_KIND, OBJ_BIT_KIND = 0, 1
  53. all_boxes_with_idx = [(i, SUB_BIT_KIND, sub["bbox"][0], sub["bbox"][1]) for i, sub in enumerate(subjects)] + [
  54. (i + OBJ_IDX_OFFSET, OBJ_BIT_KIND, obj["bbox"][0], obj["bbox"][1]) for i, obj in enumerate(objects)
  55. ]
  56. seen_idx = set()
  57. seen_sub_idx = set()
  58. while N > len(seen_sub_idx):
  59. candidates = []
  60. for idx, kind, x0, y0 in all_boxes_with_idx:
  61. if idx in seen_idx:
  62. continue
  63. candidates.append((idx, kind, x0, y0))
  64. if len(candidates) == 0:
  65. break
  66. left_x = min([v[2] for v in candidates])
  67. top_y = min([v[3] for v in candidates])
  68. candidates.sort(key=lambda x: (x[2] - left_x) ** 2 + (x[3] - top_y) ** 2)
  69. fst_idx, fst_kind, left_x, top_y = candidates[0]
  70. fst_bbox = subjects[fst_idx]['bbox'] if fst_kind == SUB_BIT_KIND else objects[fst_idx - OBJ_IDX_OFFSET]['bbox']
  71. candidates.sort(
  72. key=lambda x: bbox_distance(fst_bbox, subjects[x[0]]['bbox']) if x[1] == SUB_BIT_KIND else bbox_distance(
  73. fst_bbox, objects[x[0] - OBJ_IDX_OFFSET]['bbox']))
  74. nxt = None
  75. for i in range(1, len(candidates)):
  76. if candidates[i][1] ^ fst_kind == 1:
  77. nxt = candidates[i]
  78. break
  79. if nxt is None:
  80. break
  81. if fst_kind == SUB_BIT_KIND:
  82. sub_idx, obj_idx = fst_idx, nxt[0] - OBJ_IDX_OFFSET
  83. else:
  84. sub_idx, obj_idx = nxt[0], fst_idx - OBJ_IDX_OFFSET
  85. pair_dis = bbox_distance(subjects[sub_idx]["bbox"], objects[obj_idx]["bbox"])
  86. nearest_dis = float("inf")
  87. for i in range(N):
  88. # 取消原先算法中 1对1 匹配的偏置
  89. # if i in seen_idx or i == sub_idx:continue
  90. nearest_dis = min(nearest_dis, bbox_distance(subjects[i]["bbox"], objects[obj_idx]["bbox"]))
  91. if pair_dis >= 3 * nearest_dis:
  92. seen_idx.add(sub_idx)
  93. continue
  94. seen_idx.add(sub_idx)
  95. seen_idx.add(obj_idx + OBJ_IDX_OFFSET)
  96. seen_sub_idx.add(sub_idx)
  97. ret.append(
  98. {
  99. "sub_bbox": extract_subject_func(subjects[sub_idx]),
  100. "obj_bboxes": [extract_object_func(objects[obj_idx])],
  101. "sub_idx": sub_idx,
  102. }
  103. )
  104. for i in range(len(objects)):
  105. j = i + OBJ_IDX_OFFSET
  106. if j in seen_idx:
  107. continue
  108. seen_idx.add(j)
  109. nearest_dis, nearest_sub_idx = float("inf"), -1
  110. for k in range(len(subjects)):
  111. dis = bbox_distance(objects[i]["bbox"], subjects[k]["bbox"])
  112. if dis < nearest_dis:
  113. nearest_dis = dis
  114. nearest_sub_idx = k
  115. for k in range(len(subjects)):
  116. if k != nearest_sub_idx:
  117. continue
  118. if k in seen_sub_idx:
  119. for kk in range(len(ret)):
  120. if ret[kk]["sub_idx"] == k:
  121. ret[kk]["obj_bboxes"].append(extract_object_func(objects[i]))
  122. break
  123. else:
  124. ret.append(
  125. {
  126. "sub_bbox": extract_subject_func(subjects[k]),
  127. "obj_bboxes": [extract_object_func(objects[i])],
  128. "sub_idx": k,
  129. }
  130. )
  131. seen_sub_idx.add(k)
  132. seen_idx.add(k)
  133. for i in range(len(subjects)):
  134. if i in seen_sub_idx:
  135. continue
  136. ret.append(
  137. {
  138. "sub_bbox": extract_subject_func(subjects[i]),
  139. "obj_bboxes": [],
  140. "sub_idx": i,
  141. }
  142. )
  143. return ret
  144. def tie_up_category_by_index(
  145. get_subjects_func: Callable,
  146. get_objects_func: Callable,
  147. extract_subject_func: Callable = None,
  148. extract_object_func: Callable = None,
  149. object_block_type: str = "object",
  150. ):
  151. """
  152. 基于index的类别关联方法,用于将主体对象与客体对象进行关联
  153. 客体优先匹配给index最接近的主体,匹配优先级为:
  154. 1. index差值(最高优先级)
  155. 2. bbox边缘距离(相邻边距离)
  156. 3. bbox中心点距离(最低优先级,作为最终tiebreaker)
  157. 参数:
  158. get_subjects_func: 函数,提取主体对象
  159. get_objects_func: 函数,提取客体对象
  160. extract_subject_func: 函数,自定义提取主体属性(默认使用bbox和其他属性)
  161. extract_object_func: 函数,自定义提取客体属性(默认使用bbox和其他属性)
  162. 返回:
  163. 关联后的对象列表,按主体index升序排列
  164. """
  165. subjects = get_subjects_func()
  166. objects = get_objects_func()
  167. # 如果没有提供自定义提取函数,使用默认函数
  168. if extract_subject_func is None:
  169. extract_subject_func = lambda x: x
  170. if extract_object_func is None:
  171. extract_object_func = lambda x: x
  172. # 初始化结果字典,key为主体索引,value为关联信息
  173. result_dict = {}
  174. # 初始化所有主体
  175. for i, subject in enumerate(subjects):
  176. result_dict[i] = {
  177. "sub_bbox": extract_subject_func(subject),
  178. "obj_bboxes": [],
  179. "sub_idx": i,
  180. }
  181. # 提取所有客体的index集合,用于计算有效index差值
  182. object_indices = set(obj["index"] for obj in objects)
  183. def calc_effective_index_diff(obj_index: int, sub_index: int) -> int:
  184. """
  185. 计算有效的index差值
  186. 有效差值 = 绝对差值 - 区间内其他客体的数量
  187. 即:如果obj_index和sub_index之间的差值是由其他客体造成的,则应该扣除这部分差值
  188. """
  189. if obj_index == sub_index:
  190. return 0
  191. start, end = min(obj_index, sub_index), max(obj_index, sub_index)
  192. abs_diff = end - start
  193. # 计算区间(start, end)内有多少个其他客体的index
  194. other_objects_count = 0
  195. for idx in range(start + 1, end):
  196. if idx in object_indices:
  197. other_objects_count += 1
  198. return abs_diff - other_objects_count
  199. # 为每个客体找到最匹配的主体
  200. for obj in objects:
  201. if len(subjects) == 0:
  202. # 如果没有主体,跳过客体
  203. continue
  204. obj_index = obj["index"]
  205. min_index_diff = float("inf")
  206. best_subject_indices = []
  207. # 找出有效index差值最小的所有主体
  208. for i, subject in enumerate(subjects):
  209. sub_index = subject["index"]
  210. index_diff = calc_effective_index_diff(obj_index, sub_index)
  211. if index_diff < min_index_diff:
  212. min_index_diff = index_diff
  213. best_subject_indices = [i]
  214. elif index_diff == min_index_diff:
  215. best_subject_indices.append(i)
  216. if len(best_subject_indices) == 1:
  217. best_subject_idx = best_subject_indices[0]
  218. # 如果有多个主体的index差值相同(最多两个),根据边缘距离进行筛选
  219. elif len(best_subject_indices) == 2:
  220. # 计算所有候选主体的边缘距离
  221. edge_distances = [(idx, bbox_distance(obj["bbox"], subjects[idx]["bbox"])) for idx in best_subject_indices]
  222. edge_dist_diff = abs(edge_distances[0][1] - edge_distances[1][1])
  223. for idx, edge_dist in edge_distances:
  224. logger.debug(f"Obj index: {obj_index}, Sub index: {subjects[idx]['index']}, Edge distance: {edge_dist}")
  225. if edge_dist_diff > 2:
  226. # 边缘距离差值大于2,匹配边缘距离更小的主体
  227. best_subject_idx = min(edge_distances, key=lambda x: x[1])[0]
  228. logger.debug(f"Obj index: {obj_index}, edge_dist_diff > 2, matching to subject with min edge distance, index: {subjects[best_subject_idx]['index']}")
  229. elif object_block_type == "table_caption":
  230. # 边缘距离差值<=2且为table_caption,匹配index更大的主体
  231. best_subject_idx = max(best_subject_indices, key=lambda idx: subjects[idx]["index"])
  232. logger.debug(f"Obj index: {obj_index}, edge_dist_diff <= 2 and table_caption, matching to later subject with index: {subjects[best_subject_idx]['index']}")
  233. elif object_block_type.endswith("footnote"):
  234. # 边缘距离差值<=2且为footnote,匹配index更小的主体
  235. best_subject_idx = min(best_subject_indices, key=lambda idx: subjects[idx]["index"])
  236. logger.debug(f"Obj index: {obj_index}, edge_dist_diff <= 2 and footnote, matching to earlier subject with index: {subjects[best_subject_idx]['index']}")
  237. else:
  238. # 边缘距离差值<=2 且不适用特殊匹配规则,使用中心点距离匹配
  239. center_distances = [(idx, bbox_center_distance(obj["bbox"], subjects[idx]["bbox"])) for idx in best_subject_indices]
  240. for idx, center_dist in center_distances:
  241. logger.debug(f"Obj index: {obj_index}, Sub index: {subjects[idx]['index']}, Center distance: {center_dist}")
  242. best_subject_idx = min(center_distances, key=lambda x: x[1])[0]
  243. else:
  244. raise ValueError("More than two subjects have the same minimal index difference, which is unexpected.")
  245. # 将客体添加到最佳主体的obj_bboxes中
  246. result_dict[best_subject_idx]["obj_bboxes"].append(extract_object_func(obj))
  247. # 转换为列表并按主体index排序
  248. ret = list(result_dict.values())
  249. ret.sort(key=lambda x: x["sub_idx"])
  250. return ret