|
|
@@ -464,3 +464,421 @@ if __name__ == "__main__":
|
|
|
magic_model = MagicModel(model_list, pdf_docs)
|
|
|
for i in range(7):
|
|
|
print(magic_model.get_imgs(i))
|
|
|
+
|
|
|
+
|
|
|
+ def __reduct_overlap(self, bboxes):
|
|
|
+ N = len(bboxes)
|
|
|
+ keep = [True] * N
|
|
|
+ for i in range(N):
|
|
|
+ for j in range(N):
|
|
|
+ if i == j:
|
|
|
+ continue
|
|
|
+ if _is_in(bboxes[i], bboxes[j]):
|
|
|
+ keep[i] = False
|
|
|
+
|
|
|
+ return [bboxes[i] for i in range(N) if keep[i]]
|
|
|
+
|
|
|
+ def __tie_up_category_by_distance(
|
|
|
+ self, page_no, subject_category_id, object_category_id
|
|
|
+ ):
|
|
|
+ """
|
|
|
+ 假定每个 subject 最多有一个 object (可以有多个相邻的 object 合并为单个 object),每个 object 只能属于一个 subject
|
|
|
+ """
|
|
|
+ ret = []
|
|
|
+ MAX_DIS_OF_POINT = 10**9 + 7
|
|
|
+
|
|
|
+ subjects = self.__reduct_overlap(
|
|
|
+ list(
|
|
|
+ map(
|
|
|
+ lambda x: x["bbox"],
|
|
|
+ filter(
|
|
|
+ lambda x: x["category_id"] == subject_category_id,
|
|
|
+ self.__model_list[page_no]["layout_dets"],
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ objects = self.__reduct_overlap(
|
|
|
+ list(
|
|
|
+ map(
|
|
|
+ lambda x: x["bbox"],
|
|
|
+ filter(
|
|
|
+ lambda x: x["category_id"] == object_category_id,
|
|
|
+ self.__model_list[page_no]["layout_dets"],
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+ subject_object_relation_map = {}
|
|
|
+
|
|
|
+ subjects.sort(key=lambda x: x[0] ** 2 + x[1] ** 2) # get the distance !
|
|
|
+
|
|
|
+ all_bboxes = []
|
|
|
+
|
|
|
+ for v in subjects:
|
|
|
+ all_bboxes.append({"category_id": subject_category_id, "bbox": v})
|
|
|
+
|
|
|
+ for v in objects:
|
|
|
+ all_bboxes.append({"category_id": object_category_id, "bbox": v})
|
|
|
+
|
|
|
+ N = len(all_bboxes)
|
|
|
+ dis = [[MAX_DIS_OF_POINT] * N for _ in range(N)]
|
|
|
+
|
|
|
+ for i in range(N):
|
|
|
+ for j in range(i):
|
|
|
+ if (
|
|
|
+ all_bboxes[i]["category_id"] == subject_category_id
|
|
|
+ and all_bboxes[j]["category_id"] == subject_category_id
|
|
|
+ ):
|
|
|
+ continue
|
|
|
+
|
|
|
+ dis[i][j] = bbox_distance(all_bboxes[i]["bbox"], all_bboxes[j]["bbox"])
|
|
|
+ dis[j][i] = dis[i][j]
|
|
|
+
|
|
|
+ used = set()
|
|
|
+ for i in range(N):
|
|
|
+ # 求第 i 个 subject 所关联的 object
|
|
|
+ if all_bboxes[i]["category_id"] != subject_category_id:
|
|
|
+ continue
|
|
|
+ seen = set()
|
|
|
+ candidates = []
|
|
|
+ arr = []
|
|
|
+ for j in range(N):
|
|
|
+
|
|
|
+ pos_flag_count = sum(
|
|
|
+ list(
|
|
|
+ map(
|
|
|
+ lambda x: 1 if x else 0,
|
|
|
+ bbox_relative_pos(
|
|
|
+ all_bboxes[i]["bbox"], all_bboxes[j]["bbox"]
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+ if pos_flag_count > 1:
|
|
|
+ continue
|
|
|
+ if (
|
|
|
+ all_bboxes[j]["category_id"] != object_category_id
|
|
|
+ or j in used
|
|
|
+ or dis[i][j] == MAX_DIS_OF_POINT
|
|
|
+ ):
|
|
|
+ continue
|
|
|
+ arr.append((dis[i][j], j))
|
|
|
+
|
|
|
+ arr.sort(key=lambda x: x[0])
|
|
|
+ if len(arr) > 0:
|
|
|
+ candidates.append(arr[0][1])
|
|
|
+ seen.add(arr[0][1])
|
|
|
+
|
|
|
+ # 已经获取初始种子
|
|
|
+ for j in set(candidates):
|
|
|
+ tmp = []
|
|
|
+ for k in range(i + 1, N):
|
|
|
+ pos_flag_count = sum(
|
|
|
+ list(
|
|
|
+ map(
|
|
|
+ lambda x: 1 if x else 0,
|
|
|
+ bbox_relative_pos(
|
|
|
+ all_bboxes[j]["bbox"], all_bboxes[k]["bbox"]
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ )
|
|
|
+ )
|
|
|
+
|
|
|
+ if pos_flag_count > 1:
|
|
|
+ continue
|
|
|
+
|
|
|
+ if (
|
|
|
+ all_bboxes[k]["category_id"] != object_category_id
|
|
|
+ or k in used
|
|
|
+ or k in seen
|
|
|
+ or dis[j][k] == MAX_DIS_OF_POINT
|
|
|
+ ):
|
|
|
+ continue
|
|
|
+ is_nearest = True
|
|
|
+ for l in range(i + 1, N):
|
|
|
+ if l in (j, k) or l in used or l in seen:
|
|
|
+ continue
|
|
|
+
|
|
|
+
|
|
|
+ if not float_gt(dis[l][k], dis[j][k]):
|
|
|
+ is_nearest = False
|
|
|
+ break
|
|
|
+
|
|
|
+
|
|
|
+ if is_nearest:
|
|
|
+ tmp.append(k)
|
|
|
+ seen.add(k)
|
|
|
+
|
|
|
+ candidates = tmp
|
|
|
+ if len(candidates) == 0:
|
|
|
+ break
|
|
|
+
|
|
|
+ # 已经获取到某个 figure 下所有的最靠近的 captions,以及最靠近这些 captions 的 captions 。
|
|
|
+ # 先扩一下 bbox,
|
|
|
+ x0s = [all_bboxes[idx]["bbox"][0] for idx in seen] + [
|
|
|
+ all_bboxes[i]["bbox"][0]
|
|
|
+ ]
|
|
|
+ y0s = [all_bboxes[idx]["bbox"][1] for idx in seen] + [
|
|
|
+ all_bboxes[i]["bbox"][1]
|
|
|
+ ]
|
|
|
+ x1s = [all_bboxes[idx]["bbox"][2] for idx in seen] + [
|
|
|
+ all_bboxes[i]["bbox"][2]
|
|
|
+ ]
|
|
|
+ y1s = [all_bboxes[idx]["bbox"][3] for idx in seen] + [
|
|
|
+ all_bboxes[i]["bbox"][3]
|
|
|
+ ]
|
|
|
+
|
|
|
+ ox0, oy0, ox1, oy1 = min(x0s), min(y0s), max(x1s), max(y1s)
|
|
|
+ ix0, iy0, ix1, iy1 = all_bboxes[i]["bbox"]
|
|
|
+
|
|
|
+ # 分成了 4 个截取空间,需要计算落在每个截取空间下 objects 合并后占据的矩形面积
|
|
|
+ caption_poses = [
|
|
|
+ [ox0, oy0, ix0, oy1],
|
|
|
+ [ox0, oy0, ox1, iy0],
|
|
|
+ [ox0, iy1, ox1, oy1],
|
|
|
+ [ix1, oy0, ox1, oy1],
|
|
|
+ ]
|
|
|
+
|
|
|
+ caption_areas = []
|
|
|
+ for bbox in caption_poses:
|
|
|
+ embed_arr = []
|
|
|
+ for idx in seen:
|
|
|
+ if _is_in(all_bboxes[idx]["bbox"], bbox):
|
|
|
+ embed_arr.append(idx)
|
|
|
+
|
|
|
+ if len(embed_arr) > 0:
|
|
|
+ embed_x0 = min([all_bboxes[idx]["bbox"][0] for idx in embed_arr])
|
|
|
+ embed_y0 = min([all_bboxes[idx]["bbox"][1] for idx in embed_arr])
|
|
|
+ embed_x1 = max([all_bboxes[idx]["bbox"][2] for idx in embed_arr])
|
|
|
+ embed_y1 = max([all_bboxes[idx]["bbox"][3] for idx in embed_arr])
|
|
|
+ caption_areas.append(
|
|
|
+ int(abs(embed_x1 - embed_x0) * abs(embed_y1 - embed_y0))
|
|
|
+ )
|
|
|
+ else:
|
|
|
+ caption_areas.append(0)
|
|
|
+
|
|
|
+ subject_object_relation_map[i] = []
|
|
|
+ if max(caption_areas) > 0:
|
|
|
+ max_area_idx = caption_areas.index(max(caption_areas))
|
|
|
+ caption_bbox = caption_poses[max_area_idx]
|
|
|
+
|
|
|
+ for j in seen:
|
|
|
+ if _is_in(all_bboxes[j]["bbox"], caption_bbox):
|
|
|
+ used.add(j)
|
|
|
+ subject_object_relation_map[i].append(j)
|
|
|
+
|
|
|
+ for i in sorted(subject_object_relation_map.keys()):
|
|
|
+ result = {
|
|
|
+ "subject_body": all_bboxes[i]["bbox"],
|
|
|
+ "all": all_bboxes[i]["bbox"],
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(subject_object_relation_map[i]) > 0:
|
|
|
+ x0 = min(
|
|
|
+ [all_bboxes[j]["bbox"][0] for j in subject_object_relation_map[i]]
|
|
|
+ )
|
|
|
+ y0 = min(
|
|
|
+ [all_bboxes[j]["bbox"][1] for j in subject_object_relation_map[i]]
|
|
|
+ )
|
|
|
+ x1 = max(
|
|
|
+ [all_bboxes[j]["bbox"][2] for j in subject_object_relation_map[i]]
|
|
|
+ )
|
|
|
+ y1 = max(
|
|
|
+ [all_bboxes[j]["bbox"][3] for j in subject_object_relation_map[i]]
|
|
|
+ )
|
|
|
+ result["object_body"] = [x0, y0, x1, y1]
|
|
|
+ result["all"] = [
|
|
|
+ min(x0, all_bboxes[i]["bbox"][0]),
|
|
|
+ min(y0, all_bboxes[i]["bbox"][1]),
|
|
|
+ max(x1, all_bboxes[i]["bbox"][2]),
|
|
|
+ max(y1, all_bboxes[i]["bbox"][3]),
|
|
|
+ ]
|
|
|
+ ret.append(result)
|
|
|
+
|
|
|
+ total_subject_object_dis = 0
|
|
|
+ # 计算已经配对的 distance 距离
|
|
|
+ for i in subject_object_relation_map.keys():
|
|
|
+ for j in subject_object_relation_map[i]:
|
|
|
+ total_subject_object_dis += bbox_distance(
|
|
|
+ all_bboxes[i]["bbox"], all_bboxes[j]["bbox"]
|
|
|
+ )
|
|
|
+
|
|
|
+ # 计算未匹配的 subject 和 object 的距离(非精确版)
|
|
|
+ with_caption_subject = set(
|
|
|
+ [
|
|
|
+ key
|
|
|
+ for key in subject_object_relation_map.keys()
|
|
|
+ if len(subject_object_relation_map[i]) > 0
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ for i in range(N):
|
|
|
+ if all_bboxes[i]["category_id"] != object_category_id or i in used:
|
|
|
+ continue
|
|
|
+ candidates = []
|
|
|
+ for j in range(N):
|
|
|
+ if (
|
|
|
+ all_bboxes[j]["category_id"] != subject_category_id
|
|
|
+ or j in with_caption_subject
|
|
|
+ ):
|
|
|
+ continue
|
|
|
+ candidates.append((dis[i][j], j))
|
|
|
+ if len(candidates) > 0:
|
|
|
+ candidates.sort(key=lambda x: x[0])
|
|
|
+ total_subject_object_dis += candidates[0][1]
|
|
|
+ with_caption_subject.add(j)
|
|
|
+ return ret, total_subject_object_dis
|
|
|
+
|
|
|
+ def get_imgs(self, page_no: int): # @许瑞
|
|
|
+ records, _ = self.__tie_up_category_by_distance(page_no, 3, 4)
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ "bbox": record["all"],
|
|
|
+ "img_body_bbox": record["subject_body"],
|
|
|
+ "img_caption_bbox": record.get("object_body", None),
|
|
|
+ }
|
|
|
+ for record in records
|
|
|
+ ]
|
|
|
+
|
|
|
+ def get_tables(
|
|
|
+ self, page_no: int
|
|
|
+ ) -> list: # 3个坐标, caption, table主体,table-note
|
|
|
+ with_captions, _ = self.__tie_up_category_by_distance(page_no, 5, 6)
|
|
|
+ with_footnotes, _ = self.__tie_up_category_by_distance(page_no, 5, 7)
|
|
|
+ ret = []
|
|
|
+ N, M = len(with_captions), len(with_footnotes)
|
|
|
+ assert N == M
|
|
|
+ for i in range(N):
|
|
|
+ record = {
|
|
|
+ "table_caption_bbox": with_captions[i].get("object_body", None),
|
|
|
+ "table_body_bbox": with_captions[i]["subject_body"],
|
|
|
+ "table_footnote_bbox": with_footnotes[i].get("object_body", None),
|
|
|
+ }
|
|
|
+
|
|
|
+ x0 = min(with_captions[i]["all"][0], with_footnotes[i]["all"][0])
|
|
|
+ y0 = min(with_captions[i]["all"][1], with_footnotes[i]["all"][1])
|
|
|
+ x1 = max(with_captions[i]["all"][2], with_footnotes[i]["all"][2])
|
|
|
+ y1 = max(with_captions[i]["all"][3], with_footnotes[i]["all"][3])
|
|
|
+ record["bbox"] = [x0, y0, x1, y1]
|
|
|
+ ret.append(record)
|
|
|
+ return ret
|
|
|
+
|
|
|
+ def get_equations(self, page_no: int) -> list: # 有坐标,也有字
|
|
|
+ inline_equations = self.__get_blocks_by_type(ModelBlockTypeEnum.EMBEDDING.value, page_no, ["latex"])
|
|
|
+ interline_equations = self.__get_blocks_by_type(ModelBlockTypeEnum.ISOLATED.value, page_no, ["latex"])
|
|
|
+ interline_equations_blocks = self.__get_blocks_by_type(ModelBlockTypeEnum.ISOLATE_FORMULA.value, page_no)
|
|
|
+ return inline_equations, interline_equations, interline_equations_blocks
|
|
|
+
|
|
|
+ def get_discarded(self, page_no: int) -> list: # 自研模型,只有坐标
|
|
|
+ blocks = self.__get_blocks_by_type(ModelBlockTypeEnum.ABANDON.value, page_no)
|
|
|
+ return blocks
|
|
|
+
|
|
|
+ def get_text_blocks(self, page_no: int) -> list: # 自研模型搞的,只有坐标,没有字
|
|
|
+ blocks = self.__get_blocks_by_type(ModelBlockTypeEnum.PLAIN_TEXT.value, page_no)
|
|
|
+ return blocks
|
|
|
+
|
|
|
+ def get_title_blocks(self, page_no: int) -> list: # 自研模型,只有坐标,没字
|
|
|
+ blocks = self.__get_blocks_by_type(ModelBlockTypeEnum.TITLE.value, page_no)
|
|
|
+ return blocks
|
|
|
+
|
|
|
+ def get_ocr_text(self, page_no: int) -> list: # paddle 搞的,有字也有坐标
|
|
|
+ text_spans = []
|
|
|
+ model_page_info = self.__model_list[page_no]
|
|
|
+ layout_dets = model_page_info["layout_dets"]
|
|
|
+ for layout_det in layout_dets:
|
|
|
+ if layout_det["category_id"] == "15":
|
|
|
+ span = {
|
|
|
+ "bbox": layout_det['bbox'],
|
|
|
+ "content": layout_det["text"],
|
|
|
+ }
|
|
|
+ text_spans.append(span)
|
|
|
+ return text_spans
|
|
|
+
|
|
|
+ def get_all_spans(self, page_no: int) -> list:
|
|
|
+ all_spans = []
|
|
|
+ model_page_info = self.__model_list[page_no]
|
|
|
+ layout_dets = model_page_info["layout_dets"]
|
|
|
+ allow_category_id_list = [3, 5, 13, 14, 15]
|
|
|
+ """当成span拼接的"""
|
|
|
+ # 3: 'image', # 图片
|
|
|
+ # 4: 'table', # 表格
|
|
|
+ # 13: 'inline_equation', # 行内公式
|
|
|
+ # 14: 'interline_equation', # 行间公式
|
|
|
+ # 15: 'text', # ocr识别文本
|
|
|
+ for layout_det in layout_dets:
|
|
|
+ category_id = layout_det["category_id"]
|
|
|
+ if category_id in allow_category_id_list:
|
|
|
+ span = {
|
|
|
+ "bbox": layout_det['bbox']
|
|
|
+ }
|
|
|
+ if category_id == 3:
|
|
|
+ span["type"] = ContentType.Image
|
|
|
+ elif category_id == 5:
|
|
|
+ span["type"] = ContentType.Table
|
|
|
+ elif category_id == 13:
|
|
|
+ span["content"] = layout_det["latex"]
|
|
|
+ span["type"] = ContentType.InlineEquation
|
|
|
+ elif category_id == 14:
|
|
|
+ span["content"] = layout_det["latex"]
|
|
|
+ span["type"] = ContentType.InterlineEquation
|
|
|
+ elif category_id == 15:
|
|
|
+ span["content"] = layout_det["text"]
|
|
|
+ span["type"] = ContentType.Text
|
|
|
+ all_spans.append(span)
|
|
|
+ return all_spans
|
|
|
+
|
|
|
+ def get_page_size(self, page_no: int): # 获取页面宽高
|
|
|
+ # 获取当前页的page对象
|
|
|
+ page = self.__docs[page_no]
|
|
|
+ # 获取当前页的宽高
|
|
|
+ page_w = page.rect.width
|
|
|
+ page_h = page.rect.height
|
|
|
+ return page_w, page_h
|
|
|
+
|
|
|
+ def __get_blocks_by_type(self, types: list, page_no: int, extra_col: list[str] = []) -> list:
|
|
|
+ blocks = []
|
|
|
+ for page_dict in self.__model_list:
|
|
|
+ layout_dets = page_dict.get("layout_dets", [])
|
|
|
+ page_info = page_dict.get("page_info", {})
|
|
|
+ page_number = page_info.get("page_no", -1)
|
|
|
+ if page_no != page_number:
|
|
|
+ continue
|
|
|
+ for item in layout_dets:
|
|
|
+ category_id = item.get("category_id", -1)
|
|
|
+ bbox = item.get("bbox", None)
|
|
|
+
|
|
|
+ if category_id in types:
|
|
|
+ block = {
|
|
|
+ "bbox": bbox
|
|
|
+ }
|
|
|
+ for col in extra_col:
|
|
|
+ block[col] = item.get(col, None)
|
|
|
+ blocks.append(block)
|
|
|
+ return blocks
|
|
|
+
|
|
|
+if __name__ == "__main__":
|
|
|
+ drw = DiskReaderWriter(r"D:/project/20231108code-clean")
|
|
|
+ if 0:
|
|
|
+ pdf_file_path = r"linshixuqiu\19983-00.pdf"
|
|
|
+ model_file_path = r"linshixuqiu\19983-00_new.json"
|
|
|
+ pdf_bytes = drw.read(pdf_file_path, AbsReaderWriter.MODE_BIN)
|
|
|
+ model_json_txt = drw.read(model_file_path, AbsReaderWriter.MODE_TXT)
|
|
|
+ model_list = json.loads(model_json_txt)
|
|
|
+ write_path = r"D:\project\20231108code-clean\linshixuqiu\19983-00"
|
|
|
+ img_bucket_path = "imgs"
|
|
|
+ img_writer = DiskReaderWriter(join_path(write_path, img_bucket_path))
|
|
|
+ pdf_docs = fitz.open("pdf", pdf_bytes)
|
|
|
+ magic_model = MagicModel(model_list, pdf_docs)
|
|
|
+
|
|
|
+ if 1:
|
|
|
+ model_list = json.loads(
|
|
|
+ drw.read("/opt/data/pdf/20240418/j.chroma.2009.03.042.json")
|
|
|
+ )
|
|
|
+ pdf_bytes = drw.read(
|
|
|
+ "/opt/data/pdf/20240418/j.chroma.2009.03.042.pdf", AbsReaderWriter.MODE_BIN
|
|
|
+ )
|
|
|
+ pdf_docs = fitz.open("pdf", pdf_bytes)
|
|
|
+ magic_model = MagicModel(model_list, pdf_docs)
|
|
|
+ for i in range(7):
|
|
|
+ print(magic_model.get_imgs(i))
|